Wed, 31 Dec 2014 13:27:57 +0100
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 | } |