browser/devtools/tilt/tilt-utils.js

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

     1 /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* vim: set 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 "use strict";
     8 const {Cc, Ci, Cu} = require("chrome");
    10 Cu.import("resource://gre/modules/Services.jsm");
    11 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    12 Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm");
    14 const STACK_THICKNESS = 15;
    16 /**
    17  * Module containing various helper functions used throughout Tilt.
    18  */
    19 this.TiltUtils = {};
    20 module.exports = this.TiltUtils;
    22 /**
    23  * Various console/prompt output functions required by the engine.
    24  */
    25 TiltUtils.Output = {
    27   /**
    28    * Logs a message to the console.
    29    *
    30    * @param {String} aMessage
    31    *                 the message to be logged
    32    */
    33   log: function TUO_log(aMessage)
    34   {
    35     if (this.suppressLogs) {
    36       return;
    37     }
    38     // get the console service
    39     let consoleService = Cc["@mozilla.org/consoleservice;1"]
    40       .getService(Ci.nsIConsoleService);
    42     // log the message
    43     consoleService.logStringMessage(aMessage);
    44   },
    46   /**
    47    * Logs an error to the console.
    48    *
    49    * @param {String} aMessage
    50    *                 the message to be logged
    51    * @param {Object} aProperties
    52    *                 and object containing script error initialization details
    53    */
    54   error: function TUO_error(aMessage, aProperties)
    55   {
    56     if (this.suppressErrors) {
    57       return;
    58     }
    59     // make sure the properties parameter is a valid object
    60     aProperties = aProperties || {};
    62     // get the console service
    63     let consoleService = Cc["@mozilla.org/consoleservice;1"]
    64       .getService(Ci.nsIConsoleService);
    66     // get the script error service
    67     let scriptError = Cc["@mozilla.org/scripterror;1"]
    68       .createInstance(Ci.nsIScriptError);
    70     // initialize a script error
    71     scriptError.init(aMessage,
    72       aProperties.sourceName || "",
    73       aProperties.sourceLine || "",
    74       aProperties.lineNumber || 0,
    75       aProperties.columnNumber || 0,
    76       aProperties.flags || 0,
    77       aProperties.category || "");
    79     // log the error
    80     consoleService.logMessage(scriptError);
    81   },
    83   /**
    84    * Shows a modal alert message popup.
    85    *
    86    * @param {String} aTitle
    87    *                 the title of the popup
    88    * @param {String} aMessage
    89    *                 the message to be logged
    90    */
    91   alert: function TUO_alert(aTitle, aMessage)
    92   {
    93     if (this.suppressAlerts) {
    94       return;
    95     }
    96     if (!aMessage) {
    97       aMessage = aTitle;
    98       aTitle = "";
    99     }
   101     // get the prompt service
   102     let prompt = Cc["@mozilla.org/embedcomp/prompt-service;1"]
   103       .getService(Ci.nsIPromptService);
   105     // show the alert message
   106     prompt.alert(null, aTitle, aMessage);
   107   }
   108 };
   110 /**
   111  * Helper functions for managing preferences.
   112  */
   113 TiltUtils.Preferences = {
   115   /**
   116    * Gets a custom Tilt preference.
   117    * If the preference does not exist, undefined is returned. If it does exist,
   118    * but the type is not correctly specified, null is returned.
   119    *
   120    * @param {String} aPref
   121    *                 the preference name
   122    * @param {String} aType
   123    *                 either "boolean", "string" or "integer"
   124    *
   125    * @return {Boolean | String | Number} the requested preference
   126    */
   127   get: function TUP_get(aPref, aType)
   128   {
   129     if (!aPref || !aType) {
   130       return;
   131     }
   133     try {
   134       let prefs = this._branch;
   136       switch(aType) {
   137         case "boolean":
   138           return prefs.getBoolPref(aPref);
   139         case "string":
   140           return prefs.getCharPref(aPref);
   141         case "integer":
   142           return prefs.getIntPref(aPref);
   143       }
   144       return null;
   146     } catch(e) {
   147       // handle any unexpected exceptions
   148       TiltUtils.Output.error(e.message);
   149       return undefined;
   150     }
   151   },
   153   /**
   154    * Sets a custom Tilt preference.
   155    * If the preference already exists, it is overwritten.
   156    *
   157    * @param {String} aPref
   158    *                 the preference name
   159    * @param {String} aType
   160    *                 either "boolean", "string" or "integer"
   161    * @param {String} aValue
   162    *                 a new preference value
   163    *
   164    * @return {Boolean} true if the preference was set successfully
   165    */
   166   set: function TUP_set(aPref, aType, aValue)
   167   {
   168     if (!aPref || !aType || aValue === undefined || aValue === null) {
   169       return;
   170     }
   172     try {
   173       let prefs = this._branch;
   175       switch(aType) {
   176         case "boolean":
   177           return prefs.setBoolPref(aPref, aValue);
   178         case "string":
   179           return prefs.setCharPref(aPref, aValue);
   180         case "integer":
   181           return prefs.setIntPref(aPref, aValue);
   182       }
   183     } catch(e) {
   184       // handle any unexpected exceptions
   185       TiltUtils.Output.error(e.message);
   186     }
   187     return false;
   188   },
   190   /**
   191    * Creates a custom Tilt preference.
   192    * If the preference already exists, it is left unchanged.
   193    *
   194    * @param {String} aPref
   195    *                 the preference name
   196    * @param {String} aType
   197    *                 either "boolean", "string" or "integer"
   198    * @param {String} aValue
   199    *                 the initial preference value
   200    *
   201    * @return {Boolean} true if the preference was initialized successfully
   202    */
   203   create: function TUP_create(aPref, aType, aValue)
   204   {
   205     if (!aPref || !aType || aValue === undefined || aValue === null) {
   206       return;
   207     }
   209     try {
   210       let prefs = this._branch;
   212       if (!prefs.prefHasUserValue(aPref)) {
   213         switch(aType) {
   214           case "boolean":
   215             return prefs.setBoolPref(aPref, aValue);
   216           case "string":
   217             return prefs.setCharPref(aPref, aValue);
   218           case "integer":
   219             return prefs.setIntPref(aPref, aValue);
   220         }
   221       }
   222     } catch(e) {
   223       // handle any unexpected exceptions
   224       TiltUtils.Output.error(e.message);
   225     }
   226     return false;
   227   },
   229   /**
   230    * The preferences branch for this extension.
   231    */
   232   _branch: (function(aBranch) {
   233     return Cc["@mozilla.org/preferences-service;1"]
   234       .getService(Ci.nsIPrefService)
   235       .getBranch(aBranch);
   237   }("devtools.tilt."))
   238 };
   240 /**
   241  * Easy way to access the string bundle.
   242  */
   243 TiltUtils.L10n = {
   245   /**
   246    * The string bundle element.
   247    */
   248   stringBundle: null,
   250   /**
   251    * Returns a string in the string bundle.
   252    * If the string bundle is not found, null is returned.
   253    *
   254    * @param {String} aName
   255    *                 the string name in the bundle
   256    *
   257    * @return {String} the equivalent string from the bundle
   258    */
   259   get: function TUL_get(aName)
   260   {
   261     // check to see if the parent string bundle document element is valid
   262     if (!this.stringBundle || !aName) {
   263       return null;
   264     }
   265     return this.stringBundle.GetStringFromName(aName);
   266   },
   268   /**
   269    * Returns a formatted string using the string bundle.
   270    * If the string bundle is not found, null is returned.
   271    *
   272    * @param {String} aName
   273    *                 the string name in the bundle
   274    * @param {Array} aArgs
   275    *                an array of arguments for the formatted string
   276    *
   277    * @return {String} the equivalent formatted string from the bundle
   278    */
   279   format: function TUL_format(aName, aArgs)
   280   {
   281     // check to see if the parent string bundle document element is valid
   282     if (!this.stringBundle || !aName || !aArgs) {
   283       return null;
   284     }
   285     return this.stringBundle.formatStringFromName(aName, aArgs, aArgs.length);
   286   }
   287 };
   289 /**
   290  * Utilities for accessing and manipulating a document.
   291  */
   292 TiltUtils.DOM = {
   294   /**
   295    * Current parent node object used when creating canvas elements.
   296    */
   297   parentNode: null,
   299   /**
   300    * Helper method, allowing to easily create and manage a canvas element.
   301    * If the width and height params are falsy, they default to the parent node
   302    * client width and height.
   303    *
   304    * @param {Document} aParentNode
   305    *                   the parent node used to create the canvas
   306    *                   if not specified, it will be reused from the cache
   307    * @param {Object} aProperties
   308    *                 optional, object containing some of the following props:
   309    *       {Boolean} focusable
   310    *                 optional, true to make the canvas focusable
   311    *       {Boolean} append
   312    *                 optional, true to append the canvas to the parent node
   313    *        {Number} width
   314    *                 optional, specifies the width of the canvas
   315    *        {Number} height
   316    *                 optional, specifies the height of the canvas
   317    *        {String} id
   318    *                 optional, id for the created canvas element
   319    *
   320    * @return {HTMLCanvasElement} the newly created canvas element
   321    */
   322   initCanvas: function TUD_initCanvas(aParentNode, aProperties)
   323   {
   324     // check to see if the parent node element is valid
   325     if (!(aParentNode = aParentNode || this.parentNode)) {
   326       return null;
   327     }
   329     // make sure the properties parameter is a valid object
   330     aProperties = aProperties || {};
   332     // cache this parent node so that it can be reused
   333     this.parentNode = aParentNode;
   335     // create the canvas element
   336     let canvas = aParentNode.ownerDocument.
   337       createElementNS("http://www.w3.org/1999/xhtml", "canvas");
   339     let width = aProperties.width || aParentNode.clientWidth;
   340     let height = aProperties.height || aParentNode.clientHeight;
   341     let id = aProperties.id || null;
   343     canvas.setAttribute("style", "min-width: 1px; min-height: 1px;");
   344     canvas.setAttribute("width", width);
   345     canvas.setAttribute("height", height);
   346     canvas.setAttribute("id", id);
   348     // the canvas is unfocusable by default, we may require otherwise
   349     if (aProperties.focusable) {
   350       canvas.setAttribute("tabindex", "1");
   351       canvas.style.outline = "none";
   352     }
   354     // append the canvas element to the current parent node, if specified
   355     if (aProperties.append) {
   356       aParentNode.appendChild(canvas);
   357     }
   359     return canvas;
   360   },
   362   /**
   363    * Gets the full webpage dimensions (width and height).
   364    *
   365    * @param {Window} aContentWindow
   366    *                 the content window holding the document
   367    *
   368    * @return {Object} an object containing the width and height coords
   369    */
   370   getContentWindowDimensions: function TUD_getContentWindowDimensions(
   371     aContentWindow)
   372   {
   373     return {
   374       width: aContentWindow.innerWidth + aContentWindow.scrollMaxX,
   375       height: aContentWindow.innerHeight + aContentWindow.scrollMaxY
   376     };
   377   },
   379   /**
   380    * Calculates the position and depth to display a node, this can be overriden
   381    * to change the visualization.
   382    *
   383    * @param {Window} aContentWindow
   384    *                 the window content holding the document
   385    * @param {Node}   aNode
   386    *                 the node to get the position for
   387    * @param {Object} aParentPosition
   388    *                 the position of the parent node, as returned by this
   389    *                 function
   390    *
   391    * @return {Object} an object describing the node's position in 3D space
   392    *                  containing the following properties:
   393    *         {Number} top
   394    *                  distance along the x axis
   395    *         {Number} left
   396    *                  distance along the y axis
   397    *         {Number} depth
   398    *                  distance along the z axis
   399    *         {Number} width
   400    *                  width of the node
   401    *         {Number} height
   402    *                  height of the node
   403    *         {Number} thickness
   404    *                  thickness of the node
   405    */
   406   getNodePosition: function TUD_getNodePosition(aContentWindow, aNode,
   407                                                 aParentPosition) {
   408     let lh = new LayoutHelpers(aContentWindow);
   409     // get the x, y, width and height coordinates of the node
   410     let coord = lh.getRect(aNode, aContentWindow);
   411     if (!coord) {
   412       return null;
   413     }
   415     coord.depth = aParentPosition ? (aParentPosition.depth + aParentPosition.thickness) : 0;
   416     coord.thickness = STACK_THICKNESS;
   418     return coord;
   419   },
   421   /**
   422    * Traverses a document object model & calculates useful info for each node.
   423    *
   424    * @param {Window} aContentWindow
   425    *                 the window content holding the document
   426    * @param {Object} aProperties
   427    *                 optional, an object containing the following properties:
   428    *        {Function} nodeCallback
   429    *                   a function to call instead of TiltUtils.DOM.getNodePosition
   430    *                   to get the position and depth to display nodes
   431    *        {Object} invisibleElements
   432    *                 elements which should be ignored
   433    *        {Number} minSize
   434    *                 the minimum dimensions needed for a node to be traversed
   435    *        {Number} maxX
   436    *                 the maximum left position of an element
   437    *        {Number} maxY
   438    *                 the maximum top position of an element
   439    *
   440    * @return {Array} list containing nodes positions and local names
   441    */
   442   traverse: function TUD_traverse(aContentWindow, aProperties)
   443   {
   444     // make sure the properties parameter is a valid object
   445     aProperties = aProperties || {};
   447     let aInvisibleElements = aProperties.invisibleElements || {};
   448     let aMinSize = aProperties.minSize || -1;
   449     let aMaxX = aProperties.maxX || Number.MAX_VALUE;
   450     let aMaxY = aProperties.maxY || Number.MAX_VALUE;
   452     let nodeCallback = aProperties.nodeCallback || this.getNodePosition.bind(this);
   454     let nodes = aContentWindow.document.childNodes;
   455     let store = { info: [], nodes: [] };
   456     let depth = 0;
   458     let queue = [
   459       { parentPosition: null, nodes: aContentWindow.document.childNodes }
   460     ]
   462     while (queue.length) {
   463       let { nodes, parentPosition } = queue.shift();
   465       for (let node of nodes) {
   466         // skip some nodes to avoid visualization meshes that are too bloated
   467         let name = node.localName;
   468         if (!name || aInvisibleElements[name]) {
   469           continue;
   470         }
   472         let coord = nodeCallback(aContentWindow, node, parentPosition);
   473         if (!coord) {
   474           continue;
   475         }
   477         // the maximum size slices the traversal where needed
   478         if (coord.left > aMaxX || coord.top > aMaxY) {
   479           continue;
   480         }
   482         // use this node only if it actually has visible dimensions
   483         if (coord.width > aMinSize && coord.height > aMinSize) {
   485           // save the necessary details into a list to be returned later
   486           store.info.push({ coord: coord, name: name });
   487           store.nodes.push(node);
   488         }
   490         let childNodes = (name === "iframe" || name === "frame") ? node.contentDocument.childNodes : node.childNodes;
   491         if (childNodes.length > 0)
   492           queue.push({ parentPosition: coord, nodes: childNodes });
   493       }
   494     }
   496     return store;
   497   }
   498 };
   500 /**
   501  * Binds a new owner object to the child functions.
   502  * If the new parent is not specified, it will default to the passed scope.
   503  *
   504  * @param {Object} aScope
   505  *                 the object from which all functions will be rebound
   506  * @param {String} aRegex
   507  *                 a regular expression to identify certain functions
   508  * @param {Object} aParent
   509  *                 the new parent for the object's functions
   510  */
   511 TiltUtils.bindObjectFunc = function TU_bindObjectFunc(aScope, aRegex, aParent)
   512 {
   513   if (!aScope) {
   514     return;
   515   }
   517   for (let i in aScope) {
   518     try {
   519       if ("function" === typeof aScope[i] && (aRegex ? i.match(aRegex) : 1)) {
   520         aScope[i] = aScope[i].bind(aParent || aScope);
   521       }
   522     } catch(e) {
   523       TiltUtils.Output.error(e);
   524     }
   525   }
   526 };
   528 /**
   529  * Destroys an object and deletes all members.
   530  *
   531  * @param {Object} aScope
   532  *                 the object from which all children will be destroyed
   533  */
   534 TiltUtils.destroyObject = function TU_destroyObject(aScope)
   535 {
   536   if (!aScope) {
   537     return;
   538   }
   540   // objects in Tilt usually use a function to handle internal destruction
   541   if ("function" === typeof aScope._finalize) {
   542     aScope._finalize();
   543   }
   544   for (let i in aScope) {
   545     if (aScope.hasOwnProperty(i)) {
   546       delete aScope[i];
   547     }
   548   }
   549 };
   551 /**
   552  * Retrieve the unique ID of a window object.
   553  *
   554  * @param {Window} aWindow
   555  *                 the window to get the ID from
   556  *
   557  * @return {Number} the window ID
   558  */
   559 TiltUtils.getWindowId = function TU_getWindowId(aWindow)
   560 {
   561   if (!aWindow) {
   562     return;
   563   }
   565   return aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
   566                 .getInterface(Ci.nsIDOMWindowUtils)
   567                 .currentInnerWindowID;
   568 };
   570 /**
   571  * Sets the markup document viewer zoom for the currently selected browser.
   572  *
   573  * @param {Window} aChromeWindow
   574  *                 the top-level browser window
   575  *
   576  * @param {Number} the zoom ammount
   577  */
   578 TiltUtils.setDocumentZoom = function TU_setDocumentZoom(aChromeWindow, aZoom) {
   579   aChromeWindow.gBrowser.selectedBrowser.markupDocumentViewer.fullZoom = aZoom;
   580 };
   582 /**
   583  * Performs a garbage collection.
   584  *
   585  * @param {Window} aChromeWindow
   586  *                 the top-level browser window
   587  */
   588 TiltUtils.gc = function TU_gc(aChromeWindow)
   589 {
   590   aChromeWindow.QueryInterface(Ci.nsIInterfaceRequestor)
   591                .getInterface(Ci.nsIDOMWindowUtils)
   592                .garbageCollect();
   593 };
   595 /**
   596  * Clears the cache and sets all the variables to null.
   597  */
   598 TiltUtils.clearCache = function TU_clearCache()
   599 {
   600   TiltUtils.DOM.parentNode = null;
   601 };
   603 // bind the owner object to the necessary functions
   604 TiltUtils.bindObjectFunc(TiltUtils.Output);
   605 TiltUtils.bindObjectFunc(TiltUtils.Preferences);
   606 TiltUtils.bindObjectFunc(TiltUtils.L10n);
   607 TiltUtils.bindObjectFunc(TiltUtils.DOM);
   609 // set the necessary string bundle
   610 XPCOMUtils.defineLazyGetter(TiltUtils.L10n, "stringBundle", function() {
   611   return Services.strings.createBundle(
   612     "chrome://browser/locale/devtools/tilt.properties");
   613 });

mercurial