diff -r 000000000000 -r 6474c204b198 browser/devtools/tilt/tilt-utils.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/browser/devtools/tilt/tilt-utils.js Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,613 @@ +/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +const {Cc, Ci, Cu} = require("chrome"); + +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm"); + +const STACK_THICKNESS = 15; + +/** + * Module containing various helper functions used throughout Tilt. + */ +this.TiltUtils = {}; +module.exports = this.TiltUtils; + +/** + * Various console/prompt output functions required by the engine. + */ +TiltUtils.Output = { + + /** + * Logs a message to the console. + * + * @param {String} aMessage + * the message to be logged + */ + log: function TUO_log(aMessage) + { + if (this.suppressLogs) { + return; + } + // get the console service + let consoleService = Cc["@mozilla.org/consoleservice;1"] + .getService(Ci.nsIConsoleService); + + // log the message + consoleService.logStringMessage(aMessage); + }, + + /** + * Logs an error to the console. + * + * @param {String} aMessage + * the message to be logged + * @param {Object} aProperties + * and object containing script error initialization details + */ + error: function TUO_error(aMessage, aProperties) + { + if (this.suppressErrors) { + return; + } + // make sure the properties parameter is a valid object + aProperties = aProperties || {}; + + // get the console service + let consoleService = Cc["@mozilla.org/consoleservice;1"] + .getService(Ci.nsIConsoleService); + + // get the script error service + let scriptError = Cc["@mozilla.org/scripterror;1"] + .createInstance(Ci.nsIScriptError); + + // initialize a script error + scriptError.init(aMessage, + aProperties.sourceName || "", + aProperties.sourceLine || "", + aProperties.lineNumber || 0, + aProperties.columnNumber || 0, + aProperties.flags || 0, + aProperties.category || ""); + + // log the error + consoleService.logMessage(scriptError); + }, + + /** + * Shows a modal alert message popup. + * + * @param {String} aTitle + * the title of the popup + * @param {String} aMessage + * the message to be logged + */ + alert: function TUO_alert(aTitle, aMessage) + { + if (this.suppressAlerts) { + return; + } + if (!aMessage) { + aMessage = aTitle; + aTitle = ""; + } + + // get the prompt service + let prompt = Cc["@mozilla.org/embedcomp/prompt-service;1"] + .getService(Ci.nsIPromptService); + + // show the alert message + prompt.alert(null, aTitle, aMessage); + } +}; + +/** + * Helper functions for managing preferences. + */ +TiltUtils.Preferences = { + + /** + * Gets a custom Tilt preference. + * If the preference does not exist, undefined is returned. If it does exist, + * but the type is not correctly specified, null is returned. + * + * @param {String} aPref + * the preference name + * @param {String} aType + * either "boolean", "string" or "integer" + * + * @return {Boolean | String | Number} the requested preference + */ + get: function TUP_get(aPref, aType) + { + if (!aPref || !aType) { + return; + } + + try { + let prefs = this._branch; + + switch(aType) { + case "boolean": + return prefs.getBoolPref(aPref); + case "string": + return prefs.getCharPref(aPref); + case "integer": + return prefs.getIntPref(aPref); + } + return null; + + } catch(e) { + // handle any unexpected exceptions + TiltUtils.Output.error(e.message); + return undefined; + } + }, + + /** + * Sets a custom Tilt preference. + * If the preference already exists, it is overwritten. + * + * @param {String} aPref + * the preference name + * @param {String} aType + * either "boolean", "string" or "integer" + * @param {String} aValue + * a new preference value + * + * @return {Boolean} true if the preference was set successfully + */ + set: function TUP_set(aPref, aType, aValue) + { + if (!aPref || !aType || aValue === undefined || aValue === null) { + return; + } + + try { + let prefs = this._branch; + + switch(aType) { + case "boolean": + return prefs.setBoolPref(aPref, aValue); + case "string": + return prefs.setCharPref(aPref, aValue); + case "integer": + return prefs.setIntPref(aPref, aValue); + } + } catch(e) { + // handle any unexpected exceptions + TiltUtils.Output.error(e.message); + } + return false; + }, + + /** + * Creates a custom Tilt preference. + * If the preference already exists, it is left unchanged. + * + * @param {String} aPref + * the preference name + * @param {String} aType + * either "boolean", "string" or "integer" + * @param {String} aValue + * the initial preference value + * + * @return {Boolean} true if the preference was initialized successfully + */ + create: function TUP_create(aPref, aType, aValue) + { + if (!aPref || !aType || aValue === undefined || aValue === null) { + return; + } + + try { + let prefs = this._branch; + + if (!prefs.prefHasUserValue(aPref)) { + switch(aType) { + case "boolean": + return prefs.setBoolPref(aPref, aValue); + case "string": + return prefs.setCharPref(aPref, aValue); + case "integer": + return prefs.setIntPref(aPref, aValue); + } + } + } catch(e) { + // handle any unexpected exceptions + TiltUtils.Output.error(e.message); + } + return false; + }, + + /** + * The preferences branch for this extension. + */ + _branch: (function(aBranch) { + return Cc["@mozilla.org/preferences-service;1"] + .getService(Ci.nsIPrefService) + .getBranch(aBranch); + + }("devtools.tilt.")) +}; + +/** + * Easy way to access the string bundle. + */ +TiltUtils.L10n = { + + /** + * The string bundle element. + */ + stringBundle: null, + + /** + * Returns a string in the string bundle. + * If the string bundle is not found, null is returned. + * + * @param {String} aName + * the string name in the bundle + * + * @return {String} the equivalent string from the bundle + */ + get: function TUL_get(aName) + { + // check to see if the parent string bundle document element is valid + if (!this.stringBundle || !aName) { + return null; + } + return this.stringBundle.GetStringFromName(aName); + }, + + /** + * Returns a formatted string using the string bundle. + * If the string bundle is not found, null is returned. + * + * @param {String} aName + * the string name in the bundle + * @param {Array} aArgs + * an array of arguments for the formatted string + * + * @return {String} the equivalent formatted string from the bundle + */ + format: function TUL_format(aName, aArgs) + { + // check to see if the parent string bundle document element is valid + if (!this.stringBundle || !aName || !aArgs) { + return null; + } + return this.stringBundle.formatStringFromName(aName, aArgs, aArgs.length); + } +}; + +/** + * Utilities for accessing and manipulating a document. + */ +TiltUtils.DOM = { + + /** + * Current parent node object used when creating canvas elements. + */ + parentNode: null, + + /** + * Helper method, allowing to easily create and manage a canvas element. + * If the width and height params are falsy, they default to the parent node + * client width and height. + * + * @param {Document} aParentNode + * the parent node used to create the canvas + * if not specified, it will be reused from the cache + * @param {Object} aProperties + * optional, object containing some of the following props: + * {Boolean} focusable + * optional, true to make the canvas focusable + * {Boolean} append + * optional, true to append the canvas to the parent node + * {Number} width + * optional, specifies the width of the canvas + * {Number} height + * optional, specifies the height of the canvas + * {String} id + * optional, id for the created canvas element + * + * @return {HTMLCanvasElement} the newly created canvas element + */ + initCanvas: function TUD_initCanvas(aParentNode, aProperties) + { + // check to see if the parent node element is valid + if (!(aParentNode = aParentNode || this.parentNode)) { + return null; + } + + // make sure the properties parameter is a valid object + aProperties = aProperties || {}; + + // cache this parent node so that it can be reused + this.parentNode = aParentNode; + + // create the canvas element + let canvas = aParentNode.ownerDocument. + createElementNS("http://www.w3.org/1999/xhtml", "canvas"); + + let width = aProperties.width || aParentNode.clientWidth; + let height = aProperties.height || aParentNode.clientHeight; + let id = aProperties.id || null; + + canvas.setAttribute("style", "min-width: 1px; min-height: 1px;"); + canvas.setAttribute("width", width); + canvas.setAttribute("height", height); + canvas.setAttribute("id", id); + + // the canvas is unfocusable by default, we may require otherwise + if (aProperties.focusable) { + canvas.setAttribute("tabindex", "1"); + canvas.style.outline = "none"; + } + + // append the canvas element to the current parent node, if specified + if (aProperties.append) { + aParentNode.appendChild(canvas); + } + + return canvas; + }, + + /** + * Gets the full webpage dimensions (width and height). + * + * @param {Window} aContentWindow + * the content window holding the document + * + * @return {Object} an object containing the width and height coords + */ + getContentWindowDimensions: function TUD_getContentWindowDimensions( + aContentWindow) + { + return { + width: aContentWindow.innerWidth + aContentWindow.scrollMaxX, + height: aContentWindow.innerHeight + aContentWindow.scrollMaxY + }; + }, + + /** + * Calculates the position and depth to display a node, this can be overriden + * to change the visualization. + * + * @param {Window} aContentWindow + * the window content holding the document + * @param {Node} aNode + * the node to get the position for + * @param {Object} aParentPosition + * the position of the parent node, as returned by this + * function + * + * @return {Object} an object describing the node's position in 3D space + * containing the following properties: + * {Number} top + * distance along the x axis + * {Number} left + * distance along the y axis + * {Number} depth + * distance along the z axis + * {Number} width + * width of the node + * {Number} height + * height of the node + * {Number} thickness + * thickness of the node + */ + getNodePosition: function TUD_getNodePosition(aContentWindow, aNode, + aParentPosition) { + let lh = new LayoutHelpers(aContentWindow); + // get the x, y, width and height coordinates of the node + let coord = lh.getRect(aNode, aContentWindow); + if (!coord) { + return null; + } + + coord.depth = aParentPosition ? (aParentPosition.depth + aParentPosition.thickness) : 0; + coord.thickness = STACK_THICKNESS; + + return coord; + }, + + /** + * Traverses a document object model & calculates useful info for each node. + * + * @param {Window} aContentWindow + * the window content holding the document + * @param {Object} aProperties + * optional, an object containing the following properties: + * {Function} nodeCallback + * a function to call instead of TiltUtils.DOM.getNodePosition + * to get the position and depth to display nodes + * {Object} invisibleElements + * elements which should be ignored + * {Number} minSize + * the minimum dimensions needed for a node to be traversed + * {Number} maxX + * the maximum left position of an element + * {Number} maxY + * the maximum top position of an element + * + * @return {Array} list containing nodes positions and local names + */ + traverse: function TUD_traverse(aContentWindow, aProperties) + { + // make sure the properties parameter is a valid object + aProperties = aProperties || {}; + + let aInvisibleElements = aProperties.invisibleElements || {}; + let aMinSize = aProperties.minSize || -1; + let aMaxX = aProperties.maxX || Number.MAX_VALUE; + let aMaxY = aProperties.maxY || Number.MAX_VALUE; + + let nodeCallback = aProperties.nodeCallback || this.getNodePosition.bind(this); + + let nodes = aContentWindow.document.childNodes; + let store = { info: [], nodes: [] }; + let depth = 0; + + let queue = [ + { parentPosition: null, nodes: aContentWindow.document.childNodes } + ] + + while (queue.length) { + let { nodes, parentPosition } = queue.shift(); + + for (let node of nodes) { + // skip some nodes to avoid visualization meshes that are too bloated + let name = node.localName; + if (!name || aInvisibleElements[name]) { + continue; + } + + let coord = nodeCallback(aContentWindow, node, parentPosition); + if (!coord) { + continue; + } + + // the maximum size slices the traversal where needed + if (coord.left > aMaxX || coord.top > aMaxY) { + continue; + } + + // use this node only if it actually has visible dimensions + if (coord.width > aMinSize && coord.height > aMinSize) { + + // save the necessary details into a list to be returned later + store.info.push({ coord: coord, name: name }); + store.nodes.push(node); + } + + let childNodes = (name === "iframe" || name === "frame") ? node.contentDocument.childNodes : node.childNodes; + if (childNodes.length > 0) + queue.push({ parentPosition: coord, nodes: childNodes }); + } + } + + return store; + } +}; + +/** + * Binds a new owner object to the child functions. + * If the new parent is not specified, it will default to the passed scope. + * + * @param {Object} aScope + * the object from which all functions will be rebound + * @param {String} aRegex + * a regular expression to identify certain functions + * @param {Object} aParent + * the new parent for the object's functions + */ +TiltUtils.bindObjectFunc = function TU_bindObjectFunc(aScope, aRegex, aParent) +{ + if (!aScope) { + return; + } + + for (let i in aScope) { + try { + if ("function" === typeof aScope[i] && (aRegex ? i.match(aRegex) : 1)) { + aScope[i] = aScope[i].bind(aParent || aScope); + } + } catch(e) { + TiltUtils.Output.error(e); + } + } +}; + +/** + * Destroys an object and deletes all members. + * + * @param {Object} aScope + * the object from which all children will be destroyed + */ +TiltUtils.destroyObject = function TU_destroyObject(aScope) +{ + if (!aScope) { + return; + } + + // objects in Tilt usually use a function to handle internal destruction + if ("function" === typeof aScope._finalize) { + aScope._finalize(); + } + for (let i in aScope) { + if (aScope.hasOwnProperty(i)) { + delete aScope[i]; + } + } +}; + +/** + * Retrieve the unique ID of a window object. + * + * @param {Window} aWindow + * the window to get the ID from + * + * @return {Number} the window ID + */ +TiltUtils.getWindowId = function TU_getWindowId(aWindow) +{ + if (!aWindow) { + return; + } + + return aWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils) + .currentInnerWindowID; +}; + +/** + * Sets the markup document viewer zoom for the currently selected browser. + * + * @param {Window} aChromeWindow + * the top-level browser window + * + * @param {Number} the zoom ammount + */ +TiltUtils.setDocumentZoom = function TU_setDocumentZoom(aChromeWindow, aZoom) { + aChromeWindow.gBrowser.selectedBrowser.markupDocumentViewer.fullZoom = aZoom; +}; + +/** + * Performs a garbage collection. + * + * @param {Window} aChromeWindow + * the top-level browser window + */ +TiltUtils.gc = function TU_gc(aChromeWindow) +{ + aChromeWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils) + .garbageCollect(); +}; + +/** + * Clears the cache and sets all the variables to null. + */ +TiltUtils.clearCache = function TU_clearCache() +{ + TiltUtils.DOM.parentNode = null; +}; + +// bind the owner object to the necessary functions +TiltUtils.bindObjectFunc(TiltUtils.Output); +TiltUtils.bindObjectFunc(TiltUtils.Preferences); +TiltUtils.bindObjectFunc(TiltUtils.L10n); +TiltUtils.bindObjectFunc(TiltUtils.DOM); + +// set the necessary string bundle +XPCOMUtils.defineLazyGetter(TiltUtils.L10n, "stringBundle", function() { + return Services.strings.createBundle( + "chrome://browser/locale/devtools/tilt.properties"); +});