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