1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/devtools/tilt/tilt-utils.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,613 @@ 1.4 +/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set 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 +"use strict"; 1.10 + 1.11 +const {Cc, Ci, Cu} = require("chrome"); 1.12 + 1.13 +Cu.import("resource://gre/modules/Services.jsm"); 1.14 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.15 +Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm"); 1.16 + 1.17 +const STACK_THICKNESS = 15; 1.18 + 1.19 +/** 1.20 + * Module containing various helper functions used throughout Tilt. 1.21 + */ 1.22 +this.TiltUtils = {}; 1.23 +module.exports = this.TiltUtils; 1.24 + 1.25 +/** 1.26 + * Various console/prompt output functions required by the engine. 1.27 + */ 1.28 +TiltUtils.Output = { 1.29 + 1.30 + /** 1.31 + * Logs a message to the console. 1.32 + * 1.33 + * @param {String} aMessage 1.34 + * the message to be logged 1.35 + */ 1.36 + log: function TUO_log(aMessage) 1.37 + { 1.38 + if (this.suppressLogs) { 1.39 + return; 1.40 + } 1.41 + // get the console service 1.42 + let consoleService = Cc["@mozilla.org/consoleservice;1"] 1.43 + .getService(Ci.nsIConsoleService); 1.44 + 1.45 + // log the message 1.46 + consoleService.logStringMessage(aMessage); 1.47 + }, 1.48 + 1.49 + /** 1.50 + * Logs an error to the console. 1.51 + * 1.52 + * @param {String} aMessage 1.53 + * the message to be logged 1.54 + * @param {Object} aProperties 1.55 + * and object containing script error initialization details 1.56 + */ 1.57 + error: function TUO_error(aMessage, aProperties) 1.58 + { 1.59 + if (this.suppressErrors) { 1.60 + return; 1.61 + } 1.62 + // make sure the properties parameter is a valid object 1.63 + aProperties = aProperties || {}; 1.64 + 1.65 + // get the console service 1.66 + let consoleService = Cc["@mozilla.org/consoleservice;1"] 1.67 + .getService(Ci.nsIConsoleService); 1.68 + 1.69 + // get the script error service 1.70 + let scriptError = Cc["@mozilla.org/scripterror;1"] 1.71 + .createInstance(Ci.nsIScriptError); 1.72 + 1.73 + // initialize a script error 1.74 + scriptError.init(aMessage, 1.75 + aProperties.sourceName || "", 1.76 + aProperties.sourceLine || "", 1.77 + aProperties.lineNumber || 0, 1.78 + aProperties.columnNumber || 0, 1.79 + aProperties.flags || 0, 1.80 + aProperties.category || ""); 1.81 + 1.82 + // log the error 1.83 + consoleService.logMessage(scriptError); 1.84 + }, 1.85 + 1.86 + /** 1.87 + * Shows a modal alert message popup. 1.88 + * 1.89 + * @param {String} aTitle 1.90 + * the title of the popup 1.91 + * @param {String} aMessage 1.92 + * the message to be logged 1.93 + */ 1.94 + alert: function TUO_alert(aTitle, aMessage) 1.95 + { 1.96 + if (this.suppressAlerts) { 1.97 + return; 1.98 + } 1.99 + if (!aMessage) { 1.100 + aMessage = aTitle; 1.101 + aTitle = ""; 1.102 + } 1.103 + 1.104 + // get the prompt service 1.105 + let prompt = Cc["@mozilla.org/embedcomp/prompt-service;1"] 1.106 + .getService(Ci.nsIPromptService); 1.107 + 1.108 + // show the alert message 1.109 + prompt.alert(null, aTitle, aMessage); 1.110 + } 1.111 +}; 1.112 + 1.113 +/** 1.114 + * Helper functions for managing preferences. 1.115 + */ 1.116 +TiltUtils.Preferences = { 1.117 + 1.118 + /** 1.119 + * Gets a custom Tilt preference. 1.120 + * If the preference does not exist, undefined is returned. If it does exist, 1.121 + * but the type is not correctly specified, null is returned. 1.122 + * 1.123 + * @param {String} aPref 1.124 + * the preference name 1.125 + * @param {String} aType 1.126 + * either "boolean", "string" or "integer" 1.127 + * 1.128 + * @return {Boolean | String | Number} the requested preference 1.129 + */ 1.130 + get: function TUP_get(aPref, aType) 1.131 + { 1.132 + if (!aPref || !aType) { 1.133 + return; 1.134 + } 1.135 + 1.136 + try { 1.137 + let prefs = this._branch; 1.138 + 1.139 + switch(aType) { 1.140 + case "boolean": 1.141 + return prefs.getBoolPref(aPref); 1.142 + case "string": 1.143 + return prefs.getCharPref(aPref); 1.144 + case "integer": 1.145 + return prefs.getIntPref(aPref); 1.146 + } 1.147 + return null; 1.148 + 1.149 + } catch(e) { 1.150 + // handle any unexpected exceptions 1.151 + TiltUtils.Output.error(e.message); 1.152 + return undefined; 1.153 + } 1.154 + }, 1.155 + 1.156 + /** 1.157 + * Sets a custom Tilt preference. 1.158 + * If the preference already exists, it is overwritten. 1.159 + * 1.160 + * @param {String} aPref 1.161 + * the preference name 1.162 + * @param {String} aType 1.163 + * either "boolean", "string" or "integer" 1.164 + * @param {String} aValue 1.165 + * a new preference value 1.166 + * 1.167 + * @return {Boolean} true if the preference was set successfully 1.168 + */ 1.169 + set: function TUP_set(aPref, aType, aValue) 1.170 + { 1.171 + if (!aPref || !aType || aValue === undefined || aValue === null) { 1.172 + return; 1.173 + } 1.174 + 1.175 + try { 1.176 + let prefs = this._branch; 1.177 + 1.178 + switch(aType) { 1.179 + case "boolean": 1.180 + return prefs.setBoolPref(aPref, aValue); 1.181 + case "string": 1.182 + return prefs.setCharPref(aPref, aValue); 1.183 + case "integer": 1.184 + return prefs.setIntPref(aPref, aValue); 1.185 + } 1.186 + } catch(e) { 1.187 + // handle any unexpected exceptions 1.188 + TiltUtils.Output.error(e.message); 1.189 + } 1.190 + return false; 1.191 + }, 1.192 + 1.193 + /** 1.194 + * Creates a custom Tilt preference. 1.195 + * If the preference already exists, it is left unchanged. 1.196 + * 1.197 + * @param {String} aPref 1.198 + * the preference name 1.199 + * @param {String} aType 1.200 + * either "boolean", "string" or "integer" 1.201 + * @param {String} aValue 1.202 + * the initial preference value 1.203 + * 1.204 + * @return {Boolean} true if the preference was initialized successfully 1.205 + */ 1.206 + create: function TUP_create(aPref, aType, aValue) 1.207 + { 1.208 + if (!aPref || !aType || aValue === undefined || aValue === null) { 1.209 + return; 1.210 + } 1.211 + 1.212 + try { 1.213 + let prefs = this._branch; 1.214 + 1.215 + if (!prefs.prefHasUserValue(aPref)) { 1.216 + switch(aType) { 1.217 + case "boolean": 1.218 + return prefs.setBoolPref(aPref, aValue); 1.219 + case "string": 1.220 + return prefs.setCharPref(aPref, aValue); 1.221 + case "integer": 1.222 + return prefs.setIntPref(aPref, aValue); 1.223 + } 1.224 + } 1.225 + } catch(e) { 1.226 + // handle any unexpected exceptions 1.227 + TiltUtils.Output.error(e.message); 1.228 + } 1.229 + return false; 1.230 + }, 1.231 + 1.232 + /** 1.233 + * The preferences branch for this extension. 1.234 + */ 1.235 + _branch: (function(aBranch) { 1.236 + return Cc["@mozilla.org/preferences-service;1"] 1.237 + .getService(Ci.nsIPrefService) 1.238 + .getBranch(aBranch); 1.239 + 1.240 + }("devtools.tilt.")) 1.241 +}; 1.242 + 1.243 +/** 1.244 + * Easy way to access the string bundle. 1.245 + */ 1.246 +TiltUtils.L10n = { 1.247 + 1.248 + /** 1.249 + * The string bundle element. 1.250 + */ 1.251 + stringBundle: null, 1.252 + 1.253 + /** 1.254 + * Returns a string in the string bundle. 1.255 + * If the string bundle is not found, null is returned. 1.256 + * 1.257 + * @param {String} aName 1.258 + * the string name in the bundle 1.259 + * 1.260 + * @return {String} the equivalent string from the bundle 1.261 + */ 1.262 + get: function TUL_get(aName) 1.263 + { 1.264 + // check to see if the parent string bundle document element is valid 1.265 + if (!this.stringBundle || !aName) { 1.266 + return null; 1.267 + } 1.268 + return this.stringBundle.GetStringFromName(aName); 1.269 + }, 1.270 + 1.271 + /** 1.272 + * Returns a formatted string using the string bundle. 1.273 + * If the string bundle is not found, null is returned. 1.274 + * 1.275 + * @param {String} aName 1.276 + * the string name in the bundle 1.277 + * @param {Array} aArgs 1.278 + * an array of arguments for the formatted string 1.279 + * 1.280 + * @return {String} the equivalent formatted string from the bundle 1.281 + */ 1.282 + format: function TUL_format(aName, aArgs) 1.283 + { 1.284 + // check to see if the parent string bundle document element is valid 1.285 + if (!this.stringBundle || !aName || !aArgs) { 1.286 + return null; 1.287 + } 1.288 + return this.stringBundle.formatStringFromName(aName, aArgs, aArgs.length); 1.289 + } 1.290 +}; 1.291 + 1.292 +/** 1.293 + * Utilities for accessing and manipulating a document. 1.294 + */ 1.295 +TiltUtils.DOM = { 1.296 + 1.297 + /** 1.298 + * Current parent node object used when creating canvas elements. 1.299 + */ 1.300 + parentNode: null, 1.301 + 1.302 + /** 1.303 + * Helper method, allowing to easily create and manage a canvas element. 1.304 + * If the width and height params are falsy, they default to the parent node 1.305 + * client width and height. 1.306 + * 1.307 + * @param {Document} aParentNode 1.308 + * the parent node used to create the canvas 1.309 + * if not specified, it will be reused from the cache 1.310 + * @param {Object} aProperties 1.311 + * optional, object containing some of the following props: 1.312 + * {Boolean} focusable 1.313 + * optional, true to make the canvas focusable 1.314 + * {Boolean} append 1.315 + * optional, true to append the canvas to the parent node 1.316 + * {Number} width 1.317 + * optional, specifies the width of the canvas 1.318 + * {Number} height 1.319 + * optional, specifies the height of the canvas 1.320 + * {String} id 1.321 + * optional, id for the created canvas element 1.322 + * 1.323 + * @return {HTMLCanvasElement} the newly created canvas element 1.324 + */ 1.325 + initCanvas: function TUD_initCanvas(aParentNode, aProperties) 1.326 + { 1.327 + // check to see if the parent node element is valid 1.328 + if (!(aParentNode = aParentNode || this.parentNode)) { 1.329 + return null; 1.330 + } 1.331 + 1.332 + // make sure the properties parameter is a valid object 1.333 + aProperties = aProperties || {}; 1.334 + 1.335 + // cache this parent node so that it can be reused 1.336 + this.parentNode = aParentNode; 1.337 + 1.338 + // create the canvas element 1.339 + let canvas = aParentNode.ownerDocument. 1.340 + createElementNS("http://www.w3.org/1999/xhtml", "canvas"); 1.341 + 1.342 + let width = aProperties.width || aParentNode.clientWidth; 1.343 + let height = aProperties.height || aParentNode.clientHeight; 1.344 + let id = aProperties.id || null; 1.345 + 1.346 + canvas.setAttribute("style", "min-width: 1px; min-height: 1px;"); 1.347 + canvas.setAttribute("width", width); 1.348 + canvas.setAttribute("height", height); 1.349 + canvas.setAttribute("id", id); 1.350 + 1.351 + // the canvas is unfocusable by default, we may require otherwise 1.352 + if (aProperties.focusable) { 1.353 + canvas.setAttribute("tabindex", "1"); 1.354 + canvas.style.outline = "none"; 1.355 + } 1.356 + 1.357 + // append the canvas element to the current parent node, if specified 1.358 + if (aProperties.append) { 1.359 + aParentNode.appendChild(canvas); 1.360 + } 1.361 + 1.362 + return canvas; 1.363 + }, 1.364 + 1.365 + /** 1.366 + * Gets the full webpage dimensions (width and height). 1.367 + * 1.368 + * @param {Window} aContentWindow 1.369 + * the content window holding the document 1.370 + * 1.371 + * @return {Object} an object containing the width and height coords 1.372 + */ 1.373 + getContentWindowDimensions: function TUD_getContentWindowDimensions( 1.374 + aContentWindow) 1.375 + { 1.376 + return { 1.377 + width: aContentWindow.innerWidth + aContentWindow.scrollMaxX, 1.378 + height: aContentWindow.innerHeight + aContentWindow.scrollMaxY 1.379 + }; 1.380 + }, 1.381 + 1.382 + /** 1.383 + * Calculates the position and depth to display a node, this can be overriden 1.384 + * to change the visualization. 1.385 + * 1.386 + * @param {Window} aContentWindow 1.387 + * the window content holding the document 1.388 + * @param {Node} aNode 1.389 + * the node to get the position for 1.390 + * @param {Object} aParentPosition 1.391 + * the position of the parent node, as returned by this 1.392 + * function 1.393 + * 1.394 + * @return {Object} an object describing the node's position in 3D space 1.395 + * containing the following properties: 1.396 + * {Number} top 1.397 + * distance along the x axis 1.398 + * {Number} left 1.399 + * distance along the y axis 1.400 + * {Number} depth 1.401 + * distance along the z axis 1.402 + * {Number} width 1.403 + * width of the node 1.404 + * {Number} height 1.405 + * height of the node 1.406 + * {Number} thickness 1.407 + * thickness of the node 1.408 + */ 1.409 + getNodePosition: function TUD_getNodePosition(aContentWindow, aNode, 1.410 + aParentPosition) { 1.411 + let lh = new LayoutHelpers(aContentWindow); 1.412 + // get the x, y, width and height coordinates of the node 1.413 + let coord = lh.getRect(aNode, aContentWindow); 1.414 + if (!coord) { 1.415 + return null; 1.416 + } 1.417 + 1.418 + coord.depth = aParentPosition ? (aParentPosition.depth + aParentPosition.thickness) : 0; 1.419 + coord.thickness = STACK_THICKNESS; 1.420 + 1.421 + return coord; 1.422 + }, 1.423 + 1.424 + /** 1.425 + * Traverses a document object model & calculates useful info for each node. 1.426 + * 1.427 + * @param {Window} aContentWindow 1.428 + * the window content holding the document 1.429 + * @param {Object} aProperties 1.430 + * optional, an object containing the following properties: 1.431 + * {Function} nodeCallback 1.432 + * a function to call instead of TiltUtils.DOM.getNodePosition 1.433 + * to get the position and depth to display nodes 1.434 + * {Object} invisibleElements 1.435 + * elements which should be ignored 1.436 + * {Number} minSize 1.437 + * the minimum dimensions needed for a node to be traversed 1.438 + * {Number} maxX 1.439 + * the maximum left position of an element 1.440 + * {Number} maxY 1.441 + * the maximum top position of an element 1.442 + * 1.443 + * @return {Array} list containing nodes positions and local names 1.444 + */ 1.445 + traverse: function TUD_traverse(aContentWindow, aProperties) 1.446 + { 1.447 + // make sure the properties parameter is a valid object 1.448 + aProperties = aProperties || {}; 1.449 + 1.450 + let aInvisibleElements = aProperties.invisibleElements || {}; 1.451 + let aMinSize = aProperties.minSize || -1; 1.452 + let aMaxX = aProperties.maxX || Number.MAX_VALUE; 1.453 + let aMaxY = aProperties.maxY || Number.MAX_VALUE; 1.454 + 1.455 + let nodeCallback = aProperties.nodeCallback || this.getNodePosition.bind(this); 1.456 + 1.457 + let nodes = aContentWindow.document.childNodes; 1.458 + let store = { info: [], nodes: [] }; 1.459 + let depth = 0; 1.460 + 1.461 + let queue = [ 1.462 + { parentPosition: null, nodes: aContentWindow.document.childNodes } 1.463 + ] 1.464 + 1.465 + while (queue.length) { 1.466 + let { nodes, parentPosition } = queue.shift(); 1.467 + 1.468 + for (let node of nodes) { 1.469 + // skip some nodes to avoid visualization meshes that are too bloated 1.470 + let name = node.localName; 1.471 + if (!name || aInvisibleElements[name]) { 1.472 + continue; 1.473 + } 1.474 + 1.475 + let coord = nodeCallback(aContentWindow, node, parentPosition); 1.476 + if (!coord) { 1.477 + continue; 1.478 + } 1.479 + 1.480 + // the maximum size slices the traversal where needed 1.481 + if (coord.left > aMaxX || coord.top > aMaxY) { 1.482 + continue; 1.483 + } 1.484 + 1.485 + // use this node only if it actually has visible dimensions 1.486 + if (coord.width > aMinSize && coord.height > aMinSize) { 1.487 + 1.488 + // save the necessary details into a list to be returned later 1.489 + store.info.push({ coord: coord, name: name }); 1.490 + store.nodes.push(node); 1.491 + } 1.492 + 1.493 + let childNodes = (name === "iframe" || name === "frame") ? node.contentDocument.childNodes : node.childNodes; 1.494 + if (childNodes.length > 0) 1.495 + queue.push({ parentPosition: coord, nodes: childNodes }); 1.496 + } 1.497 + } 1.498 + 1.499 + return store; 1.500 + } 1.501 +}; 1.502 + 1.503 +/** 1.504 + * Binds a new owner object to the child functions. 1.505 + * If the new parent is not specified, it will default to the passed scope. 1.506 + * 1.507 + * @param {Object} aScope 1.508 + * the object from which all functions will be rebound 1.509 + * @param {String} aRegex 1.510 + * a regular expression to identify certain functions 1.511 + * @param {Object} aParent 1.512 + * the new parent for the object's functions 1.513 + */ 1.514 +TiltUtils.bindObjectFunc = function TU_bindObjectFunc(aScope, aRegex, aParent) 1.515 +{ 1.516 + if (!aScope) { 1.517 + return; 1.518 + } 1.519 + 1.520 + for (let i in aScope) { 1.521 + try { 1.522 + if ("function" === typeof aScope[i] && (aRegex ? i.match(aRegex) : 1)) { 1.523 + aScope[i] = aScope[i].bind(aParent || aScope); 1.524 + } 1.525 + } catch(e) { 1.526 + TiltUtils.Output.error(e); 1.527 + } 1.528 + } 1.529 +}; 1.530 + 1.531 +/** 1.532 + * Destroys an object and deletes all members. 1.533 + * 1.534 + * @param {Object} aScope 1.535 + * the object from which all children will be destroyed 1.536 + */ 1.537 +TiltUtils.destroyObject = function TU_destroyObject(aScope) 1.538 +{ 1.539 + if (!aScope) { 1.540 + return; 1.541 + } 1.542 + 1.543 + // objects in Tilt usually use a function to handle internal destruction 1.544 + if ("function" === typeof aScope._finalize) { 1.545 + aScope._finalize(); 1.546 + } 1.547 + for (let i in aScope) { 1.548 + if (aScope.hasOwnProperty(i)) { 1.549 + delete aScope[i]; 1.550 + } 1.551 + } 1.552 +}; 1.553 + 1.554 +/** 1.555 + * Retrieve the unique ID of a window object. 1.556 + * 1.557 + * @param {Window} aWindow 1.558 + * the window to get the ID from 1.559 + * 1.560 + * @return {Number} the window ID 1.561 + */ 1.562 +TiltUtils.getWindowId = function TU_getWindowId(aWindow) 1.563 +{ 1.564 + if (!aWindow) { 1.565 + return; 1.566 + } 1.567 + 1.568 + return aWindow.QueryInterface(Ci.nsIInterfaceRequestor) 1.569 + .getInterface(Ci.nsIDOMWindowUtils) 1.570 + .currentInnerWindowID; 1.571 +}; 1.572 + 1.573 +/** 1.574 + * Sets the markup document viewer zoom for the currently selected browser. 1.575 + * 1.576 + * @param {Window} aChromeWindow 1.577 + * the top-level browser window 1.578 + * 1.579 + * @param {Number} the zoom ammount 1.580 + */ 1.581 +TiltUtils.setDocumentZoom = function TU_setDocumentZoom(aChromeWindow, aZoom) { 1.582 + aChromeWindow.gBrowser.selectedBrowser.markupDocumentViewer.fullZoom = aZoom; 1.583 +}; 1.584 + 1.585 +/** 1.586 + * Performs a garbage collection. 1.587 + * 1.588 + * @param {Window} aChromeWindow 1.589 + * the top-level browser window 1.590 + */ 1.591 +TiltUtils.gc = function TU_gc(aChromeWindow) 1.592 +{ 1.593 + aChromeWindow.QueryInterface(Ci.nsIInterfaceRequestor) 1.594 + .getInterface(Ci.nsIDOMWindowUtils) 1.595 + .garbageCollect(); 1.596 +}; 1.597 + 1.598 +/** 1.599 + * Clears the cache and sets all the variables to null. 1.600 + */ 1.601 +TiltUtils.clearCache = function TU_clearCache() 1.602 +{ 1.603 + TiltUtils.DOM.parentNode = null; 1.604 +}; 1.605 + 1.606 +// bind the owner object to the necessary functions 1.607 +TiltUtils.bindObjectFunc(TiltUtils.Output); 1.608 +TiltUtils.bindObjectFunc(TiltUtils.Preferences); 1.609 +TiltUtils.bindObjectFunc(TiltUtils.L10n); 1.610 +TiltUtils.bindObjectFunc(TiltUtils.DOM); 1.611 + 1.612 +// set the necessary string bundle 1.613 +XPCOMUtils.defineLazyGetter(TiltUtils.L10n, "stringBundle", function() { 1.614 + return Services.strings.createBundle( 1.615 + "chrome://browser/locale/devtools/tilt.properties"); 1.616 +});