testing/specialpowers/content/specialpowersAPI.js

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:c9d0e7ad3eec
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;

mercurial