diff -r 000000000000 -r 6474c204b198 testing/specialpowers/content/specialpowersAPI.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/testing/specialpowers/content/specialpowersAPI.js Wed Dec 31 06:09:35 2014 +0100
@@ -0,0 +1,1751 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+/* This code is loaded in every child process that is started by mochitest in
+ * order to be used as a replacement for UniversalXPConnect
+ */
+
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+var Cu = Components.utils;
+
+Cu.import("resource://specialpowers/MockFilePicker.jsm");
+Cu.import("resource://specialpowers/MockColorPicker.jsm");
+Cu.import("resource://specialpowers/MockPermissionPrompt.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+function SpecialPowersAPI() {
+ this._consoleListeners = [];
+ this._encounteredCrashDumpFiles = [];
+ this._unexpectedCrashDumpFiles = { };
+ this._crashDumpDir = null;
+ this._mfl = null;
+ this._prefEnvUndoStack = [];
+ this._pendingPrefs = [];
+ this._applyingPrefs = false;
+ this._permissionsUndoStack = [];
+ this._pendingPermissions = [];
+ this._applyingPermissions = false;
+ this._fm = null;
+ this._cb = null;
+}
+
+function bindDOMWindowUtils(aWindow) {
+ if (!aWindow)
+ return
+
+ var util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ return wrapPrivileged(util);
+}
+
+function getRawComponents(aWindow) {
+ // If we're running in automation that supports enablePrivilege, then we also
+ // provided access to the privileged Components.
+ try {
+ let win = Cu.waiveXrays(aWindow);
+ if (typeof win.netscape.security.PrivilegeManager == 'object')
+ Cu.forcePrivilegedComponentsForScope(aWindow);
+ } catch (e) {}
+ return Cu.getComponentsForScope(aWindow);
+}
+
+function isWrappable(x) {
+ if (typeof x === "object")
+ return x !== null;
+ return typeof x === "function";
+};
+
+function isWrapper(x) {
+ return isWrappable(x) && (typeof x.SpecialPowers_wrappedObject !== "undefined");
+};
+
+function unwrapIfWrapped(x) {
+ return isWrapper(x) ? unwrapPrivileged(x) : x;
+};
+
+function wrapIfUnwrapped(x) {
+ return isWrapper(x) ? x : wrapPrivileged(x);
+}
+
+function isXrayWrapper(x) {
+ return Cu.isXrayWrapper(x);
+}
+
+function callGetOwnPropertyDescriptor(obj, name) {
+ // Quickstubbed getters and setters are propertyOps, and don't get reified
+ // until someone calls __lookupGetter__ or __lookupSetter__ on them (note
+ // that there are special version of those functions for quickstubs, so
+ // apply()ing Object.prototype.__lookupGetter__ isn't good enough). Try to
+ // trigger reification before calling Object.getOwnPropertyDescriptor.
+ //
+ // See bug 764315.
+ try {
+ obj.__lookupGetter__(name);
+ obj.__lookupSetter__(name);
+ } catch(e) { }
+ return Object.getOwnPropertyDescriptor(obj, name);
+}
+
+// We can't call apply() directy on Xray-wrapped functions, so we have to be
+// clever.
+function doApply(fun, invocant, args) {
+ return Function.prototype.apply.call(fun, invocant, args);
+}
+
+function wrapPrivileged(obj) {
+
+ // Primitives pass straight through.
+ if (!isWrappable(obj))
+ return obj;
+
+ // No double wrapping.
+ if (isWrapper(obj))
+ throw "Trying to double-wrap object!";
+
+ // Make our core wrapper object.
+ var handler = new SpecialPowersHandler(obj);
+
+ // If the object is callable, make a function proxy.
+ if (typeof obj === "function") {
+ var callTrap = function() {
+ // The invocant and arguments may or may not be wrappers. Unwrap them if necessary.
+ var invocant = unwrapIfWrapped(this);
+ var unwrappedArgs = Array.prototype.slice.call(arguments).map(unwrapIfWrapped);
+
+ try {
+ return wrapPrivileged(doApply(obj, invocant, unwrappedArgs));
+ } catch (e) {
+ // Wrap exceptions and re-throw them.
+ throw wrapIfUnwrapped(e);
+ }
+ };
+ var constructTrap = function() {
+ // The arguments may or may not be wrappers. Unwrap them if necessary.
+ var unwrappedArgs = Array.prototype.slice.call(arguments).map(unwrapIfWrapped);
+
+ // We want to invoke "obj" as a constructor, but using unwrappedArgs as
+ // the arguments. Make sure to wrap and re-throw exceptions!
+ try {
+ return wrapPrivileged(new obj(...unwrappedArgs));
+ } catch (e) {
+ throw wrapIfUnwrapped(e);
+ }
+ };
+
+ return Proxy.createFunction(handler, callTrap, constructTrap);
+ }
+
+ // Otherwise, just make a regular object proxy.
+ return Proxy.create(handler);
+};
+
+function unwrapPrivileged(x) {
+
+ // We don't wrap primitives, so sometimes we have a primitive where we'd
+ // expect to have a wrapper. The proxy pretends to be the type that it's
+ // emulating, so we can just as easily check isWrappable() on a proxy as
+ // we can on an unwrapped object.
+ if (!isWrappable(x))
+ return x;
+
+ // If we have a wrappable type, make sure it's wrapped.
+ if (!isWrapper(x))
+ throw "Trying to unwrap a non-wrapped object!";
+
+ // Unwrap.
+ return x.SpecialPowers_wrappedObject;
+};
+
+function crawlProtoChain(obj, fn) {
+ var rv = fn(obj);
+ if (rv !== undefined)
+ return rv;
+ if (Object.getPrototypeOf(obj))
+ return crawlProtoChain(Object.getPrototypeOf(obj), fn);
+};
+
+/*
+ * We want to waive the __exposedProps__ security check for SpecialPowers-wrapped
+ * objects. We do this by creating a proxy singleton that just always returns 'rw'
+ * for any property name.
+ */
+function ExposedPropsWaiverHandler() {
+ // NB: XPConnect denies access if the relevant member of __exposedProps__ is not
+ // enumerable.
+ var _permit = { value: 'rw', writable: false, configurable: false, enumerable: true };
+ return {
+ getOwnPropertyDescriptor: function(name) { return _permit; },
+ getPropertyDescriptor: function(name) { return _permit; },
+ getOwnPropertyNames: function() { throw Error("Can't enumerate ExposedPropsWaiver"); },
+ getPropertyNames: function() { throw Error("Can't enumerate ExposedPropsWaiver"); },
+ enumerate: function() { throw Error("Can't enumerate ExposedPropsWaiver"); },
+ defineProperty: function(name) { throw Error("Can't define props on ExposedPropsWaiver"); },
+ delete: function(name) { throw Error("Can't delete props from ExposedPropsWaiver"); }
+ };
+};
+ExposedPropsWaiver = Proxy.create(ExposedPropsWaiverHandler());
+
+function SpecialPowersHandler(obj) {
+ this.wrappedObject = obj;
+};
+
+// Allow us to transitively maintain the membrane by wrapping descriptors
+// we return.
+SpecialPowersHandler.prototype.doGetPropertyDescriptor = function(name, own) {
+
+ // Handle our special API.
+ if (name == "SpecialPowers_wrappedObject")
+ return { value: this.wrappedObject, writeable: false, configurable: false, enumerable: false };
+
+ // Handle __exposedProps__.
+ if (name == "__exposedProps__")
+ return { value: ExposedPropsWaiver, writable: false, configurable: false, enumerable: false };
+
+ // In general, we want Xray wrappers for content DOM objects, because waiving
+ // Xray gives us Xray waiver wrappers that clamp the principal when we cross
+ // compartment boundaries. However, Xray adds some gunk to toString(), which
+ // has the potential to confuse consumers that aren't expecting Xray wrappers.
+ // Since toString() is a non-privileged method that returns only strings, we
+ // can just waive Xray for that case.
+ var obj = name == 'toString' ? XPCNativeWrapper.unwrap(this.wrappedObject)
+ : this.wrappedObject;
+
+ //
+ // Call through to the wrapped object.
+ //
+ // Note that we have several cases here, each of which requires special handling.
+ //
+ var desc;
+
+ // Case 1: Own Properties.
+ //
+ // This one is easy, thanks to Object.getOwnPropertyDescriptor().
+ if (own)
+ desc = callGetOwnPropertyDescriptor(obj, name);
+
+ // Case 2: Not own, not Xray-wrapped.
+ //
+ // Here, we can just crawl the prototype chain, calling
+ // Object.getOwnPropertyDescriptor until we find what we want.
+ //
+ // NB: Make sure to check this.wrappedObject here, rather than obj, because
+ // we may have waived Xray on obj above.
+ else if (!isXrayWrapper(this.wrappedObject))
+ desc = crawlProtoChain(obj, function(o) {return callGetOwnPropertyDescriptor(o, name);});
+
+ // Case 3: Not own, Xray-wrapped.
+ //
+ // This one is harder, because we Xray wrappers are flattened and don't have
+ // a prototype. Xray wrappers are proxies themselves, so we'd love to just call
+ // through to XrayWrapper::getPropertyDescriptor(). Unfortunately though,
+ // we don't have any way to do that. :-(
+ //
+ // So we first try with a call to getOwnPropertyDescriptor(). If that fails,
+ // we make up a descriptor, using some assumptions about what kinds of things
+ // tend to live on the prototypes of Xray-wrapped objects.
+ else {
+ desc = Object.getOwnPropertyDescriptor(obj, name);
+ if (!desc) {
+ var getter = Object.prototype.__lookupGetter__.call(obj, name);
+ var setter = Object.prototype.__lookupSetter__.call(obj, name);
+ if (getter || setter)
+ desc = {get: getter, set: setter, configurable: true, enumerable: true};
+ else if (name in obj)
+ desc = {value: obj[name], writable: false, configurable: true, enumerable: true};
+ }
+ }
+
+ // Bail if we've got nothing.
+ if (typeof desc === 'undefined')
+ return undefined;
+
+ // When accessors are implemented as JSPropertyOps rather than JSNatives (ie,
+ // QuickStubs), the js engine does the wrong thing and treats it as a value
+ // descriptor rather than an accessor descriptor. Jorendorff suggested this
+ // little hack to work around it. See bug 520882.
+ if (desc && 'value' in desc && desc.value === undefined)
+ desc.value = obj[name];
+
+ // A trapping proxy's properties must always be configurable, but sometimes
+ // this we get non-configurable properties from Object.getOwnPropertyDescriptor().
+ // Tell a white lie.
+ desc.configurable = true;
+
+ // Transitively maintain the wrapper membrane.
+ function wrapIfExists(key) { if (key in desc) desc[key] = wrapPrivileged(desc[key]); };
+ wrapIfExists('value');
+ wrapIfExists('get');
+ wrapIfExists('set');
+
+ return desc;
+};
+
+SpecialPowersHandler.prototype.getOwnPropertyDescriptor = function(name) {
+ return this.doGetPropertyDescriptor(name, true);
+};
+
+SpecialPowersHandler.prototype.getPropertyDescriptor = function(name) {
+ return this.doGetPropertyDescriptor(name, false);
+};
+
+function doGetOwnPropertyNames(obj, props) {
+
+ // Insert our special API. It's not enumerable, but getPropertyNames()
+ // includes non-enumerable properties.
+ var specialAPI = 'SpecialPowers_wrappedObject';
+ if (props.indexOf(specialAPI) == -1)
+ props.push(specialAPI);
+
+ // Do the normal thing.
+ var flt = function(a) { return props.indexOf(a) == -1; };
+ props = props.concat(Object.getOwnPropertyNames(obj).filter(flt));
+
+ // If we've got an Xray wrapper, include the expandos as well.
+ if ('wrappedJSObject' in obj)
+ props = props.concat(Object.getOwnPropertyNames(obj.wrappedJSObject)
+ .filter(flt));
+
+ return props;
+}
+
+SpecialPowersHandler.prototype.getOwnPropertyNames = function() {
+ return doGetOwnPropertyNames(this.wrappedObject, []);
+};
+
+SpecialPowersHandler.prototype.getPropertyNames = function() {
+
+ // Manually walk the prototype chain, making sure to add only property names
+ // that haven't been overridden.
+ //
+ // There's some trickiness here with Xray wrappers. Xray wrappers don't have
+ // a prototype, so we need to unwrap them if we want to get all of the names
+ // with Object.getOwnPropertyNames(). But we don't really want to unwrap the
+ // base object, because that will include expandos that are inaccessible via
+ // our implementation of get{,Own}PropertyDescriptor(). So we unwrap just
+ // before accessing the prototype. This ensures that we get Xray vision on
+ // the base object, and no Xray vision for the rest of the way up.
+ var obj = this.wrappedObject;
+ var props = [];
+ while (obj) {
+ props = doGetOwnPropertyNames(obj, props);
+ obj = Object.getPrototypeOf(XPCNativeWrapper.unwrap(obj));
+ }
+ return props;
+};
+
+SpecialPowersHandler.prototype.defineProperty = function(name, desc) {
+ return Object.defineProperty(this.wrappedObject, name, desc);
+};
+
+SpecialPowersHandler.prototype.delete = function(name) {
+ return delete this.wrappedObject[name];
+};
+
+SpecialPowersHandler.prototype.fix = function() { return undefined; /* Throws a TypeError. */ };
+
+// Per the ES5 spec this is a derived trap, but it's fundamental in spidermonkey
+// for some reason. See bug 665198.
+SpecialPowersHandler.prototype.enumerate = function() {
+ var t = this;
+ var filt = function(name) { return t.getPropertyDescriptor(name).enumerable; };
+ return this.getPropertyNames().filter(filt);
+};
+
+// SPConsoleListener reflects nsIConsoleMessage objects into JS in a
+// tidy, XPCOM-hiding way. Messages that are nsIScriptError objects
+// have their properties exposed in detail. It also auto-unregisters
+// itself when it receives a "sentinel" message.
+function SPConsoleListener(callback) {
+ this.callback = callback;
+}
+
+SPConsoleListener.prototype = {
+ observe: function(msg) {
+ let m = { message: msg.message,
+ errorMessage: null,
+ sourceName: null,
+ sourceLine: null,
+ lineNumber: null,
+ columnNumber: null,
+ category: null,
+ windowID: null,
+ isScriptError: false,
+ isWarning: false,
+ isException: false,
+ isStrict: false };
+ if (msg instanceof Ci.nsIScriptError) {
+ m.errorMessage = msg.errorMessage;
+ m.sourceName = msg.sourceName;
+ m.sourceLine = msg.sourceLine;
+ m.lineNumber = msg.lineNumber;
+ m.columnNumber = msg.columnNumber;
+ m.category = msg.category;
+ m.windowID = msg.outerWindowID;
+ m.isScriptError = true;
+ m.isWarning = ((msg.flags & Ci.nsIScriptError.warningFlag) === 1);
+ m.isException = ((msg.flags & Ci.nsIScriptError.exceptionFlag) === 1);
+ m.isStrict = ((msg.flags & Ci.nsIScriptError.strictFlag) === 1);
+ }
+
+ // expose all props of 'm' as read-only
+ let expose = {};
+ for (let prop in m)
+ expose[prop] = 'r';
+ m.__exposedProps__ = expose;
+ Object.freeze(m);
+
+ this.callback.call(undefined, m);
+
+ if (!m.isScriptError && m.message === "SENTINEL")
+ Services.console.unregisterListener(this);
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIConsoleListener])
+};
+
+function wrapCallback(cb) {
+ return function SpecialPowersCallbackWrapper() {
+ args = Array.prototype.map.call(arguments, wrapIfUnwrapped);
+ return cb.apply(this, args);
+ }
+}
+
+function wrapCallbackObject(obj) {
+ wrapper = { __exposedProps__: ExposedPropsWaiver };
+ for (var i in obj) {
+ if (typeof obj[i] == 'function')
+ wrapper[i] = wrapCallback(obj[i]);
+ else
+ wrapper[i] = obj[i];
+ }
+ return wrapper;
+}
+
+SpecialPowersAPI.prototype = {
+
+ /*
+ * Privileged object wrapping API
+ *
+ * Usage:
+ * var wrapper = SpecialPowers.wrap(obj);
+ * wrapper.privilegedMethod(); wrapper.privilegedProperty;
+ * obj === SpecialPowers.unwrap(wrapper);
+ *
+ * These functions provide transparent access to privileged objects using
+ * various pieces of deep SpiderMagic. Conceptually, a wrapper is just an
+ * object containing a reference to the underlying object, where all method
+ * calls and property accesses are transparently performed with the System
+ * Principal. Moreover, objects obtained from the wrapper (including properties
+ * and method return values) are wrapped automatically. Thus, after a single
+ * call to SpecialPowers.wrap(), the wrapper layer is transitively maintained.
+ *
+ * Known Issues:
+ *
+ * - The wrapping function does not preserve identity, so
+ * SpecialPowers.wrap(foo) !== SpecialPowers.wrap(foo). See bug 718543.
+ *
+ * - The wrapper cannot see expando properties on unprivileged DOM objects.
+ * That is to say, the wrapper uses Xray delegation.
+ *
+ * - The wrapper sometimes guesses certain ES5 attributes for returned
+ * properties. This is explained in a comment in the wrapper code above,
+ * and shouldn't be a problem.
+ */
+ wrap: wrapIfUnwrapped,
+ unwrap: unwrapIfWrapped,
+ isWrapper: isWrapper,
+
+ /*
+ * When content needs to pass a callback or a callback object to an API
+ * accessed over SpecialPowers, that API may sometimes receive arguments for
+ * whom it is forbidden to create a wrapper in content scopes. As such, we
+ * need a layer to wrap the values in SpecialPowers wrappers before they ever
+ * reach content.
+ */
+ wrapCallback: wrapCallback,
+ wrapCallbackObject: wrapCallbackObject,
+
+ /*
+ * Create blank privileged objects to use as out-params for privileged functions.
+ */
+ createBlankObject: function () {
+ var obj = new Object;
+ obj.__exposedProps__ = ExposedPropsWaiver;
+ return obj;
+ },
+
+ /*
+ * Because SpecialPowers wrappers don't preserve identity, comparing with ==
+ * can be hazardous. Sometimes we can just unwrap to compare, but sometimes
+ * wrapping the underlying object into a content scope is forbidden. This
+ * function strips any wrappers if they exist and compare the underlying
+ * values.
+ */
+ compare: function(a, b) {
+ return unwrapIfWrapped(a) === unwrapIfWrapped(b);
+ },
+
+ get MockFilePicker() {
+ return MockFilePicker
+ },
+
+ get MockColorPicker() {
+ return MockColorPicker
+ },
+
+ get MockPermissionPrompt() {
+ return MockPermissionPrompt
+ },
+
+ loadChromeScript: function (url) {
+ // Create a unique id for this chrome script
+ let uuidGenerator = Cc["@mozilla.org/uuid-generator;1"]
+ .getService(Ci.nsIUUIDGenerator);
+ let id = uuidGenerator.generateUUID().toString();
+
+ // Tells chrome code to evaluate this chrome script
+ this._sendSyncMessage("SPLoadChromeScript",
+ { url: url, id: id });
+
+ // Returns a MessageManager like API in order to be
+ // able to communicate with this chrome script
+ let listeners = [];
+ let chromeScript = {
+ addMessageListener: (name, listener) => {
+ listeners.push({ name: name, listener: listener });
+ },
+
+ removeMessageListener: (name, listener) => {
+ listeners = listeners.filter(
+ o => (o.name != name || o.listener != listener)
+ );
+ },
+
+ sendAsyncMessage: (name, message) => {
+ this._sendSyncMessage("SPChromeScriptMessage",
+ { id: id, name: name, message: message });
+ },
+
+ destroy: () => {
+ listeners = [];
+ this._removeMessageListener("SPChromeScriptMessage", chromeScript);
+ this._removeMessageListener("SPChromeScriptAssert", chromeScript);
+ },
+
+ receiveMessage: (aMessage) => {
+ let messageId = aMessage.json.id;
+ let name = aMessage.json.name;
+ let message = aMessage.json.message;
+ // Ignore message from other chrome script
+ if (messageId != id)
+ return;
+
+ if (aMessage.name == "SPChromeScriptMessage") {
+ listeners.filter(o => (o.name == name))
+ .forEach(o => o.listener(this.wrap(message)));
+ } else if (aMessage.name == "SPChromeScriptAssert") {
+ assert(aMessage.json);
+ }
+ }
+ };
+ this._addMessageListener("SPChromeScriptMessage", chromeScript);
+ this._addMessageListener("SPChromeScriptAssert", chromeScript);
+
+ let assert = json => {
+ // An assertion has been done in a mochitest chrome script
+ let {url, err, message, stack} = json;
+
+ // Try to fetch a test runner from the mochitest
+ // in order to properly log these assertions and notify
+ // all usefull log observers
+ let window = this.window.get();
+ let parentRunner, repr = function (o) o;
+ if (window) {
+ window = window.wrappedJSObject;
+ parentRunner = window.TestRunner;
+ if (window.repr) {
+ repr = window.repr;
+ }
+ }
+
+ // Craft a mochitest-like report string
+ var resultString = err ? "TEST-UNEXPECTED-FAIL" : "TEST-PASS";
+ var diagnostic =
+ message ? message :
+ ("assertion @ " + stack.filename + ":" + stack.lineNumber);
+ if (err) {
+ diagnostic +=
+ " - got " + repr(err.actual) +
+ ", expected " + repr(err.expected) +
+ " (operator " + err.operator + ")";
+ }
+ var msg = [resultString, url, diagnostic].join(" | ");
+ if (parentRunner) {
+ if (err) {
+ parentRunner.addFailedTest(url);
+ parentRunner.error(msg);
+ } else {
+ parentRunner.log(msg);
+ }
+ } else {
+ // When we are running only a single mochitest, there is no test runner
+ dump(msg + "\n");
+ }
+ };
+
+ return this.wrap(chromeScript);
+ },
+
+ get Services() {
+ return wrapPrivileged(Services);
+ },
+
+ /*
+ * In general, any Components object created for unprivileged scopes is
+ * neutered (it implements nsIXPCComponentsBase, but not nsIXPCComponents).
+ * We override this in certain legacy automation configurations (see the
+ * implementation of getRawComponents() above), but don't want to support
+ * it in cases where it isn't already required.
+ *
+ * In scopes with neutered Components, we don't have a natural referent for
+ * things like SpecialPowers.Cc. So in those cases, we fall back to the
+ * Components object from the SpecialPowers scope. This doesn't quite behave
+ * the same way (in particular, SpecialPowers.Cc[foo].createInstance() will
+ * create an instance in the SpecialPowers scope), but SpecialPowers wrapping
+ * is already a YMMV / Whatever-It-Takes-To-Get-TBPL-Green sort of thing.
+ *
+ * It probably wouldn't be too much work to just make SpecialPowers.Components
+ * unconditionally point to the Components object in the SpecialPowers scope.
+ * Try will tell what needs to be fixed up.
+ */
+ getFullComponents: function() {
+ return typeof this.Components.classes == 'object' ? this.Components
+ : Components;
+ },
+
+ /*
+ * Convenient shortcuts to the standard Components abbreviations. Note that
+ * we don't SpecialPowers-wrap Components.interfaces, because it's available
+ * to untrusted content, and wrapping it confuses QI and identity checks.
+ */
+ get Cc() { return wrapPrivileged(this.getFullComponents()).classes; },
+ get Ci() { return this.Components.interfaces; },
+ get Cu() { return wrapPrivileged(this.getFullComponents()).utils; },
+ get Cr() { return wrapPrivileged(this.Components).results; },
+
+ /*
+ * SpecialPowers.getRawComponents() allows content to get a reference to a
+ * naked (and, in certain automation configurations, privileged) Components
+ * object for its scope.
+ *
+ * SpecialPowers.getRawComponents(window) is defined as the global property
+ * window.SpecialPowers.Components for convenience.
+ */
+ getRawComponents: getRawComponents,
+
+ getDOMWindowUtils: function(aWindow) {
+ if (aWindow == this.window.get() && this.DOMWindowUtils != null)
+ return this.DOMWindowUtils;
+
+ return bindDOMWindowUtils(aWindow);
+ },
+
+ removeExpectedCrashDumpFiles: function(aExpectingProcessCrash) {
+ var success = true;
+ if (aExpectingProcessCrash) {
+ var message = {
+ op: "delete-crash-dump-files",
+ filenames: this._encounteredCrashDumpFiles
+ };
+ if (!this._sendSyncMessage("SPProcessCrashService", message)[0]) {
+ success = false;
+ }
+ }
+ this._encounteredCrashDumpFiles.length = 0;
+ return success;
+ },
+
+ findUnexpectedCrashDumpFiles: function() {
+ var self = this;
+ var message = {
+ op: "find-crash-dump-files",
+ crashDumpFilesToIgnore: this._unexpectedCrashDumpFiles
+ };
+ var crashDumpFiles = this._sendSyncMessage("SPProcessCrashService", message)[0];
+ crashDumpFiles.forEach(function(aFilename) {
+ self._unexpectedCrashDumpFiles[aFilename] = true;
+ });
+ return crashDumpFiles;
+ },
+
+ _delayCallbackTwice: function(callback) {
+ function delayedCallback() {
+ function delayAgain() {
+ content.window.setTimeout(callback, 0);
+ }
+ content.window.setTimeout(delayAgain, 0);
+ }
+ return delayedCallback;
+ },
+
+ /* apply permissions to the system and when the test case is finished (SimpleTest.finish())
+ we will revert the permission back to the original.
+
+ inPermissions is an array of objects where each object has a type, action, context, ex:
+ [{'type': 'SystemXHR', 'allow': 1, 'context': document},
+ {'type': 'SystemXHR', 'allow': Ci.nsIPermissionManager.PROMPT_ACTION, 'context': document}]
+
+ Allow can be a boolean value of true/false or ALLOW_ACTION/DENY_ACTION/PROMPT_ACTION/UNKNOWN_ACTION
+ */
+ pushPermissions: function(inPermissions, callback) {
+ var pendingPermissions = [];
+ var cleanupPermissions = [];
+
+ for (var p in inPermissions) {
+ var permission = inPermissions[p];
+ var originalValue = Ci.nsIPermissionManager.UNKNOWN_ACTION;
+ if (this.testPermission(permission.type, Ci.nsIPermissionManager.ALLOW_ACTION, permission.context)) {
+ originalValue = Ci.nsIPermissionManager.ALLOW_ACTION;
+ } else if (this.testPermission(permission.type, Ci.nsIPermissionManager.DENY_ACTION, permission.context)) {
+ originalValue = Ci.nsIPermissionManager.DENY_ACTION;
+ } else if (this.testPermission(permission.type, Ci.nsIPermissionManager.PROMPT_ACTION, permission.context)) {
+ originalValue = Ci.nsIPermissionManager.PROMPT_ACTION;
+ } else if (this.testPermission(permission.type, Ci.nsICookiePermission.ACCESS_SESSION, permission.context)) {
+ originalValue = Ci.nsICookiePermission.ACCESS_SESSION;
+ } else if (this.testPermission(permission.type, Ci.nsICookiePermission.ACCESS_ALLOW_FIRST_PARTY_ONLY, permission.context)) {
+ originalValue = Ci.nsICookiePermission.ACCESS_ALLOW_FIRST_PARTY_ONLY;
+ } else if (this.testPermission(permission.type, Ci.nsICookiePermission.ACCESS_LIMIT_THIRD_PARTY, permission.context)) {
+ originalValue = Ci.nsICookiePermission.ACCESS_LIMIT_THIRD_PARTY;
+ }
+
+ let [url, appId, isInBrowserElement] = this._getInfoFromPermissionArg(permission.context);
+
+ let perm;
+ if (typeof permission.allow !== 'boolean') {
+ perm = permission.allow;
+ } else {
+ perm = permission.allow ? Ci.nsIPermissionManager.ALLOW_ACTION
+ : Ci.nsIPermissionManager.DENY_ACTION;
+ }
+
+ if (permission.remove == true)
+ perm = Ci.nsIPermissionManager.UNKNOWN_ACTION;
+
+ if (originalValue == perm) {
+ continue;
+ }
+
+ var todo = {'op': 'add', 'type': permission.type, 'permission': perm, 'value': perm, 'url': url, 'appId': appId, 'isInBrowserElement': isInBrowserElement};
+ if (permission.remove == true)
+ todo.op = 'remove';
+
+ pendingPermissions.push(todo);
+
+ /* Push original permissions value or clear into cleanup array */
+ var cleanupTodo = {'op': 'add', 'type': permission.type, 'permission': perm, 'value': perm, 'url': url, 'appId': appId, 'isInBrowserElement': isInBrowserElement};
+ if (originalValue == Ci.nsIPermissionManager.UNKNOWN_ACTION) {
+ cleanupTodo.op = 'remove';
+ } else {
+ cleanupTodo.value = originalValue;
+ cleanupTodo.permission = originalValue;
+ }
+ cleanupPermissions.push(cleanupTodo);
+ }
+
+ if (pendingPermissions.length > 0) {
+ // The callback needs to be delayed twice. One delay is because the pref
+ // service doesn't guarantee the order it calls its observers in, so it
+ // may notify the observer holding the callback before the other
+ // observers have been notified and given a chance to make the changes
+ // that the callback checks for. The second delay is because pref
+ // observers often defer making their changes by posting an event to the
+ // event loop.
+ this._permissionsUndoStack.push(cleanupPermissions);
+ this._pendingPermissions.push([pendingPermissions,
+ this._delayCallbackTwice(callback)]);
+ this._applyPermissions();
+ } else {
+ content.window.setTimeout(callback, 0);
+ }
+ },
+
+ popPermissions: function(callback) {
+ if (this._permissionsUndoStack.length > 0) {
+ // See pushPermissions comment regarding delay.
+ let cb = callback ? this._delayCallbackTwice(callback) : null;
+ /* Each pop from the stack will yield an object {op/type/permission/value/url/appid/isInBrowserElement} or null */
+ this._pendingPermissions.push([this._permissionsUndoStack.pop(), cb]);
+ this._applyPermissions();
+ } else {
+ content.window.setTimeout(callback, 0);
+ }
+ },
+
+ flushPermissions: function(callback) {
+ while (this._permissionsUndoStack.length > 1)
+ this.popPermissions(null);
+
+ this.popPermissions(callback);
+ },
+
+
+ _permissionObserver: {
+ _lastPermission: {},
+ _callBack: null,
+ _nextCallback: null,
+
+ observe: function (aSubject, aTopic, aData)
+ {
+ if (aTopic == "perm-changed") {
+ var permission = aSubject.QueryInterface(Ci.nsIPermission);
+ if (permission.type == this._lastPermission.type) {
+ var os = Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService);
+ os.removeObserver(this, "perm-changed");
+ content.window.setTimeout(this._callback, 0);
+ content.window.setTimeout(this._nextCallback, 0);
+ }
+ }
+ }
+ },
+
+ /*
+ Iterate through one atomic set of permissions actions and perform allow/deny as appropriate.
+ All actions performed must modify the relevant permission.
+ */
+ _applyPermissions: function() {
+ if (this._applyingPermissions || this._pendingPermissions.length <= 0) {
+ return;
+ }
+
+ /* Set lock and get prefs from the _pendingPrefs queue */
+ this._applyingPermissions = true;
+ var transaction = this._pendingPermissions.shift();
+ var pendingActions = transaction[0];
+ var callback = transaction[1];
+ var lastPermission = pendingActions[pendingActions.length-1];
+
+ var self = this;
+ var os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
+ this._permissionObserver._lastPermission = lastPermission;
+ this._permissionObserver._callback = callback;
+ this._permissionObserver._nextCallback = function () {
+ self._applyingPermissions = false;
+ // Now apply any permissions that may have been queued while we were applying
+ self._applyPermissions();
+ }
+
+ os.addObserver(this._permissionObserver, "perm-changed", false);
+
+ for (var idx in pendingActions) {
+ var perm = pendingActions[idx];
+ this._sendSyncMessage('SPPermissionManager', perm)[0];
+ }
+ },
+
+ /*
+ * Take in a list of pref changes to make, and invoke |callback| once those
+ * changes have taken effect. When the test finishes, these changes are
+ * reverted.
+ *
+ * |inPrefs| must be an object with up to two properties: "set" and "clear".
+ * pushPrefEnv will set prefs as indicated in |inPrefs.set| and will unset
+ * the prefs indicated in |inPrefs.clear|.
+ *
+ * For example, you might pass |inPrefs| as:
+ *
+ * inPrefs = {'set': [['foo.bar', 2], ['magic.pref', 'baz']],
+ * 'clear': [['clear.this'], ['also.this']] };
+ *
+ * Notice that |set| and |clear| are both an array of arrays. In |set|, each
+ * of the inner arrays must have the form [pref_name, value] or [pref_name,
+ * value, iid]. (The latter form is used for prefs with "complex" values.)
+ *
+ * In |clear|, each inner array should have the form [pref_name].
+ *
+ * If you set the same pref more than once (or both set and clear a pref),
+ * the behavior of this method is undefined.
+ *
+ * (Implementation note: _prefEnvUndoStack is a stack of values to revert to,
+ * not values which have been set!)
+ *
+ * TODO: complex values for original cleanup?
+ *
+ */
+ pushPrefEnv: function(inPrefs, callback) {
+ var prefs = Components.classes["@mozilla.org/preferences-service;1"].
+ getService(Components.interfaces.nsIPrefBranch);
+
+ var pref_string = [];
+ pref_string[prefs.PREF_INT] = "INT";
+ pref_string[prefs.PREF_BOOL] = "BOOL";
+ pref_string[prefs.PREF_STRING] = "CHAR";
+
+ var pendingActions = [];
+ var cleanupActions = [];
+
+ for (var action in inPrefs) { /* set|clear */
+ for (var idx in inPrefs[action]) {
+ var aPref = inPrefs[action][idx];
+ var prefName = aPref[0];
+ var prefValue = null;
+ var prefIid = null;
+ var prefType = prefs.PREF_INVALID;
+ var originalValue = null;
+
+ if (aPref.length == 3) {
+ prefValue = aPref[1];
+ prefIid = aPref[2];
+ } else if (aPref.length == 2) {
+ prefValue = aPref[1];
+ }
+
+ /* If pref is not found or invalid it doesn't exist. */
+ if (prefs.getPrefType(prefName) != prefs.PREF_INVALID) {
+ prefType = pref_string[prefs.getPrefType(prefName)];
+ if ((prefs.prefHasUserValue(prefName) && action == 'clear') ||
+ (action == 'set'))
+ originalValue = this._getPref(prefName, prefType);
+ } else if (action == 'set') {
+ /* prefName doesn't exist, so 'clear' is pointless */
+ if (aPref.length == 3) {
+ prefType = "COMPLEX";
+ } else if (aPref.length == 2) {
+ if (typeof(prefValue) == "boolean")
+ prefType = "BOOL";
+ else if (typeof(prefValue) == "number")
+ prefType = "INT";
+ else if (typeof(prefValue) == "string")
+ prefType = "CHAR";
+ }
+ }
+
+ /* PREF_INVALID: A non existing pref which we are clearing or invalid values for a set */
+ if (prefType == prefs.PREF_INVALID)
+ continue;
+
+ /* We are not going to set a pref if the value is the same */
+ if (originalValue == prefValue)
+ continue;
+
+ pendingActions.push({'action': action, 'type': prefType, 'name': prefName, 'value': prefValue, 'Iid': prefIid});
+
+ /* Push original preference value or clear into cleanup array */
+ var cleanupTodo = {'action': action, 'type': prefType, 'name': prefName, 'value': originalValue, 'Iid': prefIid};
+ if (originalValue == null) {
+ cleanupTodo.action = 'clear';
+ } else {
+ cleanupTodo.action = 'set';
+ }
+ cleanupActions.push(cleanupTodo);
+ }
+ }
+
+ if (pendingActions.length > 0) {
+ // The callback needs to be delayed twice. One delay is because the pref
+ // service doesn't guarantee the order it calls its observers in, so it
+ // may notify the observer holding the callback before the other
+ // observers have been notified and given a chance to make the changes
+ // that the callback checks for. The second delay is because pref
+ // observers often defer making their changes by posting an event to the
+ // event loop.
+ this._prefEnvUndoStack.push(cleanupActions);
+ this._pendingPrefs.push([pendingActions,
+ this._delayCallbackTwice(callback)]);
+ this._applyPrefs();
+ } else {
+ content.window.setTimeout(callback, 0);
+ }
+ },
+
+ popPrefEnv: function(callback) {
+ if (this._prefEnvUndoStack.length > 0) {
+ // See pushPrefEnv comment regarding delay.
+ let cb = callback ? this._delayCallbackTwice(callback) : null;
+ /* Each pop will have a valid block of preferences */
+ this._pendingPrefs.push([this._prefEnvUndoStack.pop(), cb]);
+ this._applyPrefs();
+ } else {
+ content.window.setTimeout(callback, 0);
+ }
+ },
+
+ flushPrefEnv: function(callback) {
+ while (this._prefEnvUndoStack.length > 1)
+ this.popPrefEnv(null);
+
+ this.popPrefEnv(callback);
+ },
+
+ /*
+ Iterate through one atomic set of pref actions and perform sets/clears as appropriate.
+ All actions performed must modify the relevant pref.
+ */
+ _applyPrefs: function() {
+ if (this._applyingPrefs || this._pendingPrefs.length <= 0) {
+ return;
+ }
+
+ /* Set lock and get prefs from the _pendingPrefs queue */
+ this._applyingPrefs = true;
+ var transaction = this._pendingPrefs.shift();
+ var pendingActions = transaction[0];
+ var callback = transaction[1];
+
+ var lastPref = pendingActions[pendingActions.length-1];
+
+ var pb = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+ var self = this;
+ pb.addObserver(lastPref.name, function prefObs(subject, topic, data) {
+ pb.removeObserver(lastPref.name, prefObs);
+
+ content.window.setTimeout(callback, 0);
+ content.window.setTimeout(function () {
+ self._applyingPrefs = false;
+ // Now apply any prefs that may have been queued while we were applying
+ self._applyPrefs();
+ }, 0);
+ }, false);
+
+ for (var idx in pendingActions) {
+ var pref = pendingActions[idx];
+ if (pref.action == 'set') {
+ this._setPref(pref.name, pref.type, pref.value, pref.Iid);
+ } else if (pref.action == 'clear') {
+ this.clearUserPref(pref.name);
+ }
+ }
+ },
+
+ // Disables the app install prompt for the duration of this test. There is
+ // no need to re-enable the prompt at the end of the test.
+ //
+ // The provided callback is invoked once the prompt is disabled.
+ autoConfirmAppInstall: function(cb) {
+ this.pushPrefEnv({set: [['dom.mozApps.auto_confirm_install', true]]}, cb);
+ },
+
+ // Allow tests to disable the per platform app validity checks so we can
+ // test higher level WebApp functionality without full platform support.
+ setAllAppsLaunchable: function(launchable) {
+ this._sendSyncMessage("SPWebAppService", {
+ op: "set-launchable",
+ launchable: launchable
+ });
+ },
+
+ // Restore the launchable property to its default value.
+ flushAllAppsLaunchable: function() {
+ this._sendSyncMessage("SPWebAppService", {
+ op: "set-launchable",
+ launchable: false
+ });
+ },
+
+ _proxiedObservers: {
+ "specialpowers-http-notify-request": function(aMessage) {
+ let uri = aMessage.json.uri;
+ Services.obs.notifyObservers(null, "specialpowers-http-notify-request", uri);
+ },
+ },
+
+ _addObserverProxy: function(notification) {
+ if (notification in this._proxiedObservers) {
+ this._addMessageListener(notification, this._proxiedObservers[notification]);
+ }
+ },
+
+ _removeObserverProxy: function(notification) {
+ if (notification in this._proxiedObservers) {
+ this._removeMessageListener(notification, this._proxiedObservers[notification]);
+ }
+ },
+
+ addObserver: function(obs, notification, weak) {
+ this._addObserverProxy(notification);
+ if (typeof obs == 'object' && obs.observe.name != 'SpecialPowersCallbackWrapper')
+ obs.observe = wrapCallback(obs.observe);
+ var obsvc = Cc['@mozilla.org/observer-service;1']
+ .getService(Ci.nsIObserverService);
+ obsvc.addObserver(obs, notification, weak);
+ },
+ removeObserver: function(obs, notification) {
+ this._removeObserverProxy(notification);
+ var obsvc = Cc['@mozilla.org/observer-service;1']
+ .getService(Ci.nsIObserverService);
+ obsvc.removeObserver(obs, notification);
+ },
+ notifyObservers: function(subject, topic, data) {
+ var obsvc = Cc['@mozilla.org/observer-service;1']
+ .getService(Ci.nsIObserverService);
+ obsvc.notifyObservers(subject, topic, data);
+ },
+
+ can_QI: function(obj) {
+ return obj.QueryInterface !== undefined;
+ },
+ do_QueryInterface: function(obj, iface) {
+ return obj.QueryInterface(Ci[iface]);
+ },
+
+ call_Instanceof: function (obj1, obj2) {
+ obj1=unwrapIfWrapped(obj1);
+ obj2=unwrapIfWrapped(obj2);
+ return obj1 instanceof obj2;
+ },
+
+ // Returns a privileged getter from an object. GetOwnPropertyDescriptor does
+ // not work here because xray wrappers don't properly implement it.
+ //
+ // This terribleness is used by content/base/test/test_object.html because
+ //