testing/specialpowers/content/specialpowersAPI.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial