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