|
1 /* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */ |
|
3 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 "use strict"; |
|
8 |
|
9 const {classes: Cc, interfaces: Ci, utils: Cu} = Components; |
|
10 const DOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils); |
|
11 |
|
12 function FontInspector(inspector, window) |
|
13 { |
|
14 this.inspector = inspector; |
|
15 this.chromeDoc = window.document; |
|
16 this.init(); |
|
17 } |
|
18 |
|
19 FontInspector.prototype = { |
|
20 init: function FI_init() { |
|
21 this.update = this.update.bind(this); |
|
22 this.onNewNode = this.onNewNode.bind(this); |
|
23 this.inspector.selection.on("new-node", this.onNewNode); |
|
24 this.inspector.sidebar.on("fontinspector-selected", this.onNewNode); |
|
25 this.showAll = this.showAll.bind(this); |
|
26 this.showAllButton = this.chromeDoc.getElementById("showall"); |
|
27 this.showAllButton.addEventListener("click", this.showAll); |
|
28 this.update(); |
|
29 }, |
|
30 |
|
31 /** |
|
32 * Is the fontinspector visible in the sidebar? |
|
33 */ |
|
34 isActive: function FI_isActive() { |
|
35 return this.inspector.sidebar && |
|
36 this.inspector.sidebar.getCurrentTabID() == "fontinspector"; |
|
37 }, |
|
38 |
|
39 /** |
|
40 * Remove listeners. |
|
41 */ |
|
42 destroy: function FI_destroy() { |
|
43 this.chromeDoc = null; |
|
44 this.inspector.sidebar.off("layoutview-selected", this.onNewNode); |
|
45 this.inspector.selection.off("new-node", this.onNewNode); |
|
46 this.showAllButton.removeEventListener("click", this.showAll); |
|
47 }, |
|
48 |
|
49 /** |
|
50 * Selection 'new-node' event handler. |
|
51 */ |
|
52 onNewNode: function FI_onNewNode() { |
|
53 if (this.isActive() && |
|
54 this.inspector.selection.isLocal() && |
|
55 this.inspector.selection.isConnected() && |
|
56 this.inspector.selection.isElementNode()) { |
|
57 this.undim(); |
|
58 this.update(); |
|
59 } else { |
|
60 this.dim(); |
|
61 } |
|
62 }, |
|
63 |
|
64 /** |
|
65 * Hide the font list. No node are selected. |
|
66 */ |
|
67 dim: function FI_dim() { |
|
68 this.chromeDoc.body.classList.add("dim"); |
|
69 this.chromeDoc.querySelector("#all-fonts").innerHTML = ""; |
|
70 }, |
|
71 |
|
72 /** |
|
73 * Show the font list. A node is selected. |
|
74 */ |
|
75 undim: function FI_undim() { |
|
76 this.chromeDoc.body.classList.remove("dim"); |
|
77 }, |
|
78 |
|
79 /** |
|
80 * Retrieve all the font related info we have for the selected |
|
81 * node and display them. |
|
82 */ |
|
83 update: function FI_update() { |
|
84 if (!this.isActive() || |
|
85 !this.inspector.selection.isConnected() || |
|
86 !this.inspector.selection.isElementNode() || |
|
87 this.chromeDoc.body.classList.contains("dim")) { |
|
88 return; |
|
89 } |
|
90 |
|
91 let node = this.inspector.selection.node; |
|
92 let contentDocument = node.ownerDocument; |
|
93 |
|
94 // We don't get fonts for a node, but for a range |
|
95 let rng = contentDocument.createRange(); |
|
96 rng.selectNode(node); |
|
97 let fonts = DOMUtils.getUsedFontFaces(rng); |
|
98 let fontsArray = []; |
|
99 for (let i = 0; i < fonts.length; i++) { |
|
100 fontsArray.push(fonts.item(i)); |
|
101 } |
|
102 fontsArray = fontsArray.sort(function(a, b) { |
|
103 return a.srcIndex < b.srcIndex; |
|
104 }); |
|
105 this.chromeDoc.querySelector("#all-fonts").innerHTML = ""; |
|
106 for (let f of fontsArray) { |
|
107 this.render(f, contentDocument); |
|
108 } |
|
109 }, |
|
110 |
|
111 /** |
|
112 * Display the information of one font. |
|
113 */ |
|
114 render: function FI_render(font, document) { |
|
115 let s = this.chromeDoc.querySelector("#template > section"); |
|
116 s = s.cloneNode(true); |
|
117 |
|
118 s.querySelector(".font-name").textContent = font.name; |
|
119 s.querySelector(".font-css-name").textContent = font.CSSFamilyName; |
|
120 s.querySelector(".font-format").textContent = font.format; |
|
121 |
|
122 if (font.srcIndex == -1) { |
|
123 s.classList.add("is-local"); |
|
124 } else { |
|
125 s.classList.add("is-remote"); |
|
126 } |
|
127 |
|
128 s.querySelector(".font-url").value = font.URI; |
|
129 |
|
130 let iframe = s.querySelector(".font-preview"); |
|
131 if (font.rule) { |
|
132 // This is the @font-face{…} code. |
|
133 let cssText = font.rule.style.parentRule.cssText; |
|
134 |
|
135 s.classList.add("has-code"); |
|
136 s.querySelector(".font-css-code").textContent = cssText; |
|
137 |
|
138 // We guess the base URL of the stylesheet to make |
|
139 // sure the font will be accessible in the preview. |
|
140 // If the font-face is in an inline <style>, we get |
|
141 // the location of the page. |
|
142 let origin = font.rule.style.parentRule.parentStyleSheet.href; |
|
143 if (!origin) { // Inline stylesheet |
|
144 origin = document.location.href; |
|
145 } |
|
146 // We remove the last part of the URL to get a correct base. |
|
147 let base = origin.replace(/\/[^\/]*$/,"/") |
|
148 |
|
149 // From all this information, we build a preview. |
|
150 this.buildPreview(iframe, font.CSSFamilyName, cssText, base); |
|
151 } else { |
|
152 this.buildPreview(iframe, font.CSSFamilyName, "", ""); |
|
153 } |
|
154 |
|
155 this.chromeDoc.querySelector("#all-fonts").appendChild(s); |
|
156 }, |
|
157 |
|
158 /** |
|
159 * Show a preview of the font in an iframe. |
|
160 */ |
|
161 buildPreview: function FI_buildPreview(iframe, name, cssCode, base) { |
|
162 /* The HTML code of the preview is: |
|
163 * <!DOCTYPE HTML> |
|
164 * <head> |
|
165 * <base href="{base}"></base> |
|
166 * </head> |
|
167 * <style> |
|
168 * p {font-family: {name};} |
|
169 * * {font-size: 40px;line-height:60px;padding:0 10px;margin:0}; |
|
170 * </style> |
|
171 * <p contenteditable spellcheck='false'>Abc</p> |
|
172 */ |
|
173 let extraCSS = "* {padding:0;margin:0}"; |
|
174 extraCSS += ".theme-dark {color: white}"; |
|
175 extraCSS += "p {font-size: 40px;line-height:60px;padding:0 10px;margin:0;}"; |
|
176 cssCode += extraCSS; |
|
177 let src = "data:text/html;charset=utf-8,<!DOCTYPE HTML><head><base></base></head><style></style><p contenteditable spellcheck='false'>Abc</p>"; |
|
178 iframe.addEventListener("load", function onload() { |
|
179 iframe.removeEventListener("load", onload, true); |
|
180 let doc = iframe.contentWindow.document; |
|
181 // We could have done that earlier, but we want to avoid any URL-encoding |
|
182 // nightmare. |
|
183 doc.querySelector("base").href = base; |
|
184 doc.querySelector("style").textContent = cssCode; |
|
185 doc.querySelector("p").style.fontFamily = name; |
|
186 // Forward theme |
|
187 doc.documentElement.className = document.documentElement.className; |
|
188 }, true); |
|
189 iframe.src = src; |
|
190 }, |
|
191 |
|
192 /** |
|
193 * Select the <body> to show all the fonts included in the document. |
|
194 */ |
|
195 showAll: function FI_showAll() { |
|
196 if (!this.isActive() || |
|
197 !this.inspector.selection.isConnected() || |
|
198 !this.inspector.selection.isElementNode()) { |
|
199 return; |
|
200 } |
|
201 |
|
202 // Select the body node to show all fonts |
|
203 let walker = this.inspector.walker; |
|
204 |
|
205 walker.getRootNode().then(root => walker.querySelector(root, "body")).then(body => { |
|
206 this.inspector.selection.setNodeFront(body, "fontinspector"); |
|
207 }); |
|
208 }, |
|
209 } |
|
210 |
|
211 window.setPanel = function(panel) { |
|
212 window.fontInspector = new FontInspector(panel, window); |
|
213 } |
|
214 |
|
215 window.onunload = function() { |
|
216 if (window.fontInspector) { |
|
217 window.fontInspector.destroy(); |
|
218 } |
|
219 } |