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: // and tags will spawn plugins if their prototype is touched, michael@0: // so we need to get and cache the getter of |hasRunningPlugin| if we want to michael@0: // call it without paradoxically spawning the plugin. michael@0: do_lookupGetter: function(obj, name) { michael@0: return Object.prototype.__lookupGetter__.call(obj, name); michael@0: }, michael@0: michael@0: // Mimic the get*Pref API michael@0: getBoolPref: function(aPrefName) { michael@0: return (this._getPref(aPrefName, 'BOOL')); michael@0: }, michael@0: getIntPref: function(aPrefName) { michael@0: return (this._getPref(aPrefName, 'INT')); michael@0: }, michael@0: getCharPref: function(aPrefName) { michael@0: return (this._getPref(aPrefName, 'CHAR')); michael@0: }, michael@0: getComplexValue: function(aPrefName, aIid) { michael@0: return (this._getPref(aPrefName, 'COMPLEX', aIid)); michael@0: }, michael@0: michael@0: // Mimic the set*Pref API michael@0: setBoolPref: function(aPrefName, aValue) { michael@0: return (this._setPref(aPrefName, 'BOOL', aValue)); michael@0: }, michael@0: setIntPref: function(aPrefName, aValue) { michael@0: return (this._setPref(aPrefName, 'INT', aValue)); michael@0: }, michael@0: setCharPref: function(aPrefName, aValue) { michael@0: return (this._setPref(aPrefName, 'CHAR', aValue)); michael@0: }, michael@0: setComplexValue: function(aPrefName, aIid, aValue) { michael@0: return (this._setPref(aPrefName, 'COMPLEX', aValue, aIid)); michael@0: }, michael@0: michael@0: // Mimic the clearUserPref API michael@0: clearUserPref: function(aPrefName) { michael@0: var msg = {'op':'clear', 'prefName': aPrefName, 'prefType': ""}; michael@0: this._sendSyncMessage('SPPrefService', msg); michael@0: }, michael@0: michael@0: // Private pref functions to communicate to chrome michael@0: _getPref: function(aPrefName, aPrefType, aIid) { michael@0: var msg = {}; michael@0: if (aIid) { michael@0: // Overloading prefValue to handle complex prefs michael@0: msg = {'op':'get', 'prefName': aPrefName, 'prefType':aPrefType, 'prefValue':[aIid]}; michael@0: } else { michael@0: msg = {'op':'get', 'prefName': aPrefName,'prefType': aPrefType}; michael@0: } michael@0: var val = this._sendSyncMessage('SPPrefService', msg); michael@0: michael@0: if (val == null || val[0] == null) michael@0: throw "Error getting pref"; michael@0: return val[0]; michael@0: }, michael@0: _setPref: function(aPrefName, aPrefType, aValue, aIid) { michael@0: var msg = {}; michael@0: if (aIid) { michael@0: msg = {'op':'set','prefName':aPrefName, 'prefType': aPrefType, 'prefValue': [aIid,aValue]}; michael@0: } else { michael@0: msg = {'op':'set', 'prefName': aPrefName, 'prefType': aPrefType, 'prefValue': aValue}; michael@0: } michael@0: return(this._sendSyncMessage('SPPrefService', msg)[0]); michael@0: }, michael@0: michael@0: _getDocShell: function(window) { michael@0: return window.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIWebNavigation) michael@0: .QueryInterface(Ci.nsIDocShell); michael@0: }, michael@0: _getMUDV: function(window) { michael@0: return this._getDocShell(window).contentViewer michael@0: .QueryInterface(Ci.nsIMarkupDocumentViewer); michael@0: }, michael@0: //XXX: these APIs really ought to be removed, they're not e10s-safe. michael@0: // (also they're pretty Firefox-specific) michael@0: _getTopChromeWindow: function(window) { michael@0: return window.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIWebNavigation) michael@0: .QueryInterface(Ci.nsIDocShellTreeItem) michael@0: .rootTreeItem michael@0: .QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIDOMWindow) michael@0: .QueryInterface(Ci.nsIDOMChromeWindow); michael@0: }, michael@0: _getAutoCompletePopup: function(window) { michael@0: return this._getTopChromeWindow(window).document michael@0: .getElementById("PopupAutoComplete"); michael@0: }, michael@0: addAutoCompletePopupEventListener: function(window, eventname, listener) { michael@0: this._getAutoCompletePopup(window).addEventListener(eventname, michael@0: listener, michael@0: false); michael@0: }, michael@0: removeAutoCompletePopupEventListener: function(window, eventname, listener) { michael@0: this._getAutoCompletePopup(window).removeEventListener(eventname, michael@0: listener, michael@0: false); michael@0: }, michael@0: get formHistory() { michael@0: let tmp = {}; michael@0: Cu.import("resource://gre/modules/FormHistory.jsm", tmp); michael@0: return wrapPrivileged(tmp.FormHistory); michael@0: }, michael@0: getFormFillController: function(window) { michael@0: return Components.classes["@mozilla.org/satchel/form-fill-controller;1"] michael@0: .getService(Components.interfaces.nsIFormFillController); michael@0: }, michael@0: attachFormFillControllerTo: function(window) { michael@0: this.getFormFillController() michael@0: .attachToBrowser(this._getDocShell(window), michael@0: this._getAutoCompletePopup(window)); michael@0: }, michael@0: detachFormFillControllerFrom: function(window) { michael@0: this.getFormFillController().detachFromBrowser(this._getDocShell(window)); michael@0: }, michael@0: isBackButtonEnabled: function(window) { michael@0: return !this._getTopChromeWindow(window).document michael@0: .getElementById("Browser:Back") michael@0: .hasAttribute("disabled"); michael@0: }, michael@0: //XXX end of problematic APIs michael@0: michael@0: addChromeEventListener: function(type, listener, capture, allowUntrusted) { michael@0: addEventListener(type, listener, capture, allowUntrusted); michael@0: }, michael@0: removeChromeEventListener: function(type, listener, capture) { michael@0: removeEventListener(type, listener, capture); michael@0: }, michael@0: michael@0: // Note: each call to registerConsoleListener MUST be paired with a michael@0: // call to postConsoleSentinel; when the callback receives the michael@0: // sentinel it will unregister itself (_after_ calling the michael@0: // callback). SimpleTest.expectConsoleMessages does this for you. michael@0: // If you register more than one console listener, a call to michael@0: // postConsoleSentinel will zap all of them. michael@0: registerConsoleListener: function(callback) { michael@0: let listener = new SPConsoleListener(callback); michael@0: Services.console.registerListener(listener); michael@0: }, michael@0: postConsoleSentinel: function() { michael@0: Services.console.logStringMessage("SENTINEL"); michael@0: }, michael@0: resetConsole: function() { michael@0: Services.console.reset(); michael@0: }, michael@0: michael@0: getMaxLineBoxWidth: function(window) { michael@0: return this._getMUDV(window).maxLineBoxWidth; michael@0: }, michael@0: michael@0: setMaxLineBoxWidth: function(window, width) { michael@0: this._getMUDV(window).changeMaxLineBoxWidth(width); michael@0: }, michael@0: michael@0: getFullZoom: function(window) { michael@0: return this._getMUDV(window).fullZoom; michael@0: }, michael@0: setFullZoom: function(window, zoom) { michael@0: this._getMUDV(window).fullZoom = zoom; michael@0: }, michael@0: getTextZoom: function(window) { michael@0: return this._getMUDV(window).textZoom; michael@0: }, michael@0: setTextZoom: function(window, zoom) { michael@0: this._getMUDV(window).textZoom = zoom; michael@0: }, michael@0: michael@0: emulateMedium: function(window, mediaType) { michael@0: this._getMUDV(window).emulateMedium(mediaType); michael@0: }, michael@0: stopEmulatingMedium: function(window) { michael@0: this._getMUDV(window).stopEmulatingMedium(); michael@0: }, michael@0: michael@0: snapshotWindowWithOptions: function (win, rect, bgcolor, options) { michael@0: var el = this.window.get().document.createElementNS("http://www.w3.org/1999/xhtml", "canvas"); michael@0: if (rect === undefined) { michael@0: rect = { top: win.scrollY, left: win.scrollX, michael@0: width: win.innerWidth, height: win.innerHeight }; michael@0: } michael@0: if (bgcolor === undefined) { michael@0: bgcolor = "rgb(255,255,255)"; michael@0: } michael@0: if (options === undefined) { michael@0: options = { }; michael@0: } michael@0: michael@0: el.width = rect.width; michael@0: el.height = rect.height; michael@0: var ctx = el.getContext("2d"); michael@0: var flags = 0; michael@0: michael@0: for (var option in options) { michael@0: flags |= options[option] && ctx[option]; michael@0: } michael@0: michael@0: ctx.drawWindow(win, michael@0: rect.left, rect.top, rect.width, rect.height, michael@0: bgcolor, michael@0: flags); michael@0: return el; michael@0: }, michael@0: michael@0: snapshotWindow: function (win, withCaret, rect, bgcolor) { michael@0: return this.snapshotWindowWithOptions(win, rect, bgcolor, michael@0: { DRAWWINDOW_DRAW_CARET: withCaret }); michael@0: }, michael@0: michael@0: snapshotRect: function (win, rect, bgcolor) { michael@0: return this.snapshotWindowWithOptions(win, rect, bgcolor); michael@0: }, michael@0: michael@0: gc: function() { michael@0: this.DOMWindowUtils.garbageCollect(); michael@0: }, michael@0: michael@0: forceGC: function() { michael@0: Cu.forceGC(); michael@0: }, michael@0: michael@0: forceCC: function() { michael@0: Cu.forceCC(); michael@0: }, michael@0: michael@0: // Due to various dependencies between JS objects and C++ objects, an ordinary michael@0: // forceGC doesn't necessarily clear all unused objects, thus the GC and CC michael@0: // needs to run several times and when no other JS is running. michael@0: // The current number of iterations has been determined according to massive michael@0: // cross platform testing. michael@0: exactGC: function(win, callback) { michael@0: var self = this; michael@0: let count = 0; michael@0: michael@0: function doPreciseGCandCC() { michael@0: function scheduledGCCallback() { michael@0: self.getDOMWindowUtils(win).cycleCollect(); michael@0: michael@0: if (++count < 2) { michael@0: doPreciseGCandCC(); michael@0: } else { michael@0: callback(); michael@0: } michael@0: } michael@0: michael@0: Cu.schedulePreciseGC(scheduledGCCallback); michael@0: } michael@0: michael@0: doPreciseGCandCC(); michael@0: }, michael@0: michael@0: setGCZeal: function(zeal) { michael@0: Cu.setGCZeal(zeal); michael@0: }, michael@0: michael@0: isMainProcess: function() { michael@0: try { michael@0: return Cc["@mozilla.org/xre/app-info;1"]. michael@0: getService(Ci.nsIXULRuntime). michael@0: processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT; michael@0: } catch (e) { } michael@0: return true; michael@0: }, michael@0: michael@0: _xpcomabi: null, michael@0: michael@0: get XPCOMABI() { michael@0: if (this._xpcomabi != null) michael@0: return this._xpcomabi; michael@0: michael@0: var xulRuntime = Cc["@mozilla.org/xre/app-info;1"] michael@0: .getService(Components.interfaces.nsIXULAppInfo) michael@0: .QueryInterface(Components.interfaces.nsIXULRuntime); michael@0: michael@0: this._xpcomabi = xulRuntime.XPCOMABI; michael@0: return this._xpcomabi; michael@0: }, michael@0: michael@0: // The optional aWin parameter allows the caller to specify a given window in michael@0: // whose scope the runnable should be dispatched. If aFun throws, the michael@0: // exception will be reported to aWin. michael@0: executeSoon: function(aFun, aWin) { michael@0: // Create the runnable in the scope of aWin to avoid running into COWs. michael@0: var runnable = {}; michael@0: if (aWin) michael@0: runnable = Cu.createObjectIn(aWin); michael@0: runnable.run = aFun; michael@0: Cu.dispatch(runnable, aWin); michael@0: }, michael@0: michael@0: _os: null, michael@0: michael@0: get OS() { michael@0: if (this._os != null) michael@0: return this._os; michael@0: michael@0: var xulRuntime = Cc["@mozilla.org/xre/app-info;1"] michael@0: .getService(Components.interfaces.nsIXULAppInfo) michael@0: .QueryInterface(Components.interfaces.nsIXULRuntime); michael@0: michael@0: this._os = xulRuntime.OS; michael@0: return this._os; michael@0: }, michael@0: michael@0: addSystemEventListener: function(target, type, listener, useCapture) { michael@0: Cc["@mozilla.org/eventlistenerservice;1"]. michael@0: getService(Ci.nsIEventListenerService). michael@0: addSystemEventListener(target, type, listener, useCapture); michael@0: }, michael@0: removeSystemEventListener: function(target, type, listener, useCapture) { michael@0: Cc["@mozilla.org/eventlistenerservice;1"]. michael@0: getService(Ci.nsIEventListenerService). michael@0: removeSystemEventListener(target, type, listener, useCapture); michael@0: }, michael@0: michael@0: getDOMRequestService: function() { michael@0: var serv = Cc["@mozilla.org/dom/dom-request-service;1"]. michael@0: getService(Ci.nsIDOMRequestService); michael@0: var res = { __exposedProps__: {} }; michael@0: var props = ["createRequest", "createCursor", "fireError", "fireSuccess", michael@0: "fireDone", "fireDetailedError"]; michael@0: for (i in props) { michael@0: let prop = props[i]; michael@0: res[prop] = function() { return serv[prop].apply(serv, arguments) }; michael@0: res.__exposedProps__[prop] = "r"; michael@0: } michael@0: return res; michael@0: }, michael@0: michael@0: setLogFile: function(path) { michael@0: this._mfl = new MozillaFileLogger(path); michael@0: }, michael@0: michael@0: log: function(data) { michael@0: this._mfl.log(data); michael@0: }, michael@0: michael@0: closeLogFile: function() { michael@0: this._mfl.close(); michael@0: }, michael@0: michael@0: addCategoryEntry: function(category, entry, value, persists, replace) { michael@0: Components.classes["@mozilla.org/categorymanager;1"]. michael@0: getService(Components.interfaces.nsICategoryManager). michael@0: addCategoryEntry(category, entry, value, persists, replace); michael@0: }, michael@0: michael@0: deleteCategoryEntry: function(category, entry, persists) { michael@0: Components.classes["@mozilla.org/categorymanager;1"]. michael@0: getService(Components.interfaces.nsICategoryManager). michael@0: deleteCategoryEntry(category, entry, persists); michael@0: }, michael@0: michael@0: copyString: function(str, doc) { michael@0: Components.classes["@mozilla.org/widget/clipboardhelper;1"]. michael@0: getService(Components.interfaces.nsIClipboardHelper). michael@0: copyString(str, doc); michael@0: }, michael@0: michael@0: openDialog: function(win, args) { michael@0: return win.openDialog.apply(win, args); michael@0: }, michael@0: michael@0: // :jdm gets credit for this. ex: getPrivilegedProps(window, 'location.href'); michael@0: getPrivilegedProps: function(obj, props) { michael@0: var parts = props.split('.'); michael@0: michael@0: for (var i = 0; i < parts.length; i++) { michael@0: var p = parts[i]; michael@0: if (obj[p]) { michael@0: obj = obj[p]; michael@0: } else { michael@0: return null; michael@0: } michael@0: } michael@0: return obj; michael@0: }, michael@0: michael@0: get focusManager() { michael@0: if (this._fm != null) michael@0: return this._fm; michael@0: michael@0: this._fm = Components.classes["@mozilla.org/focus-manager;1"]. michael@0: getService(Components.interfaces.nsIFocusManager); michael@0: michael@0: return this._fm; michael@0: }, michael@0: michael@0: getFocusedElementForWindow: function(targetWindow, aDeep, childTargetWindow) { michael@0: return this.focusManager.getFocusedElementForWindow(targetWindow, aDeep, childTargetWindow); michael@0: }, michael@0: michael@0: activeWindow: function() { michael@0: return this.focusManager.activeWindow; michael@0: }, michael@0: michael@0: focusedWindow: function() { michael@0: return this.focusManager.focusedWindow; michael@0: }, michael@0: michael@0: focus: function(aWindow) { michael@0: // This is called inside TestRunner._makeIframe without aWindow, because of assertions in oop mochitests michael@0: // With aWindow, it is called in SimpleTest.waitForFocus to allow popup window opener focus switching michael@0: if (aWindow) michael@0: aWindow.focus(); michael@0: sendAsyncMessage("SpecialPowers.Focus", {}); michael@0: }, michael@0: michael@0: getClipboardData: function(flavor, whichClipboard) { michael@0: if (this._cb == null) michael@0: this._cb = Components.classes["@mozilla.org/widget/clipboard;1"]. michael@0: getService(Components.interfaces.nsIClipboard); michael@0: if (whichClipboard === undefined) michael@0: whichClipboard = this._cb.kGlobalClipboard; michael@0: michael@0: var xferable = Components.classes["@mozilla.org/widget/transferable;1"]. michael@0: createInstance(Components.interfaces.nsITransferable); michael@0: xferable.init(this._getDocShell(content.window) michael@0: .QueryInterface(Components.interfaces.nsILoadContext)); michael@0: xferable.addDataFlavor(flavor); michael@0: this._cb.getData(xferable, whichClipboard); michael@0: var data = {}; michael@0: try { michael@0: xferable.getTransferData(flavor, data, {}); michael@0: } catch (e) {} michael@0: data = data.value || null; michael@0: if (data == null) michael@0: return ""; michael@0: michael@0: return data.QueryInterface(Components.interfaces.nsISupportsString).data; michael@0: }, michael@0: michael@0: clipboardCopyString: function(preExpectedVal, doc) { michael@0: var cbHelperSvc = Components.classes["@mozilla.org/widget/clipboardhelper;1"]. michael@0: getService(Components.interfaces.nsIClipboardHelper); michael@0: cbHelperSvc.copyString(preExpectedVal, doc); michael@0: }, michael@0: michael@0: supportsSelectionClipboard: function() { michael@0: if (this._cb == null) { michael@0: this._cb = Components.classes["@mozilla.org/widget/clipboard;1"]. michael@0: getService(Components.interfaces.nsIClipboard); michael@0: } michael@0: return this._cb.supportsSelectionClipboard(); michael@0: }, michael@0: michael@0: swapFactoryRegistration: function(cid, contractID, newFactory, oldFactory) { michael@0: var componentRegistrar = Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar); michael@0: michael@0: var unregisterFactory = newFactory; michael@0: var registerFactory = oldFactory; michael@0: michael@0: if (cid == null) { michael@0: if (contractID != null) { michael@0: cid = componentRegistrar.contractIDToCID(contractID); michael@0: oldFactory = Components.manager.getClassObject(Components.classes[contractID], michael@0: Components.interfaces.nsIFactory); michael@0: } else { michael@0: return {'error': "trying to register a new contract ID: Missing contractID"}; michael@0: } michael@0: michael@0: unregisterFactory = oldFactory; michael@0: registerFactory = newFactory; michael@0: } michael@0: componentRegistrar.unregisterFactory(cid, michael@0: unregisterFactory); michael@0: michael@0: // Restore the original factory. michael@0: componentRegistrar.registerFactory(cid, michael@0: "", michael@0: contractID, michael@0: registerFactory); michael@0: return {'cid':cid, 'originalFactory':oldFactory}; michael@0: }, michael@0: michael@0: _getElement: function(aWindow, id) { michael@0: return ((typeof(id) == "string") ? michael@0: aWindow.document.getElementById(id) : id); michael@0: }, michael@0: michael@0: dispatchEvent: function(aWindow, target, event) { michael@0: var el = this._getElement(aWindow, target); michael@0: return el.dispatchEvent(event); michael@0: }, michael@0: michael@0: get isDebugBuild() { michael@0: delete this.isDebugBuild; michael@0: var debug = Cc["@mozilla.org/xpcom/debug;1"].getService(Ci.nsIDebug2); michael@0: return this.isDebugBuild = debug.isDebugBuild; michael@0: }, michael@0: assertionCount: function() { michael@0: var debugsvc = Cc['@mozilla.org/xpcom/debug;1'].getService(Ci.nsIDebug2); michael@0: return debugsvc.assertionCount; michael@0: }, michael@0: michael@0: /** michael@0: * Get the message manager associated with an