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.

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

mercurial