1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/devtools/fontinspector/font-inspector.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,219 @@ 1.4 +/* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +"use strict"; 1.11 + 1.12 +const {classes: Cc, interfaces: Ci, utils: Cu} = Components; 1.13 +const DOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils); 1.14 + 1.15 +function FontInspector(inspector, window) 1.16 +{ 1.17 + this.inspector = inspector; 1.18 + this.chromeDoc = window.document; 1.19 + this.init(); 1.20 +} 1.21 + 1.22 +FontInspector.prototype = { 1.23 + init: function FI_init() { 1.24 + this.update = this.update.bind(this); 1.25 + this.onNewNode = this.onNewNode.bind(this); 1.26 + this.inspector.selection.on("new-node", this.onNewNode); 1.27 + this.inspector.sidebar.on("fontinspector-selected", this.onNewNode); 1.28 + this.showAll = this.showAll.bind(this); 1.29 + this.showAllButton = this.chromeDoc.getElementById("showall"); 1.30 + this.showAllButton.addEventListener("click", this.showAll); 1.31 + this.update(); 1.32 + }, 1.33 + 1.34 + /** 1.35 + * Is the fontinspector visible in the sidebar? 1.36 + */ 1.37 + isActive: function FI_isActive() { 1.38 + return this.inspector.sidebar && 1.39 + this.inspector.sidebar.getCurrentTabID() == "fontinspector"; 1.40 + }, 1.41 + 1.42 + /** 1.43 + * Remove listeners. 1.44 + */ 1.45 + destroy: function FI_destroy() { 1.46 + this.chromeDoc = null; 1.47 + this.inspector.sidebar.off("layoutview-selected", this.onNewNode); 1.48 + this.inspector.selection.off("new-node", this.onNewNode); 1.49 + this.showAllButton.removeEventListener("click", this.showAll); 1.50 + }, 1.51 + 1.52 + /** 1.53 + * Selection 'new-node' event handler. 1.54 + */ 1.55 + onNewNode: function FI_onNewNode() { 1.56 + if (this.isActive() && 1.57 + this.inspector.selection.isLocal() && 1.58 + this.inspector.selection.isConnected() && 1.59 + this.inspector.selection.isElementNode()) { 1.60 + this.undim(); 1.61 + this.update(); 1.62 + } else { 1.63 + this.dim(); 1.64 + } 1.65 + }, 1.66 + 1.67 + /** 1.68 + * Hide the font list. No node are selected. 1.69 + */ 1.70 + dim: function FI_dim() { 1.71 + this.chromeDoc.body.classList.add("dim"); 1.72 + this.chromeDoc.querySelector("#all-fonts").innerHTML = ""; 1.73 + }, 1.74 + 1.75 + /** 1.76 + * Show the font list. A node is selected. 1.77 + */ 1.78 + undim: function FI_undim() { 1.79 + this.chromeDoc.body.classList.remove("dim"); 1.80 + }, 1.81 + 1.82 + /** 1.83 + * Retrieve all the font related info we have for the selected 1.84 + * node and display them. 1.85 + */ 1.86 + update: function FI_update() { 1.87 + if (!this.isActive() || 1.88 + !this.inspector.selection.isConnected() || 1.89 + !this.inspector.selection.isElementNode() || 1.90 + this.chromeDoc.body.classList.contains("dim")) { 1.91 + return; 1.92 + } 1.93 + 1.94 + let node = this.inspector.selection.node; 1.95 + let contentDocument = node.ownerDocument; 1.96 + 1.97 + // We don't get fonts for a node, but for a range 1.98 + let rng = contentDocument.createRange(); 1.99 + rng.selectNode(node); 1.100 + let fonts = DOMUtils.getUsedFontFaces(rng); 1.101 + let fontsArray = []; 1.102 + for (let i = 0; i < fonts.length; i++) { 1.103 + fontsArray.push(fonts.item(i)); 1.104 + } 1.105 + fontsArray = fontsArray.sort(function(a, b) { 1.106 + return a.srcIndex < b.srcIndex; 1.107 + }); 1.108 + this.chromeDoc.querySelector("#all-fonts").innerHTML = ""; 1.109 + for (let f of fontsArray) { 1.110 + this.render(f, contentDocument); 1.111 + } 1.112 + }, 1.113 + 1.114 + /** 1.115 + * Display the information of one font. 1.116 + */ 1.117 + render: function FI_render(font, document) { 1.118 + let s = this.chromeDoc.querySelector("#template > section"); 1.119 + s = s.cloneNode(true); 1.120 + 1.121 + s.querySelector(".font-name").textContent = font.name; 1.122 + s.querySelector(".font-css-name").textContent = font.CSSFamilyName; 1.123 + s.querySelector(".font-format").textContent = font.format; 1.124 + 1.125 + if (font.srcIndex == -1) { 1.126 + s.classList.add("is-local"); 1.127 + } else { 1.128 + s.classList.add("is-remote"); 1.129 + } 1.130 + 1.131 + s.querySelector(".font-url").value = font.URI; 1.132 + 1.133 + let iframe = s.querySelector(".font-preview"); 1.134 + if (font.rule) { 1.135 + // This is the @font-face{…} code. 1.136 + let cssText = font.rule.style.parentRule.cssText; 1.137 + 1.138 + s.classList.add("has-code"); 1.139 + s.querySelector(".font-css-code").textContent = cssText; 1.140 + 1.141 + // We guess the base URL of the stylesheet to make 1.142 + // sure the font will be accessible in the preview. 1.143 + // If the font-face is in an inline <style>, we get 1.144 + // the location of the page. 1.145 + let origin = font.rule.style.parentRule.parentStyleSheet.href; 1.146 + if (!origin) { // Inline stylesheet 1.147 + origin = document.location.href; 1.148 + } 1.149 + // We remove the last part of the URL to get a correct base. 1.150 + let base = origin.replace(/\/[^\/]*$/,"/") 1.151 + 1.152 + // From all this information, we build a preview. 1.153 + this.buildPreview(iframe, font.CSSFamilyName, cssText, base); 1.154 + } else { 1.155 + this.buildPreview(iframe, font.CSSFamilyName, "", ""); 1.156 + } 1.157 + 1.158 + this.chromeDoc.querySelector("#all-fonts").appendChild(s); 1.159 + }, 1.160 + 1.161 + /** 1.162 + * Show a preview of the font in an iframe. 1.163 + */ 1.164 + buildPreview: function FI_buildPreview(iframe, name, cssCode, base) { 1.165 + /* The HTML code of the preview is: 1.166 + * <!DOCTYPE HTML> 1.167 + * <head> 1.168 + * <base href="{base}"></base> 1.169 + * </head> 1.170 + * <style> 1.171 + * p {font-family: {name};} 1.172 + * * {font-size: 40px;line-height:60px;padding:0 10px;margin:0}; 1.173 + * </style> 1.174 + * <p contenteditable spellcheck='false'>Abc</p> 1.175 + */ 1.176 + let extraCSS = "* {padding:0;margin:0}"; 1.177 + extraCSS += ".theme-dark {color: white}"; 1.178 + extraCSS += "p {font-size: 40px;line-height:60px;padding:0 10px;margin:0;}"; 1.179 + cssCode += extraCSS; 1.180 + let src = "data:text/html;charset=utf-8,<!DOCTYPE HTML><head><base></base></head><style></style><p contenteditable spellcheck='false'>Abc</p>"; 1.181 + iframe.addEventListener("load", function onload() { 1.182 + iframe.removeEventListener("load", onload, true); 1.183 + let doc = iframe.contentWindow.document; 1.184 + // We could have done that earlier, but we want to avoid any URL-encoding 1.185 + // nightmare. 1.186 + doc.querySelector("base").href = base; 1.187 + doc.querySelector("style").textContent = cssCode; 1.188 + doc.querySelector("p").style.fontFamily = name; 1.189 + // Forward theme 1.190 + doc.documentElement.className = document.documentElement.className; 1.191 + }, true); 1.192 + iframe.src = src; 1.193 + }, 1.194 + 1.195 + /** 1.196 + * Select the <body> to show all the fonts included in the document. 1.197 + */ 1.198 + showAll: function FI_showAll() { 1.199 + if (!this.isActive() || 1.200 + !this.inspector.selection.isConnected() || 1.201 + !this.inspector.selection.isElementNode()) { 1.202 + return; 1.203 + } 1.204 + 1.205 + // Select the body node to show all fonts 1.206 + let walker = this.inspector.walker; 1.207 + 1.208 + walker.getRootNode().then(root => walker.querySelector(root, "body")).then(body => { 1.209 + this.inspector.selection.setNodeFront(body, "fontinspector"); 1.210 + }); 1.211 + }, 1.212 +} 1.213 + 1.214 +window.setPanel = function(panel) { 1.215 + window.fontInspector = new FontInspector(panel, window); 1.216 +} 1.217 + 1.218 +window.onunload = function() { 1.219 + if (window.fontInspector) { 1.220 + window.fontInspector.destroy(); 1.221 + } 1.222 +}