|
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/. */ |
|
4 /* This code is loaded in every child process that is started by mochitest in |
|
5 * order to be used as a replacement for UniversalXPConnect |
|
6 */ |
|
7 |
|
8 var Ci = Components.interfaces; |
|
9 var Cc = Components.classes; |
|
10 var Cu = Components.utils; |
|
11 |
|
12 Cu.import("resource://specialpowers/MockFilePicker.jsm"); |
|
13 Cu.import("resource://specialpowers/MockColorPicker.jsm"); |
|
14 Cu.import("resource://specialpowers/MockPermissionPrompt.jsm"); |
|
15 Cu.import("resource://gre/modules/Services.jsm"); |
|
16 Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm"); |
|
17 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
18 |
|
19 function SpecialPowersAPI() { |
|
20 this._consoleListeners = []; |
|
21 this._encounteredCrashDumpFiles = []; |
|
22 this._unexpectedCrashDumpFiles = { }; |
|
23 this._crashDumpDir = null; |
|
24 this._mfl = null; |
|
25 this._prefEnvUndoStack = []; |
|
26 this._pendingPrefs = []; |
|
27 this._applyingPrefs = false; |
|
28 this._permissionsUndoStack = []; |
|
29 this._pendingPermissions = []; |
|
30 this._applyingPermissions = false; |
|
31 this._fm = null; |
|
32 this._cb = null; |
|
33 } |
|
34 |
|
35 function bindDOMWindowUtils(aWindow) { |
|
36 if (!aWindow) |
|
37 return |
|
38 |
|
39 var util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) |
|
40 .getInterface(Ci.nsIDOMWindowUtils); |
|
41 return wrapPrivileged(util); |
|
42 } |
|
43 |
|
44 function getRawComponents(aWindow) { |
|
45 // If we're running in automation that supports enablePrivilege, then we also |
|
46 // provided access to the privileged Components. |
|
47 try { |
|
48 let win = Cu.waiveXrays(aWindow); |
|
49 if (typeof win.netscape.security.PrivilegeManager == 'object') |
|
50 Cu.forcePrivilegedComponentsForScope(aWindow); |
|
51 } catch (e) {} |
|
52 return Cu.getComponentsForScope(aWindow); |
|
53 } |
|
54 |
|
55 function isWrappable(x) { |
|
56 if (typeof x === "object") |
|
57 return x !== null; |
|
58 return typeof x === "function"; |
|
59 }; |
|
60 |
|
61 function isWrapper(x) { |
|
62 return isWrappable(x) && (typeof x.SpecialPowers_wrappedObject !== "undefined"); |
|
63 }; |
|
64 |
|
65 function unwrapIfWrapped(x) { |
|
66 return isWrapper(x) ? unwrapPrivileged(x) : x; |
|
67 }; |
|
68 |
|
69 function wrapIfUnwrapped(x) { |
|
70 return isWrapper(x) ? x : wrapPrivileged(x); |
|
71 } |
|
72 |
|
73 function isXrayWrapper(x) { |
|
74 return Cu.isXrayWrapper(x); |
|
75 } |
|
76 |
|
77 function callGetOwnPropertyDescriptor(obj, name) { |
|
78 // Quickstubbed getters and setters are propertyOps, and don't get reified |
|
79 // until someone calls __lookupGetter__ or __lookupSetter__ on them (note |
|
80 // that there are special version of those functions for quickstubs, so |
|
81 // apply()ing Object.prototype.__lookupGetter__ isn't good enough). Try to |
|
82 // trigger reification before calling Object.getOwnPropertyDescriptor. |
|
83 // |
|
84 // See bug 764315. |
|
85 try { |
|
86 obj.__lookupGetter__(name); |
|
87 obj.__lookupSetter__(name); |
|
88 } catch(e) { } |
|
89 return Object.getOwnPropertyDescriptor(obj, name); |
|
90 } |
|
91 |
|
92 // We can't call apply() directy on Xray-wrapped functions, so we have to be |
|
93 // clever. |
|
94 function doApply(fun, invocant, args) { |
|
95 return Function.prototype.apply.call(fun, invocant, args); |
|
96 } |
|
97 |
|
98 function wrapPrivileged(obj) { |
|
99 |
|
100 // Primitives pass straight through. |
|
101 if (!isWrappable(obj)) |
|
102 return obj; |
|
103 |
|
104 // No double wrapping. |
|
105 if (isWrapper(obj)) |
|
106 throw "Trying to double-wrap object!"; |
|
107 |
|
108 // Make our core wrapper object. |
|
109 var handler = new SpecialPowersHandler(obj); |
|
110 |
|
111 // If the object is callable, make a function proxy. |
|
112 if (typeof obj === "function") { |
|
113 var callTrap = function() { |
|
114 // The invocant and arguments may or may not be wrappers. Unwrap them if necessary. |
|
115 var invocant = unwrapIfWrapped(this); |
|
116 var unwrappedArgs = Array.prototype.slice.call(arguments).map(unwrapIfWrapped); |
|
117 |
|
118 try { |
|
119 return wrapPrivileged(doApply(obj, invocant, unwrappedArgs)); |
|
120 } catch (e) { |
|
121 // Wrap exceptions and re-throw them. |
|
122 throw wrapIfUnwrapped(e); |
|
123 } |
|
124 }; |
|
125 var constructTrap = function() { |
|
126 // The arguments may or may not be wrappers. Unwrap them if necessary. |
|
127 var unwrappedArgs = Array.prototype.slice.call(arguments).map(unwrapIfWrapped); |
|
128 |
|
129 // We want to invoke "obj" as a constructor, but using unwrappedArgs as |
|
130 // the arguments. Make sure to wrap and re-throw exceptions! |
|
131 try { |
|
132 return wrapPrivileged(new obj(...unwrappedArgs)); |
|
133 } catch (e) { |
|
134 throw wrapIfUnwrapped(e); |
|
135 } |
|
136 }; |
|
137 |
|
138 return Proxy.createFunction(handler, callTrap, constructTrap); |
|
139 } |
|
140 |
|
141 // Otherwise, just make a regular object proxy. |
|
142 return Proxy.create(handler); |
|
143 }; |
|
144 |
|
145 function unwrapPrivileged(x) { |
|
146 |
|
147 // We don't wrap primitives, so sometimes we have a primitive where we'd |
|
148 // expect to have a wrapper. The proxy pretends to be the type that it's |
|
149 // emulating, so we can just as easily check isWrappable() on a proxy as |
|
150 // we can on an unwrapped object. |
|
151 if (!isWrappable(x)) |
|
152 return x; |
|
153 |
|
154 // If we have a wrappable type, make sure it's wrapped. |
|
155 if (!isWrapper(x)) |
|
156 throw "Trying to unwrap a non-wrapped object!"; |
|
157 |
|
158 // Unwrap. |
|
159 return x.SpecialPowers_wrappedObject; |
|
160 }; |
|
161 |
|
162 function crawlProtoChain(obj, fn) { |
|
163 var rv = fn(obj); |
|
164 if (rv !== undefined) |
|
165 return rv; |
|
166 if (Object.getPrototypeOf(obj)) |
|
167 return crawlProtoChain(Object.getPrototypeOf(obj), fn); |
|
168 }; |
|
169 |
|
170 /* |
|
171 * We want to waive the __exposedProps__ security check for SpecialPowers-wrapped |
|
172 * objects. We do this by creating a proxy singleton that just always returns 'rw' |
|
173 * for any property name. |
|
174 */ |
|
175 function ExposedPropsWaiverHandler() { |
|
176 // NB: XPConnect denies access if the relevant member of __exposedProps__ is not |
|
177 // enumerable. |
|
178 var _permit = { value: 'rw', writable: false, configurable: false, enumerable: true }; |
|
179 return { |
|
180 getOwnPropertyDescriptor: function(name) { return _permit; }, |
|
181 getPropertyDescriptor: function(name) { return _permit; }, |
|
182 getOwnPropertyNames: function() { throw Error("Can't enumerate ExposedPropsWaiver"); }, |
|
183 getPropertyNames: function() { throw Error("Can't enumerate ExposedPropsWaiver"); }, |
|
184 enumerate: function() { throw Error("Can't enumerate ExposedPropsWaiver"); }, |
|
185 defineProperty: function(name) { throw Error("Can't define props on ExposedPropsWaiver"); }, |
|
186 delete: function(name) { throw Error("Can't delete props from ExposedPropsWaiver"); } |
|
187 }; |
|
188 }; |
|
189 ExposedPropsWaiver = Proxy.create(ExposedPropsWaiverHandler()); |
|
190 |
|
191 function SpecialPowersHandler(obj) { |
|
192 this.wrappedObject = obj; |
|
193 }; |
|
194 |
|
195 // Allow us to transitively maintain the membrane by wrapping descriptors |
|
196 // we return. |
|
197 SpecialPowersHandler.prototype.doGetPropertyDescriptor = function(name, own) { |
|
198 |
|
199 // Handle our special API. |
|
200 if (name == "SpecialPowers_wrappedObject") |
|
201 return { value: this.wrappedObject, writeable: false, configurable: false, enumerable: false }; |
|
202 |
|
203 // Handle __exposedProps__. |
|
204 if (name == "__exposedProps__") |
|
205 return { value: ExposedPropsWaiver, writable: false, configurable: false, enumerable: false }; |
|
206 |
|
207 // In general, we want Xray wrappers for content DOM objects, because waiving |
|
208 // Xray gives us Xray waiver wrappers that clamp the principal when we cross |
|
209 // compartment boundaries. However, Xray adds some gunk to toString(), which |
|
210 // has the potential to confuse consumers that aren't expecting Xray wrappers. |
|
211 // Since toString() is a non-privileged method that returns only strings, we |
|
212 // can just waive Xray for that case. |
|
213 var obj = name == 'toString' ? XPCNativeWrapper.unwrap(this.wrappedObject) |
|
214 : this.wrappedObject; |
|
215 |
|
216 // |
|
217 // Call through to the wrapped object. |
|
218 // |
|
219 // Note that we have several cases here, each of which requires special handling. |
|
220 // |
|
221 var desc; |
|
222 |
|
223 // Case 1: Own Properties. |
|
224 // |
|
225 // This one is easy, thanks to Object.getOwnPropertyDescriptor(). |
|
226 if (own) |
|
227 desc = callGetOwnPropertyDescriptor(obj, name); |
|
228 |
|
229 // Case 2: Not own, not Xray-wrapped. |
|
230 // |
|
231 // Here, we can just crawl the prototype chain, calling |
|
232 // Object.getOwnPropertyDescriptor until we find what we want. |
|
233 // |
|
234 // NB: Make sure to check this.wrappedObject here, rather than obj, because |
|
235 // we may have waived Xray on obj above. |
|
236 else if (!isXrayWrapper(this.wrappedObject)) |
|
237 desc = crawlProtoChain(obj, function(o) {return callGetOwnPropertyDescriptor(o, name);}); |
|
238 |
|
239 // Case 3: Not own, Xray-wrapped. |
|
240 // |
|
241 // This one is harder, because we Xray wrappers are flattened and don't have |
|
242 // a prototype. Xray wrappers are proxies themselves, so we'd love to just call |
|
243 // through to XrayWrapper<Base>::getPropertyDescriptor(). Unfortunately though, |
|
244 // we don't have any way to do that. :-( |
|
245 // |
|
246 // So we first try with a call to getOwnPropertyDescriptor(). If that fails, |
|
247 // we make up a descriptor, using some assumptions about what kinds of things |
|
248 // tend to live on the prototypes of Xray-wrapped objects. |
|
249 else { |
|
250 desc = Object.getOwnPropertyDescriptor(obj, name); |
|
251 if (!desc) { |
|
252 var getter = Object.prototype.__lookupGetter__.call(obj, name); |
|
253 var setter = Object.prototype.__lookupSetter__.call(obj, name); |
|
254 if (getter || setter) |
|
255 desc = {get: getter, set: setter, configurable: true, enumerable: true}; |
|
256 else if (name in obj) |
|
257 desc = {value: obj[name], writable: false, configurable: true, enumerable: true}; |
|
258 } |
|
259 } |
|
260 |
|
261 // Bail if we've got nothing. |
|
262 if (typeof desc === 'undefined') |
|
263 return undefined; |
|
264 |
|
265 // When accessors are implemented as JSPropertyOps rather than JSNatives (ie, |
|
266 // QuickStubs), the js engine does the wrong thing and treats it as a value |
|
267 // descriptor rather than an accessor descriptor. Jorendorff suggested this |
|
268 // little hack to work around it. See bug 520882. |
|
269 if (desc && 'value' in desc && desc.value === undefined) |
|
270 desc.value = obj[name]; |
|
271 |
|
272 // A trapping proxy's properties must always be configurable, but sometimes |
|
273 // this we get non-configurable properties from Object.getOwnPropertyDescriptor(). |
|
274 // Tell a white lie. |
|
275 desc.configurable = true; |
|
276 |
|
277 // Transitively maintain the wrapper membrane. |
|
278 function wrapIfExists(key) { if (key in desc) desc[key] = wrapPrivileged(desc[key]); }; |
|
279 wrapIfExists('value'); |
|
280 wrapIfExists('get'); |
|
281 wrapIfExists('set'); |
|
282 |
|
283 return desc; |
|
284 }; |
|
285 |
|
286 SpecialPowersHandler.prototype.getOwnPropertyDescriptor = function(name) { |
|
287 return this.doGetPropertyDescriptor(name, true); |
|
288 }; |
|
289 |
|
290 SpecialPowersHandler.prototype.getPropertyDescriptor = function(name) { |
|
291 return this.doGetPropertyDescriptor(name, false); |
|
292 }; |
|
293 |
|
294 function doGetOwnPropertyNames(obj, props) { |
|
295 |
|
296 // Insert our special API. It's not enumerable, but getPropertyNames() |
|
297 // includes non-enumerable properties. |
|
298 var specialAPI = 'SpecialPowers_wrappedObject'; |
|
299 if (props.indexOf(specialAPI) == -1) |
|
300 props.push(specialAPI); |
|
301 |
|
302 // Do the normal thing. |
|
303 var flt = function(a) { return props.indexOf(a) == -1; }; |
|
304 props = props.concat(Object.getOwnPropertyNames(obj).filter(flt)); |
|
305 |
|
306 // If we've got an Xray wrapper, include the expandos as well. |
|
307 if ('wrappedJSObject' in obj) |
|
308 props = props.concat(Object.getOwnPropertyNames(obj.wrappedJSObject) |
|
309 .filter(flt)); |
|
310 |
|
311 return props; |
|
312 } |
|
313 |
|
314 SpecialPowersHandler.prototype.getOwnPropertyNames = function() { |
|
315 return doGetOwnPropertyNames(this.wrappedObject, []); |
|
316 }; |
|
317 |
|
318 SpecialPowersHandler.prototype.getPropertyNames = function() { |
|
319 |
|
320 // Manually walk the prototype chain, making sure to add only property names |
|
321 // that haven't been overridden. |
|
322 // |
|
323 // There's some trickiness here with Xray wrappers. Xray wrappers don't have |
|
324 // a prototype, so we need to unwrap them if we want to get all of the names |
|
325 // with Object.getOwnPropertyNames(). But we don't really want to unwrap the |
|
326 // base object, because that will include expandos that are inaccessible via |
|
327 // our implementation of get{,Own}PropertyDescriptor(). So we unwrap just |
|
328 // before accessing the prototype. This ensures that we get Xray vision on |
|
329 // the base object, and no Xray vision for the rest of the way up. |
|
330 var obj = this.wrappedObject; |
|
331 var props = []; |
|
332 while (obj) { |
|
333 props = doGetOwnPropertyNames(obj, props); |
|
334 obj = Object.getPrototypeOf(XPCNativeWrapper.unwrap(obj)); |
|
335 } |
|
336 return props; |
|
337 }; |
|
338 |
|
339 SpecialPowersHandler.prototype.defineProperty = function(name, desc) { |
|
340 return Object.defineProperty(this.wrappedObject, name, desc); |
|
341 }; |
|
342 |
|
343 SpecialPowersHandler.prototype.delete = function(name) { |
|
344 return delete this.wrappedObject[name]; |
|
345 }; |
|
346 |
|
347 SpecialPowersHandler.prototype.fix = function() { return undefined; /* Throws a TypeError. */ }; |
|
348 |
|
349 // Per the ES5 spec this is a derived trap, but it's fundamental in spidermonkey |
|
350 // for some reason. See bug 665198. |
|
351 SpecialPowersHandler.prototype.enumerate = function() { |
|
352 var t = this; |
|
353 var filt = function(name) { return t.getPropertyDescriptor(name).enumerable; }; |
|
354 return this.getPropertyNames().filter(filt); |
|
355 }; |
|
356 |
|
357 // SPConsoleListener reflects nsIConsoleMessage objects into JS in a |
|
358 // tidy, XPCOM-hiding way. Messages that are nsIScriptError objects |
|
359 // have their properties exposed in detail. It also auto-unregisters |
|
360 // itself when it receives a "sentinel" message. |
|
361 function SPConsoleListener(callback) { |
|
362 this.callback = callback; |
|
363 } |
|
364 |
|
365 SPConsoleListener.prototype = { |
|
366 observe: function(msg) { |
|
367 let m = { message: msg.message, |
|
368 errorMessage: null, |
|
369 sourceName: null, |
|
370 sourceLine: null, |
|
371 lineNumber: null, |
|
372 columnNumber: null, |
|
373 category: null, |
|
374 windowID: null, |
|
375 isScriptError: false, |
|
376 isWarning: false, |
|
377 isException: false, |
|
378 isStrict: false }; |
|
379 if (msg instanceof Ci.nsIScriptError) { |
|
380 m.errorMessage = msg.errorMessage; |
|
381 m.sourceName = msg.sourceName; |
|
382 m.sourceLine = msg.sourceLine; |
|
383 m.lineNumber = msg.lineNumber; |
|
384 m.columnNumber = msg.columnNumber; |
|
385 m.category = msg.category; |
|
386 m.windowID = msg.outerWindowID; |
|
387 m.isScriptError = true; |
|
388 m.isWarning = ((msg.flags & Ci.nsIScriptError.warningFlag) === 1); |
|
389 m.isException = ((msg.flags & Ci.nsIScriptError.exceptionFlag) === 1); |
|
390 m.isStrict = ((msg.flags & Ci.nsIScriptError.strictFlag) === 1); |
|
391 } |
|
392 |
|
393 // expose all props of 'm' as read-only |
|
394 let expose = {}; |
|
395 for (let prop in m) |
|
396 expose[prop] = 'r'; |
|
397 m.__exposedProps__ = expose; |
|
398 Object.freeze(m); |
|
399 |
|
400 this.callback.call(undefined, m); |
|
401 |
|
402 if (!m.isScriptError && m.message === "SENTINEL") |
|
403 Services.console.unregisterListener(this); |
|
404 }, |
|
405 |
|
406 QueryInterface: XPCOMUtils.generateQI([Ci.nsIConsoleListener]) |
|
407 }; |
|
408 |
|
409 function wrapCallback(cb) { |
|
410 return function SpecialPowersCallbackWrapper() { |
|
411 args = Array.prototype.map.call(arguments, wrapIfUnwrapped); |
|
412 return cb.apply(this, args); |
|
413 } |
|
414 } |
|
415 |
|
416 function wrapCallbackObject(obj) { |
|
417 wrapper = { __exposedProps__: ExposedPropsWaiver }; |
|
418 for (var i in obj) { |
|
419 if (typeof obj[i] == 'function') |
|
420 wrapper[i] = wrapCallback(obj[i]); |
|
421 else |
|
422 wrapper[i] = obj[i]; |
|
423 } |
|
424 return wrapper; |
|
425 } |
|
426 |
|
427 SpecialPowersAPI.prototype = { |
|
428 |
|
429 /* |
|
430 * Privileged object wrapping API |
|
431 * |
|
432 * Usage: |
|
433 * var wrapper = SpecialPowers.wrap(obj); |
|
434 * wrapper.privilegedMethod(); wrapper.privilegedProperty; |
|
435 * obj === SpecialPowers.unwrap(wrapper); |
|
436 * |
|
437 * These functions provide transparent access to privileged objects using |
|
438 * various pieces of deep SpiderMagic. Conceptually, a wrapper is just an |
|
439 * object containing a reference to the underlying object, where all method |
|
440 * calls and property accesses are transparently performed with the System |
|
441 * Principal. Moreover, objects obtained from the wrapper (including properties |
|
442 * and method return values) are wrapped automatically. Thus, after a single |
|
443 * call to SpecialPowers.wrap(), the wrapper layer is transitively maintained. |
|
444 * |
|
445 * Known Issues: |
|
446 * |
|
447 * - The wrapping function does not preserve identity, so |
|
448 * SpecialPowers.wrap(foo) !== SpecialPowers.wrap(foo). See bug 718543. |
|
449 * |
|
450 * - The wrapper cannot see expando properties on unprivileged DOM objects. |
|
451 * That is to say, the wrapper uses Xray delegation. |
|
452 * |
|
453 * - The wrapper sometimes guesses certain ES5 attributes for returned |
|
454 * properties. This is explained in a comment in the wrapper code above, |
|
455 * and shouldn't be a problem. |
|
456 */ |
|
457 wrap: wrapIfUnwrapped, |
|
458 unwrap: unwrapIfWrapped, |
|
459 isWrapper: isWrapper, |
|
460 |
|
461 /* |
|
462 * When content needs to pass a callback or a callback object to an API |
|
463 * accessed over SpecialPowers, that API may sometimes receive arguments for |
|
464 * whom it is forbidden to create a wrapper in content scopes. As such, we |
|
465 * need a layer to wrap the values in SpecialPowers wrappers before they ever |
|
466 * reach content. |
|
467 */ |
|
468 wrapCallback: wrapCallback, |
|
469 wrapCallbackObject: wrapCallbackObject, |
|
470 |
|
471 /* |
|
472 * Create blank privileged objects to use as out-params for privileged functions. |
|
473 */ |
|
474 createBlankObject: function () { |
|
475 var obj = new Object; |
|
476 obj.__exposedProps__ = ExposedPropsWaiver; |
|
477 return obj; |
|
478 }, |
|
479 |
|
480 /* |
|
481 * Because SpecialPowers wrappers don't preserve identity, comparing with == |
|
482 * can be hazardous. Sometimes we can just unwrap to compare, but sometimes |
|
483 * wrapping the underlying object into a content scope is forbidden. This |
|
484 * function strips any wrappers if they exist and compare the underlying |
|
485 * values. |
|
486 */ |
|
487 compare: function(a, b) { |
|
488 return unwrapIfWrapped(a) === unwrapIfWrapped(b); |
|
489 }, |
|
490 |
|
491 get MockFilePicker() { |
|
492 return MockFilePicker |
|
493 }, |
|
494 |
|
495 get MockColorPicker() { |
|
496 return MockColorPicker |
|
497 }, |
|
498 |
|
499 get MockPermissionPrompt() { |
|
500 return MockPermissionPrompt |
|
501 }, |
|
502 |
|
503 loadChromeScript: function (url) { |
|
504 // Create a unique id for this chrome script |
|
505 let uuidGenerator = Cc["@mozilla.org/uuid-generator;1"] |
|
506 .getService(Ci.nsIUUIDGenerator); |
|
507 let id = uuidGenerator.generateUUID().toString(); |
|
508 |
|
509 // Tells chrome code to evaluate this chrome script |
|
510 this._sendSyncMessage("SPLoadChromeScript", |
|
511 { url: url, id: id }); |
|
512 |
|
513 // Returns a MessageManager like API in order to be |
|
514 // able to communicate with this chrome script |
|
515 let listeners = []; |
|
516 let chromeScript = { |
|
517 addMessageListener: (name, listener) => { |
|
518 listeners.push({ name: name, listener: listener }); |
|
519 }, |
|
520 |
|
521 removeMessageListener: (name, listener) => { |
|
522 listeners = listeners.filter( |
|
523 o => (o.name != name || o.listener != listener) |
|
524 ); |
|
525 }, |
|
526 |
|
527 sendAsyncMessage: (name, message) => { |
|
528 this._sendSyncMessage("SPChromeScriptMessage", |
|
529 { id: id, name: name, message: message }); |
|
530 }, |
|
531 |
|
532 destroy: () => { |
|
533 listeners = []; |
|
534 this._removeMessageListener("SPChromeScriptMessage", chromeScript); |
|
535 this._removeMessageListener("SPChromeScriptAssert", chromeScript); |
|
536 }, |
|
537 |
|
538 receiveMessage: (aMessage) => { |
|
539 let messageId = aMessage.json.id; |
|
540 let name = aMessage.json.name; |
|
541 let message = aMessage.json.message; |
|
542 // Ignore message from other chrome script |
|
543 if (messageId != id) |
|
544 return; |
|
545 |
|
546 if (aMessage.name == "SPChromeScriptMessage") { |
|
547 listeners.filter(o => (o.name == name)) |
|
548 .forEach(o => o.listener(this.wrap(message))); |
|
549 } else if (aMessage.name == "SPChromeScriptAssert") { |
|
550 assert(aMessage.json); |
|
551 } |
|
552 } |
|
553 }; |
|
554 this._addMessageListener("SPChromeScriptMessage", chromeScript); |
|
555 this._addMessageListener("SPChromeScriptAssert", chromeScript); |
|
556 |
|
557 let assert = json => { |
|
558 // An assertion has been done in a mochitest chrome script |
|
559 let {url, err, message, stack} = json; |
|
560 |
|
561 // Try to fetch a test runner from the mochitest |
|
562 // in order to properly log these assertions and notify |
|
563 // all usefull log observers |
|
564 let window = this.window.get(); |
|
565 let parentRunner, repr = function (o) o; |
|
566 if (window) { |
|
567 window = window.wrappedJSObject; |
|
568 parentRunner = window.TestRunner; |
|
569 if (window.repr) { |
|
570 repr = window.repr; |
|
571 } |
|
572 } |
|
573 |
|
574 // Craft a mochitest-like report string |
|
575 var resultString = err ? "TEST-UNEXPECTED-FAIL" : "TEST-PASS"; |
|
576 var diagnostic = |
|
577 message ? message : |
|
578 ("assertion @ " + stack.filename + ":" + stack.lineNumber); |
|
579 if (err) { |
|
580 diagnostic += |
|
581 " - got " + repr(err.actual) + |
|
582 ", expected " + repr(err.expected) + |
|
583 " (operator " + err.operator + ")"; |
|
584 } |
|
585 var msg = [resultString, url, diagnostic].join(" | "); |
|
586 if (parentRunner) { |
|
587 if (err) { |
|
588 parentRunner.addFailedTest(url); |
|
589 parentRunner.error(msg); |
|
590 } else { |
|
591 parentRunner.log(msg); |
|
592 } |
|
593 } else { |
|
594 // When we are running only a single mochitest, there is no test runner |
|
595 dump(msg + "\n"); |
|
596 } |
|
597 }; |
|
598 |
|
599 return this.wrap(chromeScript); |
|
600 }, |
|
601 |
|
602 get Services() { |
|
603 return wrapPrivileged(Services); |
|
604 }, |
|
605 |
|
606 /* |
|
607 * In general, any Components object created for unprivileged scopes is |
|
608 * neutered (it implements nsIXPCComponentsBase, but not nsIXPCComponents). |
|
609 * We override this in certain legacy automation configurations (see the |
|
610 * implementation of getRawComponents() above), but don't want to support |
|
611 * it in cases where it isn't already required. |
|
612 * |
|
613 * In scopes with neutered Components, we don't have a natural referent for |
|
614 * things like SpecialPowers.Cc. So in those cases, we fall back to the |
|
615 * Components object from the SpecialPowers scope. This doesn't quite behave |
|
616 * the same way (in particular, SpecialPowers.Cc[foo].createInstance() will |
|
617 * create an instance in the SpecialPowers scope), but SpecialPowers wrapping |
|
618 * is already a YMMV / Whatever-It-Takes-To-Get-TBPL-Green sort of thing. |
|
619 * |
|
620 * It probably wouldn't be too much work to just make SpecialPowers.Components |
|
621 * unconditionally point to the Components object in the SpecialPowers scope. |
|
622 * Try will tell what needs to be fixed up. |
|
623 */ |
|
624 getFullComponents: function() { |
|
625 return typeof this.Components.classes == 'object' ? this.Components |
|
626 : Components; |
|
627 }, |
|
628 |
|
629 /* |
|
630 * Convenient shortcuts to the standard Components abbreviations. Note that |
|
631 * we don't SpecialPowers-wrap Components.interfaces, because it's available |
|
632 * to untrusted content, and wrapping it confuses QI and identity checks. |
|
633 */ |
|
634 get Cc() { return wrapPrivileged(this.getFullComponents()).classes; }, |
|
635 get Ci() { return this.Components.interfaces; }, |
|
636 get Cu() { return wrapPrivileged(this.getFullComponents()).utils; }, |
|
637 get Cr() { return wrapPrivileged(this.Components).results; }, |
|
638 |
|
639 /* |
|
640 * SpecialPowers.getRawComponents() allows content to get a reference to a |
|
641 * naked (and, in certain automation configurations, privileged) Components |
|
642 * object for its scope. |
|
643 * |
|
644 * SpecialPowers.getRawComponents(window) is defined as the global property |
|
645 * window.SpecialPowers.Components for convenience. |
|
646 */ |
|
647 getRawComponents: getRawComponents, |
|
648 |
|
649 getDOMWindowUtils: function(aWindow) { |
|
650 if (aWindow == this.window.get() && this.DOMWindowUtils != null) |
|
651 return this.DOMWindowUtils; |
|
652 |
|
653 return bindDOMWindowUtils(aWindow); |
|
654 }, |
|
655 |
|
656 removeExpectedCrashDumpFiles: function(aExpectingProcessCrash) { |
|
657 var success = true; |
|
658 if (aExpectingProcessCrash) { |
|
659 var message = { |
|
660 op: "delete-crash-dump-files", |
|
661 filenames: this._encounteredCrashDumpFiles |
|
662 }; |
|
663 if (!this._sendSyncMessage("SPProcessCrashService", message)[0]) { |
|
664 success = false; |
|
665 } |
|
666 } |
|
667 this._encounteredCrashDumpFiles.length = 0; |
|
668 return success; |
|
669 }, |
|
670 |
|
671 findUnexpectedCrashDumpFiles: function() { |
|
672 var self = this; |
|
673 var message = { |
|
674 op: "find-crash-dump-files", |
|
675 crashDumpFilesToIgnore: this._unexpectedCrashDumpFiles |
|
676 }; |
|
677 var crashDumpFiles = this._sendSyncMessage("SPProcessCrashService", message)[0]; |
|
678 crashDumpFiles.forEach(function(aFilename) { |
|
679 self._unexpectedCrashDumpFiles[aFilename] = true; |
|
680 }); |
|
681 return crashDumpFiles; |
|
682 }, |
|
683 |
|
684 _delayCallbackTwice: function(callback) { |
|
685 function delayedCallback() { |
|
686 function delayAgain() { |
|
687 content.window.setTimeout(callback, 0); |
|
688 } |
|
689 content.window.setTimeout(delayAgain, 0); |
|
690 } |
|
691 return delayedCallback; |
|
692 }, |
|
693 |
|
694 /* apply permissions to the system and when the test case is finished (SimpleTest.finish()) |
|
695 we will revert the permission back to the original. |
|
696 |
|
697 inPermissions is an array of objects where each object has a type, action, context, ex: |
|
698 [{'type': 'SystemXHR', 'allow': 1, 'context': document}, |
|
699 {'type': 'SystemXHR', 'allow': Ci.nsIPermissionManager.PROMPT_ACTION, 'context': document}] |
|
700 |
|
701 Allow can be a boolean value of true/false or ALLOW_ACTION/DENY_ACTION/PROMPT_ACTION/UNKNOWN_ACTION |
|
702 */ |
|
703 pushPermissions: function(inPermissions, callback) { |
|
704 var pendingPermissions = []; |
|
705 var cleanupPermissions = []; |
|
706 |
|
707 for (var p in inPermissions) { |
|
708 var permission = inPermissions[p]; |
|
709 var originalValue = Ci.nsIPermissionManager.UNKNOWN_ACTION; |
|
710 if (this.testPermission(permission.type, Ci.nsIPermissionManager.ALLOW_ACTION, permission.context)) { |
|
711 originalValue = Ci.nsIPermissionManager.ALLOW_ACTION; |
|
712 } else if (this.testPermission(permission.type, Ci.nsIPermissionManager.DENY_ACTION, permission.context)) { |
|
713 originalValue = Ci.nsIPermissionManager.DENY_ACTION; |
|
714 } else if (this.testPermission(permission.type, Ci.nsIPermissionManager.PROMPT_ACTION, permission.context)) { |
|
715 originalValue = Ci.nsIPermissionManager.PROMPT_ACTION; |
|
716 } else if (this.testPermission(permission.type, Ci.nsICookiePermission.ACCESS_SESSION, permission.context)) { |
|
717 originalValue = Ci.nsICookiePermission.ACCESS_SESSION; |
|
718 } else if (this.testPermission(permission.type, Ci.nsICookiePermission.ACCESS_ALLOW_FIRST_PARTY_ONLY, permission.context)) { |
|
719 originalValue = Ci.nsICookiePermission.ACCESS_ALLOW_FIRST_PARTY_ONLY; |
|
720 } else if (this.testPermission(permission.type, Ci.nsICookiePermission.ACCESS_LIMIT_THIRD_PARTY, permission.context)) { |
|
721 originalValue = Ci.nsICookiePermission.ACCESS_LIMIT_THIRD_PARTY; |
|
722 } |
|
723 |
|
724 let [url, appId, isInBrowserElement] = this._getInfoFromPermissionArg(permission.context); |
|
725 |
|
726 let perm; |
|
727 if (typeof permission.allow !== 'boolean') { |
|
728 perm = permission.allow; |
|
729 } else { |
|
730 perm = permission.allow ? Ci.nsIPermissionManager.ALLOW_ACTION |
|
731 : Ci.nsIPermissionManager.DENY_ACTION; |
|
732 } |
|
733 |
|
734 if (permission.remove == true) |
|
735 perm = Ci.nsIPermissionManager.UNKNOWN_ACTION; |
|
736 |
|
737 if (originalValue == perm) { |
|
738 continue; |
|
739 } |
|
740 |
|
741 var todo = {'op': 'add', 'type': permission.type, 'permission': perm, 'value': perm, 'url': url, 'appId': appId, 'isInBrowserElement': isInBrowserElement}; |
|
742 if (permission.remove == true) |
|
743 todo.op = 'remove'; |
|
744 |
|
745 pendingPermissions.push(todo); |
|
746 |
|
747 /* Push original permissions value or clear into cleanup array */ |
|
748 var cleanupTodo = {'op': 'add', 'type': permission.type, 'permission': perm, 'value': perm, 'url': url, 'appId': appId, 'isInBrowserElement': isInBrowserElement}; |
|
749 if (originalValue == Ci.nsIPermissionManager.UNKNOWN_ACTION) { |
|
750 cleanupTodo.op = 'remove'; |
|
751 } else { |
|
752 cleanupTodo.value = originalValue; |
|
753 cleanupTodo.permission = originalValue; |
|
754 } |
|
755 cleanupPermissions.push(cleanupTodo); |
|
756 } |
|
757 |
|
758 if (pendingPermissions.length > 0) { |
|
759 // The callback needs to be delayed twice. One delay is because the pref |
|
760 // service doesn't guarantee the order it calls its observers in, so it |
|
761 // may notify the observer holding the callback before the other |
|
762 // observers have been notified and given a chance to make the changes |
|
763 // that the callback checks for. The second delay is because pref |
|
764 // observers often defer making their changes by posting an event to the |
|
765 // event loop. |
|
766 this._permissionsUndoStack.push(cleanupPermissions); |
|
767 this._pendingPermissions.push([pendingPermissions, |
|
768 this._delayCallbackTwice(callback)]); |
|
769 this._applyPermissions(); |
|
770 } else { |
|
771 content.window.setTimeout(callback, 0); |
|
772 } |
|
773 }, |
|
774 |
|
775 popPermissions: function(callback) { |
|
776 if (this._permissionsUndoStack.length > 0) { |
|
777 // See pushPermissions comment regarding delay. |
|
778 let cb = callback ? this._delayCallbackTwice(callback) : null; |
|
779 /* Each pop from the stack will yield an object {op/type/permission/value/url/appid/isInBrowserElement} or null */ |
|
780 this._pendingPermissions.push([this._permissionsUndoStack.pop(), cb]); |
|
781 this._applyPermissions(); |
|
782 } else { |
|
783 content.window.setTimeout(callback, 0); |
|
784 } |
|
785 }, |
|
786 |
|
787 flushPermissions: function(callback) { |
|
788 while (this._permissionsUndoStack.length > 1) |
|
789 this.popPermissions(null); |
|
790 |
|
791 this.popPermissions(callback); |
|
792 }, |
|
793 |
|
794 |
|
795 _permissionObserver: { |
|
796 _lastPermission: {}, |
|
797 _callBack: null, |
|
798 _nextCallback: null, |
|
799 |
|
800 observe: function (aSubject, aTopic, aData) |
|
801 { |
|
802 if (aTopic == "perm-changed") { |
|
803 var permission = aSubject.QueryInterface(Ci.nsIPermission); |
|
804 if (permission.type == this._lastPermission.type) { |
|
805 var os = Components.classes["@mozilla.org/observer-service;1"] |
|
806 .getService(Components.interfaces.nsIObserverService); |
|
807 os.removeObserver(this, "perm-changed"); |
|
808 content.window.setTimeout(this._callback, 0); |
|
809 content.window.setTimeout(this._nextCallback, 0); |
|
810 } |
|
811 } |
|
812 } |
|
813 }, |
|
814 |
|
815 /* |
|
816 Iterate through one atomic set of permissions actions and perform allow/deny as appropriate. |
|
817 All actions performed must modify the relevant permission. |
|
818 */ |
|
819 _applyPermissions: function() { |
|
820 if (this._applyingPermissions || this._pendingPermissions.length <= 0) { |
|
821 return; |
|
822 } |
|
823 |
|
824 /* Set lock and get prefs from the _pendingPrefs queue */ |
|
825 this._applyingPermissions = true; |
|
826 var transaction = this._pendingPermissions.shift(); |
|
827 var pendingActions = transaction[0]; |
|
828 var callback = transaction[1]; |
|
829 var lastPermission = pendingActions[pendingActions.length-1]; |
|
830 |
|
831 var self = this; |
|
832 var os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService); |
|
833 this._permissionObserver._lastPermission = lastPermission; |
|
834 this._permissionObserver._callback = callback; |
|
835 this._permissionObserver._nextCallback = function () { |
|
836 self._applyingPermissions = false; |
|
837 // Now apply any permissions that may have been queued while we were applying |
|
838 self._applyPermissions(); |
|
839 } |
|
840 |
|
841 os.addObserver(this._permissionObserver, "perm-changed", false); |
|
842 |
|
843 for (var idx in pendingActions) { |
|
844 var perm = pendingActions[idx]; |
|
845 this._sendSyncMessage('SPPermissionManager', perm)[0]; |
|
846 } |
|
847 }, |
|
848 |
|
849 /* |
|
850 * Take in a list of pref changes to make, and invoke |callback| once those |
|
851 * changes have taken effect. When the test finishes, these changes are |
|
852 * reverted. |
|
853 * |
|
854 * |inPrefs| must be an object with up to two properties: "set" and "clear". |
|
855 * pushPrefEnv will set prefs as indicated in |inPrefs.set| and will unset |
|
856 * the prefs indicated in |inPrefs.clear|. |
|
857 * |
|
858 * For example, you might pass |inPrefs| as: |
|
859 * |
|
860 * inPrefs = {'set': [['foo.bar', 2], ['magic.pref', 'baz']], |
|
861 * 'clear': [['clear.this'], ['also.this']] }; |
|
862 * |
|
863 * Notice that |set| and |clear| are both an array of arrays. In |set|, each |
|
864 * of the inner arrays must have the form [pref_name, value] or [pref_name, |
|
865 * value, iid]. (The latter form is used for prefs with "complex" values.) |
|
866 * |
|
867 * In |clear|, each inner array should have the form [pref_name]. |
|
868 * |
|
869 * If you set the same pref more than once (or both set and clear a pref), |
|
870 * the behavior of this method is undefined. |
|
871 * |
|
872 * (Implementation note: _prefEnvUndoStack is a stack of values to revert to, |
|
873 * not values which have been set!) |
|
874 * |
|
875 * TODO: complex values for original cleanup? |
|
876 * |
|
877 */ |
|
878 pushPrefEnv: function(inPrefs, callback) { |
|
879 var prefs = Components.classes["@mozilla.org/preferences-service;1"]. |
|
880 getService(Components.interfaces.nsIPrefBranch); |
|
881 |
|
882 var pref_string = []; |
|
883 pref_string[prefs.PREF_INT] = "INT"; |
|
884 pref_string[prefs.PREF_BOOL] = "BOOL"; |
|
885 pref_string[prefs.PREF_STRING] = "CHAR"; |
|
886 |
|
887 var pendingActions = []; |
|
888 var cleanupActions = []; |
|
889 |
|
890 for (var action in inPrefs) { /* set|clear */ |
|
891 for (var idx in inPrefs[action]) { |
|
892 var aPref = inPrefs[action][idx]; |
|
893 var prefName = aPref[0]; |
|
894 var prefValue = null; |
|
895 var prefIid = null; |
|
896 var prefType = prefs.PREF_INVALID; |
|
897 var originalValue = null; |
|
898 |
|
899 if (aPref.length == 3) { |
|
900 prefValue = aPref[1]; |
|
901 prefIid = aPref[2]; |
|
902 } else if (aPref.length == 2) { |
|
903 prefValue = aPref[1]; |
|
904 } |
|
905 |
|
906 /* If pref is not found or invalid it doesn't exist. */ |
|
907 if (prefs.getPrefType(prefName) != prefs.PREF_INVALID) { |
|
908 prefType = pref_string[prefs.getPrefType(prefName)]; |
|
909 if ((prefs.prefHasUserValue(prefName) && action == 'clear') || |
|
910 (action == 'set')) |
|
911 originalValue = this._getPref(prefName, prefType); |
|
912 } else if (action == 'set') { |
|
913 /* prefName doesn't exist, so 'clear' is pointless */ |
|
914 if (aPref.length == 3) { |
|
915 prefType = "COMPLEX"; |
|
916 } else if (aPref.length == 2) { |
|
917 if (typeof(prefValue) == "boolean") |
|
918 prefType = "BOOL"; |
|
919 else if (typeof(prefValue) == "number") |
|
920 prefType = "INT"; |
|
921 else if (typeof(prefValue) == "string") |
|
922 prefType = "CHAR"; |
|
923 } |
|
924 } |
|
925 |
|
926 /* PREF_INVALID: A non existing pref which we are clearing or invalid values for a set */ |
|
927 if (prefType == prefs.PREF_INVALID) |
|
928 continue; |
|
929 |
|
930 /* We are not going to set a pref if the value is the same */ |
|
931 if (originalValue == prefValue) |
|
932 continue; |
|
933 |
|
934 pendingActions.push({'action': action, 'type': prefType, 'name': prefName, 'value': prefValue, 'Iid': prefIid}); |
|
935 |
|
936 /* Push original preference value or clear into cleanup array */ |
|
937 var cleanupTodo = {'action': action, 'type': prefType, 'name': prefName, 'value': originalValue, 'Iid': prefIid}; |
|
938 if (originalValue == null) { |
|
939 cleanupTodo.action = 'clear'; |
|
940 } else { |
|
941 cleanupTodo.action = 'set'; |
|
942 } |
|
943 cleanupActions.push(cleanupTodo); |
|
944 } |
|
945 } |
|
946 |
|
947 if (pendingActions.length > 0) { |
|
948 // The callback needs to be delayed twice. One delay is because the pref |
|
949 // service doesn't guarantee the order it calls its observers in, so it |
|
950 // may notify the observer holding the callback before the other |
|
951 // observers have been notified and given a chance to make the changes |
|
952 // that the callback checks for. The second delay is because pref |
|
953 // observers often defer making their changes by posting an event to the |
|
954 // event loop. |
|
955 this._prefEnvUndoStack.push(cleanupActions); |
|
956 this._pendingPrefs.push([pendingActions, |
|
957 this._delayCallbackTwice(callback)]); |
|
958 this._applyPrefs(); |
|
959 } else { |
|
960 content.window.setTimeout(callback, 0); |
|
961 } |
|
962 }, |
|
963 |
|
964 popPrefEnv: function(callback) { |
|
965 if (this._prefEnvUndoStack.length > 0) { |
|
966 // See pushPrefEnv comment regarding delay. |
|
967 let cb = callback ? this._delayCallbackTwice(callback) : null; |
|
968 /* Each pop will have a valid block of preferences */ |
|
969 this._pendingPrefs.push([this._prefEnvUndoStack.pop(), cb]); |
|
970 this._applyPrefs(); |
|
971 } else { |
|
972 content.window.setTimeout(callback, 0); |
|
973 } |
|
974 }, |
|
975 |
|
976 flushPrefEnv: function(callback) { |
|
977 while (this._prefEnvUndoStack.length > 1) |
|
978 this.popPrefEnv(null); |
|
979 |
|
980 this.popPrefEnv(callback); |
|
981 }, |
|
982 |
|
983 /* |
|
984 Iterate through one atomic set of pref actions and perform sets/clears as appropriate. |
|
985 All actions performed must modify the relevant pref. |
|
986 */ |
|
987 _applyPrefs: function() { |
|
988 if (this._applyingPrefs || this._pendingPrefs.length <= 0) { |
|
989 return; |
|
990 } |
|
991 |
|
992 /* Set lock and get prefs from the _pendingPrefs queue */ |
|
993 this._applyingPrefs = true; |
|
994 var transaction = this._pendingPrefs.shift(); |
|
995 var pendingActions = transaction[0]; |
|
996 var callback = transaction[1]; |
|
997 |
|
998 var lastPref = pendingActions[pendingActions.length-1]; |
|
999 |
|
1000 var pb = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch); |
|
1001 var self = this; |
|
1002 pb.addObserver(lastPref.name, function prefObs(subject, topic, data) { |
|
1003 pb.removeObserver(lastPref.name, prefObs); |
|
1004 |
|
1005 content.window.setTimeout(callback, 0); |
|
1006 content.window.setTimeout(function () { |
|
1007 self._applyingPrefs = false; |
|
1008 // Now apply any prefs that may have been queued while we were applying |
|
1009 self._applyPrefs(); |
|
1010 }, 0); |
|
1011 }, false); |
|
1012 |
|
1013 for (var idx in pendingActions) { |
|
1014 var pref = pendingActions[idx]; |
|
1015 if (pref.action == 'set') { |
|
1016 this._setPref(pref.name, pref.type, pref.value, pref.Iid); |
|
1017 } else if (pref.action == 'clear') { |
|
1018 this.clearUserPref(pref.name); |
|
1019 } |
|
1020 } |
|
1021 }, |
|
1022 |
|
1023 // Disables the app install prompt for the duration of this test. There is |
|
1024 // no need to re-enable the prompt at the end of the test. |
|
1025 // |
|
1026 // The provided callback is invoked once the prompt is disabled. |
|
1027 autoConfirmAppInstall: function(cb) { |
|
1028 this.pushPrefEnv({set: [['dom.mozApps.auto_confirm_install', true]]}, cb); |
|
1029 }, |
|
1030 |
|
1031 // Allow tests to disable the per platform app validity checks so we can |
|
1032 // test higher level WebApp functionality without full platform support. |
|
1033 setAllAppsLaunchable: function(launchable) { |
|
1034 this._sendSyncMessage("SPWebAppService", { |
|
1035 op: "set-launchable", |
|
1036 launchable: launchable |
|
1037 }); |
|
1038 }, |
|
1039 |
|
1040 // Restore the launchable property to its default value. |
|
1041 flushAllAppsLaunchable: function() { |
|
1042 this._sendSyncMessage("SPWebAppService", { |
|
1043 op: "set-launchable", |
|
1044 launchable: false |
|
1045 }); |
|
1046 }, |
|
1047 |
|
1048 _proxiedObservers: { |
|
1049 "specialpowers-http-notify-request": function(aMessage) { |
|
1050 let uri = aMessage.json.uri; |
|
1051 Services.obs.notifyObservers(null, "specialpowers-http-notify-request", uri); |
|
1052 }, |
|
1053 }, |
|
1054 |
|
1055 _addObserverProxy: function(notification) { |
|
1056 if (notification in this._proxiedObservers) { |
|
1057 this._addMessageListener(notification, this._proxiedObservers[notification]); |
|
1058 } |
|
1059 }, |
|
1060 |
|
1061 _removeObserverProxy: function(notification) { |
|
1062 if (notification in this._proxiedObservers) { |
|
1063 this._removeMessageListener(notification, this._proxiedObservers[notification]); |
|
1064 } |
|
1065 }, |
|
1066 |
|
1067 addObserver: function(obs, notification, weak) { |
|
1068 this._addObserverProxy(notification); |
|
1069 if (typeof obs == 'object' && obs.observe.name != 'SpecialPowersCallbackWrapper') |
|
1070 obs.observe = wrapCallback(obs.observe); |
|
1071 var obsvc = Cc['@mozilla.org/observer-service;1'] |
|
1072 .getService(Ci.nsIObserverService); |
|
1073 obsvc.addObserver(obs, notification, weak); |
|
1074 }, |
|
1075 removeObserver: function(obs, notification) { |
|
1076 this._removeObserverProxy(notification); |
|
1077 var obsvc = Cc['@mozilla.org/observer-service;1'] |
|
1078 .getService(Ci.nsIObserverService); |
|
1079 obsvc.removeObserver(obs, notification); |
|
1080 }, |
|
1081 notifyObservers: function(subject, topic, data) { |
|
1082 var obsvc = Cc['@mozilla.org/observer-service;1'] |
|
1083 .getService(Ci.nsIObserverService); |
|
1084 obsvc.notifyObservers(subject, topic, data); |
|
1085 }, |
|
1086 |
|
1087 can_QI: function(obj) { |
|
1088 return obj.QueryInterface !== undefined; |
|
1089 }, |
|
1090 do_QueryInterface: function(obj, iface) { |
|
1091 return obj.QueryInterface(Ci[iface]); |
|
1092 }, |
|
1093 |
|
1094 call_Instanceof: function (obj1, obj2) { |
|
1095 obj1=unwrapIfWrapped(obj1); |
|
1096 obj2=unwrapIfWrapped(obj2); |
|
1097 return obj1 instanceof obj2; |
|
1098 }, |
|
1099 |
|
1100 // Returns a privileged getter from an object. GetOwnPropertyDescriptor does |
|
1101 // not work here because xray wrappers don't properly implement it. |
|
1102 // |
|
1103 // This terribleness is used by content/base/test/test_object.html because |
|
1104 // <object> and <embed> tags will spawn plugins if their prototype is touched, |
|
1105 // so we need to get and cache the getter of |hasRunningPlugin| if we want to |
|
1106 // call it without paradoxically spawning the plugin. |
|
1107 do_lookupGetter: function(obj, name) { |
|
1108 return Object.prototype.__lookupGetter__.call(obj, name); |
|
1109 }, |
|
1110 |
|
1111 // Mimic the get*Pref API |
|
1112 getBoolPref: function(aPrefName) { |
|
1113 return (this._getPref(aPrefName, 'BOOL')); |
|
1114 }, |
|
1115 getIntPref: function(aPrefName) { |
|
1116 return (this._getPref(aPrefName, 'INT')); |
|
1117 }, |
|
1118 getCharPref: function(aPrefName) { |
|
1119 return (this._getPref(aPrefName, 'CHAR')); |
|
1120 }, |
|
1121 getComplexValue: function(aPrefName, aIid) { |
|
1122 return (this._getPref(aPrefName, 'COMPLEX', aIid)); |
|
1123 }, |
|
1124 |
|
1125 // Mimic the set*Pref API |
|
1126 setBoolPref: function(aPrefName, aValue) { |
|
1127 return (this._setPref(aPrefName, 'BOOL', aValue)); |
|
1128 }, |
|
1129 setIntPref: function(aPrefName, aValue) { |
|
1130 return (this._setPref(aPrefName, 'INT', aValue)); |
|
1131 }, |
|
1132 setCharPref: function(aPrefName, aValue) { |
|
1133 return (this._setPref(aPrefName, 'CHAR', aValue)); |
|
1134 }, |
|
1135 setComplexValue: function(aPrefName, aIid, aValue) { |
|
1136 return (this._setPref(aPrefName, 'COMPLEX', aValue, aIid)); |
|
1137 }, |
|
1138 |
|
1139 // Mimic the clearUserPref API |
|
1140 clearUserPref: function(aPrefName) { |
|
1141 var msg = {'op':'clear', 'prefName': aPrefName, 'prefType': ""}; |
|
1142 this._sendSyncMessage('SPPrefService', msg); |
|
1143 }, |
|
1144 |
|
1145 // Private pref functions to communicate to chrome |
|
1146 _getPref: function(aPrefName, aPrefType, aIid) { |
|
1147 var msg = {}; |
|
1148 if (aIid) { |
|
1149 // Overloading prefValue to handle complex prefs |
|
1150 msg = {'op':'get', 'prefName': aPrefName, 'prefType':aPrefType, 'prefValue':[aIid]}; |
|
1151 } else { |
|
1152 msg = {'op':'get', 'prefName': aPrefName,'prefType': aPrefType}; |
|
1153 } |
|
1154 var val = this._sendSyncMessage('SPPrefService', msg); |
|
1155 |
|
1156 if (val == null || val[0] == null) |
|
1157 throw "Error getting pref"; |
|
1158 return val[0]; |
|
1159 }, |
|
1160 _setPref: function(aPrefName, aPrefType, aValue, aIid) { |
|
1161 var msg = {}; |
|
1162 if (aIid) { |
|
1163 msg = {'op':'set','prefName':aPrefName, 'prefType': aPrefType, 'prefValue': [aIid,aValue]}; |
|
1164 } else { |
|
1165 msg = {'op':'set', 'prefName': aPrefName, 'prefType': aPrefType, 'prefValue': aValue}; |
|
1166 } |
|
1167 return(this._sendSyncMessage('SPPrefService', msg)[0]); |
|
1168 }, |
|
1169 |
|
1170 _getDocShell: function(window) { |
|
1171 return window.QueryInterface(Ci.nsIInterfaceRequestor) |
|
1172 .getInterface(Ci.nsIWebNavigation) |
|
1173 .QueryInterface(Ci.nsIDocShell); |
|
1174 }, |
|
1175 _getMUDV: function(window) { |
|
1176 return this._getDocShell(window).contentViewer |
|
1177 .QueryInterface(Ci.nsIMarkupDocumentViewer); |
|
1178 }, |
|
1179 //XXX: these APIs really ought to be removed, they're not e10s-safe. |
|
1180 // (also they're pretty Firefox-specific) |
|
1181 _getTopChromeWindow: function(window) { |
|
1182 return window.QueryInterface(Ci.nsIInterfaceRequestor) |
|
1183 .getInterface(Ci.nsIWebNavigation) |
|
1184 .QueryInterface(Ci.nsIDocShellTreeItem) |
|
1185 .rootTreeItem |
|
1186 .QueryInterface(Ci.nsIInterfaceRequestor) |
|
1187 .getInterface(Ci.nsIDOMWindow) |
|
1188 .QueryInterface(Ci.nsIDOMChromeWindow); |
|
1189 }, |
|
1190 _getAutoCompletePopup: function(window) { |
|
1191 return this._getTopChromeWindow(window).document |
|
1192 .getElementById("PopupAutoComplete"); |
|
1193 }, |
|
1194 addAutoCompletePopupEventListener: function(window, eventname, listener) { |
|
1195 this._getAutoCompletePopup(window).addEventListener(eventname, |
|
1196 listener, |
|
1197 false); |
|
1198 }, |
|
1199 removeAutoCompletePopupEventListener: function(window, eventname, listener) { |
|
1200 this._getAutoCompletePopup(window).removeEventListener(eventname, |
|
1201 listener, |
|
1202 false); |
|
1203 }, |
|
1204 get formHistory() { |
|
1205 let tmp = {}; |
|
1206 Cu.import("resource://gre/modules/FormHistory.jsm", tmp); |
|
1207 return wrapPrivileged(tmp.FormHistory); |
|
1208 }, |
|
1209 getFormFillController: function(window) { |
|
1210 return Components.classes["@mozilla.org/satchel/form-fill-controller;1"] |
|
1211 .getService(Components.interfaces.nsIFormFillController); |
|
1212 }, |
|
1213 attachFormFillControllerTo: function(window) { |
|
1214 this.getFormFillController() |
|
1215 .attachToBrowser(this._getDocShell(window), |
|
1216 this._getAutoCompletePopup(window)); |
|
1217 }, |
|
1218 detachFormFillControllerFrom: function(window) { |
|
1219 this.getFormFillController().detachFromBrowser(this._getDocShell(window)); |
|
1220 }, |
|
1221 isBackButtonEnabled: function(window) { |
|
1222 return !this._getTopChromeWindow(window).document |
|
1223 .getElementById("Browser:Back") |
|
1224 .hasAttribute("disabled"); |
|
1225 }, |
|
1226 //XXX end of problematic APIs |
|
1227 |
|
1228 addChromeEventListener: function(type, listener, capture, allowUntrusted) { |
|
1229 addEventListener(type, listener, capture, allowUntrusted); |
|
1230 }, |
|
1231 removeChromeEventListener: function(type, listener, capture) { |
|
1232 removeEventListener(type, listener, capture); |
|
1233 }, |
|
1234 |
|
1235 // Note: each call to registerConsoleListener MUST be paired with a |
|
1236 // call to postConsoleSentinel; when the callback receives the |
|
1237 // sentinel it will unregister itself (_after_ calling the |
|
1238 // callback). SimpleTest.expectConsoleMessages does this for you. |
|
1239 // If you register more than one console listener, a call to |
|
1240 // postConsoleSentinel will zap all of them. |
|
1241 registerConsoleListener: function(callback) { |
|
1242 let listener = new SPConsoleListener(callback); |
|
1243 Services.console.registerListener(listener); |
|
1244 }, |
|
1245 postConsoleSentinel: function() { |
|
1246 Services.console.logStringMessage("SENTINEL"); |
|
1247 }, |
|
1248 resetConsole: function() { |
|
1249 Services.console.reset(); |
|
1250 }, |
|
1251 |
|
1252 getMaxLineBoxWidth: function(window) { |
|
1253 return this._getMUDV(window).maxLineBoxWidth; |
|
1254 }, |
|
1255 |
|
1256 setMaxLineBoxWidth: function(window, width) { |
|
1257 this._getMUDV(window).changeMaxLineBoxWidth(width); |
|
1258 }, |
|
1259 |
|
1260 getFullZoom: function(window) { |
|
1261 return this._getMUDV(window).fullZoom; |
|
1262 }, |
|
1263 setFullZoom: function(window, zoom) { |
|
1264 this._getMUDV(window).fullZoom = zoom; |
|
1265 }, |
|
1266 getTextZoom: function(window) { |
|
1267 return this._getMUDV(window).textZoom; |
|
1268 }, |
|
1269 setTextZoom: function(window, zoom) { |
|
1270 this._getMUDV(window).textZoom = zoom; |
|
1271 }, |
|
1272 |
|
1273 emulateMedium: function(window, mediaType) { |
|
1274 this._getMUDV(window).emulateMedium(mediaType); |
|
1275 }, |
|
1276 stopEmulatingMedium: function(window) { |
|
1277 this._getMUDV(window).stopEmulatingMedium(); |
|
1278 }, |
|
1279 |
|
1280 snapshotWindowWithOptions: function (win, rect, bgcolor, options) { |
|
1281 var el = this.window.get().document.createElementNS("http://www.w3.org/1999/xhtml", "canvas"); |
|
1282 if (rect === undefined) { |
|
1283 rect = { top: win.scrollY, left: win.scrollX, |
|
1284 width: win.innerWidth, height: win.innerHeight }; |
|
1285 } |
|
1286 if (bgcolor === undefined) { |
|
1287 bgcolor = "rgb(255,255,255)"; |
|
1288 } |
|
1289 if (options === undefined) { |
|
1290 options = { }; |
|
1291 } |
|
1292 |
|
1293 el.width = rect.width; |
|
1294 el.height = rect.height; |
|
1295 var ctx = el.getContext("2d"); |
|
1296 var flags = 0; |
|
1297 |
|
1298 for (var option in options) { |
|
1299 flags |= options[option] && ctx[option]; |
|
1300 } |
|
1301 |
|
1302 ctx.drawWindow(win, |
|
1303 rect.left, rect.top, rect.width, rect.height, |
|
1304 bgcolor, |
|
1305 flags); |
|
1306 return el; |
|
1307 }, |
|
1308 |
|
1309 snapshotWindow: function (win, withCaret, rect, bgcolor) { |
|
1310 return this.snapshotWindowWithOptions(win, rect, bgcolor, |
|
1311 { DRAWWINDOW_DRAW_CARET: withCaret }); |
|
1312 }, |
|
1313 |
|
1314 snapshotRect: function (win, rect, bgcolor) { |
|
1315 return this.snapshotWindowWithOptions(win, rect, bgcolor); |
|
1316 }, |
|
1317 |
|
1318 gc: function() { |
|
1319 this.DOMWindowUtils.garbageCollect(); |
|
1320 }, |
|
1321 |
|
1322 forceGC: function() { |
|
1323 Cu.forceGC(); |
|
1324 }, |
|
1325 |
|
1326 forceCC: function() { |
|
1327 Cu.forceCC(); |
|
1328 }, |
|
1329 |
|
1330 // Due to various dependencies between JS objects and C++ objects, an ordinary |
|
1331 // forceGC doesn't necessarily clear all unused objects, thus the GC and CC |
|
1332 // needs to run several times and when no other JS is running. |
|
1333 // The current number of iterations has been determined according to massive |
|
1334 // cross platform testing. |
|
1335 exactGC: function(win, callback) { |
|
1336 var self = this; |
|
1337 let count = 0; |
|
1338 |
|
1339 function doPreciseGCandCC() { |
|
1340 function scheduledGCCallback() { |
|
1341 self.getDOMWindowUtils(win).cycleCollect(); |
|
1342 |
|
1343 if (++count < 2) { |
|
1344 doPreciseGCandCC(); |
|
1345 } else { |
|
1346 callback(); |
|
1347 } |
|
1348 } |
|
1349 |
|
1350 Cu.schedulePreciseGC(scheduledGCCallback); |
|
1351 } |
|
1352 |
|
1353 doPreciseGCandCC(); |
|
1354 }, |
|
1355 |
|
1356 setGCZeal: function(zeal) { |
|
1357 Cu.setGCZeal(zeal); |
|
1358 }, |
|
1359 |
|
1360 isMainProcess: function() { |
|
1361 try { |
|
1362 return Cc["@mozilla.org/xre/app-info;1"]. |
|
1363 getService(Ci.nsIXULRuntime). |
|
1364 processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT; |
|
1365 } catch (e) { } |
|
1366 return true; |
|
1367 }, |
|
1368 |
|
1369 _xpcomabi: null, |
|
1370 |
|
1371 get XPCOMABI() { |
|
1372 if (this._xpcomabi != null) |
|
1373 return this._xpcomabi; |
|
1374 |
|
1375 var xulRuntime = Cc["@mozilla.org/xre/app-info;1"] |
|
1376 .getService(Components.interfaces.nsIXULAppInfo) |
|
1377 .QueryInterface(Components.interfaces.nsIXULRuntime); |
|
1378 |
|
1379 this._xpcomabi = xulRuntime.XPCOMABI; |
|
1380 return this._xpcomabi; |
|
1381 }, |
|
1382 |
|
1383 // The optional aWin parameter allows the caller to specify a given window in |
|
1384 // whose scope the runnable should be dispatched. If aFun throws, the |
|
1385 // exception will be reported to aWin. |
|
1386 executeSoon: function(aFun, aWin) { |
|
1387 // Create the runnable in the scope of aWin to avoid running into COWs. |
|
1388 var runnable = {}; |
|
1389 if (aWin) |
|
1390 runnable = Cu.createObjectIn(aWin); |
|
1391 runnable.run = aFun; |
|
1392 Cu.dispatch(runnable, aWin); |
|
1393 }, |
|
1394 |
|
1395 _os: null, |
|
1396 |
|
1397 get OS() { |
|
1398 if (this._os != null) |
|
1399 return this._os; |
|
1400 |
|
1401 var xulRuntime = Cc["@mozilla.org/xre/app-info;1"] |
|
1402 .getService(Components.interfaces.nsIXULAppInfo) |
|
1403 .QueryInterface(Components.interfaces.nsIXULRuntime); |
|
1404 |
|
1405 this._os = xulRuntime.OS; |
|
1406 return this._os; |
|
1407 }, |
|
1408 |
|
1409 addSystemEventListener: function(target, type, listener, useCapture) { |
|
1410 Cc["@mozilla.org/eventlistenerservice;1"]. |
|
1411 getService(Ci.nsIEventListenerService). |
|
1412 addSystemEventListener(target, type, listener, useCapture); |
|
1413 }, |
|
1414 removeSystemEventListener: function(target, type, listener, useCapture) { |
|
1415 Cc["@mozilla.org/eventlistenerservice;1"]. |
|
1416 getService(Ci.nsIEventListenerService). |
|
1417 removeSystemEventListener(target, type, listener, useCapture); |
|
1418 }, |
|
1419 |
|
1420 getDOMRequestService: function() { |
|
1421 var serv = Cc["@mozilla.org/dom/dom-request-service;1"]. |
|
1422 getService(Ci.nsIDOMRequestService); |
|
1423 var res = { __exposedProps__: {} }; |
|
1424 var props = ["createRequest", "createCursor", "fireError", "fireSuccess", |
|
1425 "fireDone", "fireDetailedError"]; |
|
1426 for (i in props) { |
|
1427 let prop = props[i]; |
|
1428 res[prop] = function() { return serv[prop].apply(serv, arguments) }; |
|
1429 res.__exposedProps__[prop] = "r"; |
|
1430 } |
|
1431 return res; |
|
1432 }, |
|
1433 |
|
1434 setLogFile: function(path) { |
|
1435 this._mfl = new MozillaFileLogger(path); |
|
1436 }, |
|
1437 |
|
1438 log: function(data) { |
|
1439 this._mfl.log(data); |
|
1440 }, |
|
1441 |
|
1442 closeLogFile: function() { |
|
1443 this._mfl.close(); |
|
1444 }, |
|
1445 |
|
1446 addCategoryEntry: function(category, entry, value, persists, replace) { |
|
1447 Components.classes["@mozilla.org/categorymanager;1"]. |
|
1448 getService(Components.interfaces.nsICategoryManager). |
|
1449 addCategoryEntry(category, entry, value, persists, replace); |
|
1450 }, |
|
1451 |
|
1452 deleteCategoryEntry: function(category, entry, persists) { |
|
1453 Components.classes["@mozilla.org/categorymanager;1"]. |
|
1454 getService(Components.interfaces.nsICategoryManager). |
|
1455 deleteCategoryEntry(category, entry, persists); |
|
1456 }, |
|
1457 |
|
1458 copyString: function(str, doc) { |
|
1459 Components.classes["@mozilla.org/widget/clipboardhelper;1"]. |
|
1460 getService(Components.interfaces.nsIClipboardHelper). |
|
1461 copyString(str, doc); |
|
1462 }, |
|
1463 |
|
1464 openDialog: function(win, args) { |
|
1465 return win.openDialog.apply(win, args); |
|
1466 }, |
|
1467 |
|
1468 // :jdm gets credit for this. ex: getPrivilegedProps(window, 'location.href'); |
|
1469 getPrivilegedProps: function(obj, props) { |
|
1470 var parts = props.split('.'); |
|
1471 |
|
1472 for (var i = 0; i < parts.length; i++) { |
|
1473 var p = parts[i]; |
|
1474 if (obj[p]) { |
|
1475 obj = obj[p]; |
|
1476 } else { |
|
1477 return null; |
|
1478 } |
|
1479 } |
|
1480 return obj; |
|
1481 }, |
|
1482 |
|
1483 get focusManager() { |
|
1484 if (this._fm != null) |
|
1485 return this._fm; |
|
1486 |
|
1487 this._fm = Components.classes["@mozilla.org/focus-manager;1"]. |
|
1488 getService(Components.interfaces.nsIFocusManager); |
|
1489 |
|
1490 return this._fm; |
|
1491 }, |
|
1492 |
|
1493 getFocusedElementForWindow: function(targetWindow, aDeep, childTargetWindow) { |
|
1494 return this.focusManager.getFocusedElementForWindow(targetWindow, aDeep, childTargetWindow); |
|
1495 }, |
|
1496 |
|
1497 activeWindow: function() { |
|
1498 return this.focusManager.activeWindow; |
|
1499 }, |
|
1500 |
|
1501 focusedWindow: function() { |
|
1502 return this.focusManager.focusedWindow; |
|
1503 }, |
|
1504 |
|
1505 focus: function(aWindow) { |
|
1506 // This is called inside TestRunner._makeIframe without aWindow, because of assertions in oop mochitests |
|
1507 // With aWindow, it is called in SimpleTest.waitForFocus to allow popup window opener focus switching |
|
1508 if (aWindow) |
|
1509 aWindow.focus(); |
|
1510 sendAsyncMessage("SpecialPowers.Focus", {}); |
|
1511 }, |
|
1512 |
|
1513 getClipboardData: function(flavor, whichClipboard) { |
|
1514 if (this._cb == null) |
|
1515 this._cb = Components.classes["@mozilla.org/widget/clipboard;1"]. |
|
1516 getService(Components.interfaces.nsIClipboard); |
|
1517 if (whichClipboard === undefined) |
|
1518 whichClipboard = this._cb.kGlobalClipboard; |
|
1519 |
|
1520 var xferable = Components.classes["@mozilla.org/widget/transferable;1"]. |
|
1521 createInstance(Components.interfaces.nsITransferable); |
|
1522 xferable.init(this._getDocShell(content.window) |
|
1523 .QueryInterface(Components.interfaces.nsILoadContext)); |
|
1524 xferable.addDataFlavor(flavor); |
|
1525 this._cb.getData(xferable, whichClipboard); |
|
1526 var data = {}; |
|
1527 try { |
|
1528 xferable.getTransferData(flavor, data, {}); |
|
1529 } catch (e) {} |
|
1530 data = data.value || null; |
|
1531 if (data == null) |
|
1532 return ""; |
|
1533 |
|
1534 return data.QueryInterface(Components.interfaces.nsISupportsString).data; |
|
1535 }, |
|
1536 |
|
1537 clipboardCopyString: function(preExpectedVal, doc) { |
|
1538 var cbHelperSvc = Components.classes["@mozilla.org/widget/clipboardhelper;1"]. |
|
1539 getService(Components.interfaces.nsIClipboardHelper); |
|
1540 cbHelperSvc.copyString(preExpectedVal, doc); |
|
1541 }, |
|
1542 |
|
1543 supportsSelectionClipboard: function() { |
|
1544 if (this._cb == null) { |
|
1545 this._cb = Components.classes["@mozilla.org/widget/clipboard;1"]. |
|
1546 getService(Components.interfaces.nsIClipboard); |
|
1547 } |
|
1548 return this._cb.supportsSelectionClipboard(); |
|
1549 }, |
|
1550 |
|
1551 swapFactoryRegistration: function(cid, contractID, newFactory, oldFactory) { |
|
1552 var componentRegistrar = Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar); |
|
1553 |
|
1554 var unregisterFactory = newFactory; |
|
1555 var registerFactory = oldFactory; |
|
1556 |
|
1557 if (cid == null) { |
|
1558 if (contractID != null) { |
|
1559 cid = componentRegistrar.contractIDToCID(contractID); |
|
1560 oldFactory = Components.manager.getClassObject(Components.classes[contractID], |
|
1561 Components.interfaces.nsIFactory); |
|
1562 } else { |
|
1563 return {'error': "trying to register a new contract ID: Missing contractID"}; |
|
1564 } |
|
1565 |
|
1566 unregisterFactory = oldFactory; |
|
1567 registerFactory = newFactory; |
|
1568 } |
|
1569 componentRegistrar.unregisterFactory(cid, |
|
1570 unregisterFactory); |
|
1571 |
|
1572 // Restore the original factory. |
|
1573 componentRegistrar.registerFactory(cid, |
|
1574 "", |
|
1575 contractID, |
|
1576 registerFactory); |
|
1577 return {'cid':cid, 'originalFactory':oldFactory}; |
|
1578 }, |
|
1579 |
|
1580 _getElement: function(aWindow, id) { |
|
1581 return ((typeof(id) == "string") ? |
|
1582 aWindow.document.getElementById(id) : id); |
|
1583 }, |
|
1584 |
|
1585 dispatchEvent: function(aWindow, target, event) { |
|
1586 var el = this._getElement(aWindow, target); |
|
1587 return el.dispatchEvent(event); |
|
1588 }, |
|
1589 |
|
1590 get isDebugBuild() { |
|
1591 delete this.isDebugBuild; |
|
1592 var debug = Cc["@mozilla.org/xpcom/debug;1"].getService(Ci.nsIDebug2); |
|
1593 return this.isDebugBuild = debug.isDebugBuild; |
|
1594 }, |
|
1595 assertionCount: function() { |
|
1596 var debugsvc = Cc['@mozilla.org/xpcom/debug;1'].getService(Ci.nsIDebug2); |
|
1597 return debugsvc.assertionCount; |
|
1598 }, |
|
1599 |
|
1600 /** |
|
1601 * Get the message manager associated with an <iframe mozbrowser>. |
|
1602 */ |
|
1603 getBrowserFrameMessageManager: function(aFrameElement) { |
|
1604 return this.wrap(aFrameElement.QueryInterface(Ci.nsIFrameLoaderOwner) |
|
1605 .frameLoader |
|
1606 .messageManager); |
|
1607 }, |
|
1608 |
|
1609 setFullscreenAllowed: function(document) { |
|
1610 var pm = Cc["@mozilla.org/permissionmanager;1"].getService(Ci.nsIPermissionManager); |
|
1611 pm.addFromPrincipal(document.nodePrincipal, "fullscreen", Ci.nsIPermissionManager.ALLOW_ACTION); |
|
1612 var obsvc = Cc['@mozilla.org/observer-service;1'] |
|
1613 .getService(Ci.nsIObserverService); |
|
1614 obsvc.notifyObservers(document, "fullscreen-approved", null); |
|
1615 }, |
|
1616 |
|
1617 removeFullscreenAllowed: function(document) { |
|
1618 var pm = Cc["@mozilla.org/permissionmanager;1"].getService(Ci.nsIPermissionManager); |
|
1619 pm.removeFromPrincipal(document.nodePrincipal, "fullscreen"); |
|
1620 }, |
|
1621 |
|
1622 _getInfoFromPermissionArg: function(arg) { |
|
1623 let url = ""; |
|
1624 let appId = Ci.nsIScriptSecurityManager.NO_APP_ID; |
|
1625 let isInBrowserElement = false; |
|
1626 |
|
1627 if (typeof(arg) == "string") { |
|
1628 // It's an URL. |
|
1629 url = Cc["@mozilla.org/network/io-service;1"] |
|
1630 .getService(Ci.nsIIOService) |
|
1631 .newURI(arg, null, null) |
|
1632 .spec; |
|
1633 } else if (arg.manifestURL) { |
|
1634 // It's a thing representing an app. |
|
1635 let appsSvc = Cc["@mozilla.org/AppsService;1"] |
|
1636 .getService(Ci.nsIAppsService) |
|
1637 let app = appsSvc.getAppByManifestURL(arg.manifestURL); |
|
1638 |
|
1639 if (!app) { |
|
1640 throw "No app for this manifest!"; |
|
1641 } |
|
1642 |
|
1643 appId = appsSvc.getAppLocalIdByManifestURL(arg.manifestURL); |
|
1644 url = app.origin; |
|
1645 isInBrowserElement = arg.isInBrowserElement || false; |
|
1646 } else if (arg.nodePrincipal) { |
|
1647 // It's a document. |
|
1648 url = arg.nodePrincipal.URI.spec; |
|
1649 appId = arg.nodePrincipal.appId; |
|
1650 isInBrowserElement = arg.nodePrincipal.isInBrowserElement; |
|
1651 } else { |
|
1652 url = arg.url; |
|
1653 appId = arg.appId; |
|
1654 isInBrowserElement = arg.isInBrowserElement; |
|
1655 } |
|
1656 |
|
1657 return [ url, appId, isInBrowserElement ]; |
|
1658 }, |
|
1659 |
|
1660 addPermission: function(type, allow, arg) { |
|
1661 let [url, appId, isInBrowserElement] = this._getInfoFromPermissionArg(arg); |
|
1662 |
|
1663 let permission; |
|
1664 if (typeof allow !== 'boolean') { |
|
1665 permission = allow; |
|
1666 } else { |
|
1667 permission = allow ? Ci.nsIPermissionManager.ALLOW_ACTION |
|
1668 : Ci.nsIPermissionManager.DENY_ACTION; |
|
1669 } |
|
1670 |
|
1671 var msg = { |
|
1672 'op': 'add', |
|
1673 'type': type, |
|
1674 'permission': permission, |
|
1675 'url': url, |
|
1676 'appId': appId, |
|
1677 'isInBrowserElement': isInBrowserElement |
|
1678 }; |
|
1679 |
|
1680 this._sendSyncMessage('SPPermissionManager', msg); |
|
1681 }, |
|
1682 |
|
1683 removePermission: function(type, arg) { |
|
1684 let [url, appId, isInBrowserElement] = this._getInfoFromPermissionArg(arg); |
|
1685 |
|
1686 var msg = { |
|
1687 'op': 'remove', |
|
1688 'type': type, |
|
1689 'url': url, |
|
1690 'appId': appId, |
|
1691 'isInBrowserElement': isInBrowserElement |
|
1692 }; |
|
1693 |
|
1694 this._sendSyncMessage('SPPermissionManager', msg); |
|
1695 }, |
|
1696 |
|
1697 hasPermission: function (type, arg) { |
|
1698 let [url, appId, isInBrowserElement] = this._getInfoFromPermissionArg(arg); |
|
1699 |
|
1700 var msg = { |
|
1701 'op': 'has', |
|
1702 'type': type, |
|
1703 'url': url, |
|
1704 'appId': appId, |
|
1705 'isInBrowserElement': isInBrowserElement |
|
1706 }; |
|
1707 |
|
1708 return this._sendSyncMessage('SPPermissionManager', msg)[0]; |
|
1709 }, |
|
1710 testPermission: function (type, value, arg) { |
|
1711 let [url, appId, isInBrowserElement] = this._getInfoFromPermissionArg(arg); |
|
1712 |
|
1713 var msg = { |
|
1714 'op': 'test', |
|
1715 'type': type, |
|
1716 'value': value, |
|
1717 'url': url, |
|
1718 'appId': appId, |
|
1719 'isInBrowserElement': isInBrowserElement |
|
1720 }; |
|
1721 return this._sendSyncMessage('SPPermissionManager', msg)[0]; |
|
1722 }, |
|
1723 |
|
1724 getMozFullPath: function(file) { |
|
1725 return file.mozFullPath; |
|
1726 }, |
|
1727 |
|
1728 isWindowPrivate: function(win) { |
|
1729 return PrivateBrowsingUtils.isWindowPrivate(win); |
|
1730 }, |
|
1731 |
|
1732 notifyObserversInParentProcess: function(subject, topic, data) { |
|
1733 if (subject) { |
|
1734 throw new Error("Can't send subject to another process!"); |
|
1735 } |
|
1736 if (this.isMainProcess()) { |
|
1737 this.notifyObservers(subject, topic, data); |
|
1738 return; |
|
1739 } |
|
1740 var msg = { |
|
1741 'op': 'notify', |
|
1742 'observerTopic': topic, |
|
1743 'observerData': data |
|
1744 }; |
|
1745 this._sendSyncMessage('SPObserverService', msg); |
|
1746 }, |
|
1747 }; |
|
1748 |
|
1749 this.SpecialPowersAPI = SpecialPowersAPI; |
|
1750 this.bindDOMWindowUtils = bindDOMWindowUtils; |
|
1751 this.getRawComponents = getRawComponents; |