1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/devtools/DevToolsUtils.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,320 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +/* General utilities used throughout devtools. */ 1.11 + 1.12 +// hasChrome is provided as a global by the loader. It is true if we are running 1.13 +// on the main thread, and false if we are running on a worker thread. 1.14 +var { Ci, Cu } = require("chrome"); 1.15 +var Services = require("Services"); 1.16 +var { setTimeout } = require("Timer"); 1.17 + 1.18 +/** 1.19 + * Turn the error |aError| into a string, without fail. 1.20 + */ 1.21 +exports.safeErrorString = function safeErrorString(aError) { 1.22 + try { 1.23 + let errorString = aError.toString(); 1.24 + if (typeof errorString == "string") { 1.25 + // Attempt to attach a stack to |errorString|. If it throws an error, or 1.26 + // isn't a string, don't use it. 1.27 + try { 1.28 + if (aError.stack) { 1.29 + let stack = aError.stack.toString(); 1.30 + if (typeof stack == "string") { 1.31 + errorString += "\nStack: " + stack; 1.32 + } 1.33 + } 1.34 + } catch (ee) { } 1.35 + 1.36 + // Append additional line and column number information to the output, 1.37 + // since it might not be part of the stringified error. 1.38 + if (typeof aError.lineNumber == "number" && typeof aError.columnNumber == "number") { 1.39 + errorString += "Line: " + aError.lineNumber + ", column: " + aError.columnNumber; 1.40 + } 1.41 + 1.42 + return errorString; 1.43 + } 1.44 + } catch (ee) { } 1.45 + 1.46 + return "<failed trying to find error description>"; 1.47 +} 1.48 + 1.49 +/** 1.50 + * Report that |aWho| threw an exception, |aException|. 1.51 + */ 1.52 +exports.reportException = function reportException(aWho, aException) { 1.53 + let msg = aWho + " threw an exception: " + exports.safeErrorString(aException); 1.54 + 1.55 + dump(msg + "\n"); 1.56 + 1.57 + if (Cu.reportError) { 1.58 + /* 1.59 + * Note that the xpcshell test harness registers an observer for 1.60 + * console messages, so when we're running tests, this will cause 1.61 + * the test to quit. 1.62 + */ 1.63 + Cu.reportError(msg); 1.64 + } 1.65 +} 1.66 + 1.67 +/** 1.68 + * Given a handler function that may throw, return an infallible handler 1.69 + * function that calls the fallible handler, and logs any exceptions it 1.70 + * throws. 1.71 + * 1.72 + * @param aHandler function 1.73 + * A handler function, which may throw. 1.74 + * @param aName string 1.75 + * A name for aHandler, for use in error messages. If omitted, we use 1.76 + * aHandler.name. 1.77 + * 1.78 + * (SpiderMonkey does generate good names for anonymous functions, but we 1.79 + * don't have a way to get at them from JavaScript at the moment.) 1.80 + */ 1.81 +exports.makeInfallible = function makeInfallible(aHandler, aName) { 1.82 + if (!aName) 1.83 + aName = aHandler.name; 1.84 + 1.85 + return function (/* arguments */) { 1.86 + try { 1.87 + return aHandler.apply(this, arguments); 1.88 + } catch (ex) { 1.89 + let who = "Handler function"; 1.90 + if (aName) { 1.91 + who += " " + aName; 1.92 + } 1.93 + exports.reportException(who, ex); 1.94 + } 1.95 + } 1.96 +} 1.97 + 1.98 +/** 1.99 + * Interleaves two arrays element by element, returning the combined array, like 1.100 + * a zip. In the case of arrays with different sizes, undefined values will be 1.101 + * interleaved at the end along with the extra values of the larger array. 1.102 + * 1.103 + * @param Array a 1.104 + * @param Array b 1.105 + * @returns Array 1.106 + * The combined array, in the form [a1, b1, a2, b2, ...] 1.107 + */ 1.108 +exports.zip = function zip(a, b) { 1.109 + if (!b) { 1.110 + return a; 1.111 + } 1.112 + if (!a) { 1.113 + return b; 1.114 + } 1.115 + const pairs = []; 1.116 + for (let i = 0, aLength = a.length, bLength = b.length; 1.117 + i < aLength || i < bLength; 1.118 + i++) { 1.119 + pairs.push([a[i], b[i]]); 1.120 + } 1.121 + return pairs; 1.122 +}; 1.123 + 1.124 +/** 1.125 + * Waits for the next tick in the event loop to execute a callback. 1.126 + */ 1.127 +exports.executeSoon = function executeSoon(aFn) { 1.128 + Services.tm.mainThread.dispatch({ 1.129 + run: exports.makeInfallible(aFn) 1.130 + }, Ci.nsIThread.DISPATCH_NORMAL); 1.131 +}; 1.132 + 1.133 +/** 1.134 + * Waits for the next tick in the event loop. 1.135 + * 1.136 + * @return Promise 1.137 + * A promise that is resolved after the next tick in the event loop. 1.138 + */ 1.139 +exports.waitForTick = function waitForTick() { 1.140 + let deferred = promise.defer(); 1.141 + exports.executeSoon(deferred.resolve); 1.142 + return deferred.promise; 1.143 +}; 1.144 + 1.145 +/** 1.146 + * Waits for the specified amount of time to pass. 1.147 + * 1.148 + * @param number aDelay 1.149 + * The amount of time to wait, in milliseconds. 1.150 + * @return Promise 1.151 + * A promise that is resolved after the specified amount of time passes. 1.152 + */ 1.153 +exports.waitForTime = function waitForTime(aDelay) { 1.154 + let deferred = promise.defer(); 1.155 + setTimeout(deferred.resolve, aDelay); 1.156 + return deferred.promise; 1.157 +}; 1.158 + 1.159 +/** 1.160 + * Like Array.prototype.forEach, but doesn't cause jankiness when iterating over 1.161 + * very large arrays by yielding to the browser and continuing execution on the 1.162 + * next tick. 1.163 + * 1.164 + * @param Array aArray 1.165 + * The array being iterated over. 1.166 + * @param Function aFn 1.167 + * The function called on each item in the array. If a promise is 1.168 + * returned by this function, iterating over the array will be paused 1.169 + * until the respective promise is resolved. 1.170 + * @returns Promise 1.171 + * A promise that is resolved once the whole array has been iterated 1.172 + * over, and all promises returned by the aFn callback are resolved. 1.173 + */ 1.174 +exports.yieldingEach = function yieldingEach(aArray, aFn) { 1.175 + const deferred = promise.defer(); 1.176 + 1.177 + let i = 0; 1.178 + let len = aArray.length; 1.179 + let outstanding = [deferred.promise]; 1.180 + 1.181 + (function loop() { 1.182 + const start = Date.now(); 1.183 + 1.184 + while (i < len) { 1.185 + // Don't block the main thread for longer than 16 ms at a time. To 1.186 + // maintain 60fps, you have to render every frame in at least 16ms; we 1.187 + // aren't including time spent in non-JS here, but this is Good 1.188 + // Enough(tm). 1.189 + if (Date.now() - start > 16) { 1.190 + exports.executeSoon(loop); 1.191 + return; 1.192 + } 1.193 + 1.194 + try { 1.195 + outstanding.push(aFn(aArray[i], i++)); 1.196 + } catch (e) { 1.197 + deferred.reject(e); 1.198 + return; 1.199 + } 1.200 + } 1.201 + 1.202 + deferred.resolve(); 1.203 + }()); 1.204 + 1.205 + return promise.all(outstanding); 1.206 +} 1.207 + 1.208 +/** 1.209 + * Like XPCOMUtils.defineLazyGetter, but with a |this| sensitive getter that 1.210 + * allows the lazy getter to be defined on a prototype and work correctly with 1.211 + * instances. 1.212 + * 1.213 + * @param Object aObject 1.214 + * The prototype object to define the lazy getter on. 1.215 + * @param String aKey 1.216 + * The key to define the lazy getter on. 1.217 + * @param Function aCallback 1.218 + * The callback that will be called to determine the value. Will be 1.219 + * called with the |this| value of the current instance. 1.220 + */ 1.221 +exports.defineLazyPrototypeGetter = 1.222 +function defineLazyPrototypeGetter(aObject, aKey, aCallback) { 1.223 + Object.defineProperty(aObject, aKey, { 1.224 + configurable: true, 1.225 + get: function() { 1.226 + const value = aCallback.call(this); 1.227 + 1.228 + Object.defineProperty(this, aKey, { 1.229 + configurable: true, 1.230 + writable: true, 1.231 + value: value 1.232 + }); 1.233 + 1.234 + return value; 1.235 + } 1.236 + }); 1.237 +} 1.238 + 1.239 +/** 1.240 + * Safely get the property value from a Debugger.Object for a given key. Walks 1.241 + * the prototype chain until the property is found. 1.242 + * 1.243 + * @param Debugger.Object aObject 1.244 + * The Debugger.Object to get the value from. 1.245 + * @param String aKey 1.246 + * The key to look for. 1.247 + * @return Any 1.248 + */ 1.249 +exports.getProperty = function getProperty(aObj, aKey) { 1.250 + let root = aObj; 1.251 + try { 1.252 + do { 1.253 + const desc = aObj.getOwnPropertyDescriptor(aKey); 1.254 + if (desc) { 1.255 + if ("value" in desc) { 1.256 + return desc.value; 1.257 + } 1.258 + // Call the getter if it's safe. 1.259 + return exports.hasSafeGetter(desc) ? desc.get.call(root).return : undefined; 1.260 + } 1.261 + aObj = aObj.proto; 1.262 + } while (aObj); 1.263 + } catch (e) { 1.264 + // If anything goes wrong report the error and return undefined. 1.265 + exports.reportException("getProperty", e); 1.266 + } 1.267 + return undefined; 1.268 +}; 1.269 + 1.270 +/** 1.271 + * Determines if a descriptor has a getter which doesn't call into JavaScript. 1.272 + * 1.273 + * @param Object aDesc 1.274 + * The descriptor to check for a safe getter. 1.275 + * @return Boolean 1.276 + * Whether a safe getter was found. 1.277 + */ 1.278 +exports.hasSafeGetter = function hasSafeGetter(aDesc) { 1.279 + let fn = aDesc.get; 1.280 + return fn && fn.callable && fn.class == "Function" && fn.script === undefined; 1.281 +}; 1.282 + 1.283 +/** 1.284 + * Check if it is safe to read properties and execute methods from the given JS 1.285 + * object. Safety is defined as being protected from unintended code execution 1.286 + * from content scripts (or cross-compartment code). 1.287 + * 1.288 + * See bugs 945920 and 946752 for discussion. 1.289 + * 1.290 + * @type Object aObj 1.291 + * The object to check. 1.292 + * @return Boolean 1.293 + * True if it is safe to read properties from aObj, or false otherwise. 1.294 + */ 1.295 +exports.isSafeJSObject = function isSafeJSObject(aObj) { 1.296 + if (Cu.getGlobalForObject(aObj) == 1.297 + Cu.getGlobalForObject(exports.isSafeJSObject)) { 1.298 + return true; // aObj is not a cross-compartment wrapper. 1.299 + } 1.300 + 1.301 + let principal = Cu.getObjectPrincipal(aObj); 1.302 + if (Services.scriptSecurityManager.isSystemPrincipal(principal)) { 1.303 + return true; // allow chrome objects 1.304 + } 1.305 + 1.306 + return Cu.isXrayWrapper(aObj); 1.307 +}; 1.308 + 1.309 +exports.dumpn = function dumpn(str) { 1.310 + if (exports.dumpn.wantLogging) { 1.311 + dump("DBG-SERVER: " + str + "\n"); 1.312 + } 1.313 +} 1.314 + 1.315 +// We want wantLogging to be writable. The exports object is frozen by the 1.316 +// loader, so define it on dumpn instead. 1.317 +exports.dumpn.wantLogging = false; 1.318 + 1.319 +exports.dbg_assert = function dbg_assert(cond, e) { 1.320 + if (!cond) { 1.321 + return e; 1.322 + } 1.323 +}