toolkit/devtools/DevToolsUtils.js

Wed, 31 Dec 2014 13:27:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 13:27:57 +0100
branch
TOR_BUG_3246
changeset 6
8bccb770b82d
permissions
-rw-r--r--

Ignore runtime configuration files generated during quality assurance.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 "use strict";
     7 /* General utilities used throughout devtools. */
     9 // hasChrome is provided as a global by the loader. It is true if we are running
    10 // on the main thread, and false if we are running on a worker thread.
    11 var { Ci, Cu } = require("chrome");
    12 var Services = require("Services");
    13 var { setTimeout } = require("Timer");
    15 /**
    16  * Turn the error |aError| into a string, without fail.
    17  */
    18 exports.safeErrorString = function safeErrorString(aError) {
    19   try {
    20     let errorString = aError.toString();
    21     if (typeof errorString == "string") {
    22       // Attempt to attach a stack to |errorString|. If it throws an error, or
    23       // isn't a string, don't use it.
    24       try {
    25         if (aError.stack) {
    26           let stack = aError.stack.toString();
    27           if (typeof stack == "string") {
    28             errorString += "\nStack: " + stack;
    29           }
    30         }
    31       } catch (ee) { }
    33       // Append additional line and column number information to the output,
    34       // since it might not be part of the stringified error.
    35       if (typeof aError.lineNumber == "number" && typeof aError.columnNumber == "number") {
    36         errorString += "Line: " + aError.lineNumber + ", column: " + aError.columnNumber;
    37       }
    39       return errorString;
    40     }
    41   } catch (ee) { }
    43   return "<failed trying to find error description>";
    44 }
    46 /**
    47  * Report that |aWho| threw an exception, |aException|.
    48  */
    49 exports.reportException = function reportException(aWho, aException) {
    50   let msg = aWho + " threw an exception: " + exports.safeErrorString(aException);
    52   dump(msg + "\n");
    54   if (Cu.reportError) {
    55     /*
    56      * Note that the xpcshell test harness registers an observer for
    57      * console messages, so when we're running tests, this will cause
    58      * the test to quit.
    59      */
    60     Cu.reportError(msg);
    61   }
    62 }
    64 /**
    65  * Given a handler function that may throw, return an infallible handler
    66  * function that calls the fallible handler, and logs any exceptions it
    67  * throws.
    68  *
    69  * @param aHandler function
    70  *      A handler function, which may throw.
    71  * @param aName string
    72  *      A name for aHandler, for use in error messages. If omitted, we use
    73  *      aHandler.name.
    74  *
    75  * (SpiderMonkey does generate good names for anonymous functions, but we
    76  * don't have a way to get at them from JavaScript at the moment.)
    77  */
    78 exports.makeInfallible = function makeInfallible(aHandler, aName) {
    79   if (!aName)
    80     aName = aHandler.name;
    82   return function (/* arguments */) {
    83     try {
    84       return aHandler.apply(this, arguments);
    85     } catch (ex) {
    86       let who = "Handler function";
    87       if (aName) {
    88         who += " " + aName;
    89       }
    90       exports.reportException(who, ex);
    91     }
    92   }
    93 }
    95 /**
    96  * Interleaves two arrays element by element, returning the combined array, like
    97  * a zip. In the case of arrays with different sizes, undefined values will be
    98  * interleaved at the end along with the extra values of the larger array.
    99  *
   100  * @param Array a
   101  * @param Array b
   102  * @returns Array
   103  *          The combined array, in the form [a1, b1, a2, b2, ...]
   104  */
   105 exports.zip = function zip(a, b) {
   106   if (!b) {
   107     return a;
   108   }
   109   if (!a) {
   110     return b;
   111   }
   112   const pairs = [];
   113   for (let i = 0, aLength = a.length, bLength = b.length;
   114        i < aLength || i < bLength;
   115        i++) {
   116     pairs.push([a[i], b[i]]);
   117   }
   118   return pairs;
   119 };
   121 /**
   122  * Waits for the next tick in the event loop to execute a callback.
   123  */
   124 exports.executeSoon = function executeSoon(aFn) {
   125   Services.tm.mainThread.dispatch({
   126     run: exports.makeInfallible(aFn)
   127   }, Ci.nsIThread.DISPATCH_NORMAL);
   128 };
   130 /**
   131  * Waits for the next tick in the event loop.
   132  *
   133  * @return Promise
   134  *         A promise that is resolved after the next tick in the event loop.
   135  */
   136 exports.waitForTick = function waitForTick() {
   137   let deferred = promise.defer();
   138   exports.executeSoon(deferred.resolve);
   139   return deferred.promise;
   140 };
   142 /**
   143  * Waits for the specified amount of time to pass.
   144  *
   145  * @param number aDelay
   146  *        The amount of time to wait, in milliseconds.
   147  * @return Promise
   148  *         A promise that is resolved after the specified amount of time passes.
   149  */
   150 exports.waitForTime = function waitForTime(aDelay) {
   151   let deferred = promise.defer();
   152   setTimeout(deferred.resolve, aDelay);
   153   return deferred.promise;
   154 };
   156 /**
   157  * Like Array.prototype.forEach, but doesn't cause jankiness when iterating over
   158  * very large arrays by yielding to the browser and continuing execution on the
   159  * next tick.
   160  *
   161  * @param Array aArray
   162  *        The array being iterated over.
   163  * @param Function aFn
   164  *        The function called on each item in the array. If a promise is
   165  *        returned by this function, iterating over the array will be paused
   166  *        until the respective promise is resolved.
   167  * @returns Promise
   168  *          A promise that is resolved once the whole array has been iterated
   169  *          over, and all promises returned by the aFn callback are resolved.
   170  */
   171 exports.yieldingEach = function yieldingEach(aArray, aFn) {
   172   const deferred = promise.defer();
   174   let i = 0;
   175   let len = aArray.length;
   176   let outstanding = [deferred.promise];
   178   (function loop() {
   179     const start = Date.now();
   181     while (i < len) {
   182       // Don't block the main thread for longer than 16 ms at a time. To
   183       // maintain 60fps, you have to render every frame in at least 16ms; we
   184       // aren't including time spent in non-JS here, but this is Good
   185       // Enough(tm).
   186       if (Date.now() - start > 16) {
   187         exports.executeSoon(loop);
   188         return;
   189       }
   191       try {
   192         outstanding.push(aFn(aArray[i], i++));
   193       } catch (e) {
   194         deferred.reject(e);
   195         return;
   196       }
   197     }
   199     deferred.resolve();
   200   }());
   202   return promise.all(outstanding);
   203 }
   205 /**
   206  * Like XPCOMUtils.defineLazyGetter, but with a |this| sensitive getter that
   207  * allows the lazy getter to be defined on a prototype and work correctly with
   208  * instances.
   209  *
   210  * @param Object aObject
   211  *        The prototype object to define the lazy getter on.
   212  * @param String aKey
   213  *        The key to define the lazy getter on.
   214  * @param Function aCallback
   215  *        The callback that will be called to determine the value. Will be
   216  *        called with the |this| value of the current instance.
   217  */
   218 exports.defineLazyPrototypeGetter =
   219 function defineLazyPrototypeGetter(aObject, aKey, aCallback) {
   220   Object.defineProperty(aObject, aKey, {
   221     configurable: true,
   222     get: function() {
   223       const value = aCallback.call(this);
   225       Object.defineProperty(this, aKey, {
   226         configurable: true,
   227         writable: true,
   228         value: value
   229       });
   231       return value;
   232     }
   233   });
   234 }
   236 /**
   237  * Safely get the property value from a Debugger.Object for a given key. Walks
   238  * the prototype chain until the property is found.
   239  *
   240  * @param Debugger.Object aObject
   241  *        The Debugger.Object to get the value from.
   242  * @param String aKey
   243  *        The key to look for.
   244  * @return Any
   245  */
   246 exports.getProperty = function getProperty(aObj, aKey) {
   247   let root = aObj;
   248   try {
   249     do {
   250       const desc = aObj.getOwnPropertyDescriptor(aKey);
   251       if (desc) {
   252         if ("value" in desc) {
   253           return desc.value;
   254         }
   255         // Call the getter if it's safe.
   256         return exports.hasSafeGetter(desc) ? desc.get.call(root).return : undefined;
   257       }
   258       aObj = aObj.proto;
   259     } while (aObj);
   260   } catch (e) {
   261     // If anything goes wrong report the error and return undefined.
   262     exports.reportException("getProperty", e);
   263   }
   264   return undefined;
   265 };
   267 /**
   268  * Determines if a descriptor has a getter which doesn't call into JavaScript.
   269  *
   270  * @param Object aDesc
   271  *        The descriptor to check for a safe getter.
   272  * @return Boolean
   273  *         Whether a safe getter was found.
   274  */
   275 exports.hasSafeGetter = function hasSafeGetter(aDesc) {
   276   let fn = aDesc.get;
   277   return fn && fn.callable && fn.class == "Function" && fn.script === undefined;
   278 };
   280 /**
   281  * Check if it is safe to read properties and execute methods from the given JS
   282  * object. Safety is defined as being protected from unintended code execution
   283  * from content scripts (or cross-compartment code).
   284  *
   285  * See bugs 945920 and 946752 for discussion.
   286  *
   287  * @type Object aObj
   288  *       The object to check.
   289  * @return Boolean
   290  *         True if it is safe to read properties from aObj, or false otherwise.
   291  */
   292 exports.isSafeJSObject = function isSafeJSObject(aObj) {
   293   if (Cu.getGlobalForObject(aObj) ==
   294       Cu.getGlobalForObject(exports.isSafeJSObject)) {
   295     return true; // aObj is not a cross-compartment wrapper.
   296   }
   298   let principal = Cu.getObjectPrincipal(aObj);
   299   if (Services.scriptSecurityManager.isSystemPrincipal(principal)) {
   300     return true; // allow chrome objects
   301   }
   303   return Cu.isXrayWrapper(aObj);
   304 };
   306 exports.dumpn = function dumpn(str) {
   307   if (exports.dumpn.wantLogging) {
   308     dump("DBG-SERVER: " + str + "\n");
   309   }
   310 }
   312 // We want wantLogging to be writable. The exports object is frozen by the
   313 // loader, so define it on dumpn instead.
   314 exports.dumpn.wantLogging = false;
   316 exports.dbg_assert = function dbg_assert(cond, e) {
   317   if (!cond) {
   318     return e;
   319   }
   320 }

mercurial