[Pkg-javascript-commits] [pdf.js] 01/56: Fix special powers add-on for firefox.
David Prévot
taffit at moszumanska.debian.org
Thu May 15 15:17:42 UTC 2014
This is an automated email from the git hooks/post-receive script.
taffit pushed a commit to branch master
in repository pdf.js.
commit da522d42d4f09e5914fa16f1c0cbb079bcd5f4cf
Author: Brendan Dahl <brendan.dahl at gmail.com>
Date: Fri Apr 11 10:40:15 2014 -0700
Fix special powers add-on for firefox.
---
.../special-powers at mozilla.org/chrome.manifest | 5 +-
.../chrome/specialpowers/content/MozillaLogger.js | 121 ++
.../content/SpecialPowersObserverAPI.js | 414 +++++
.../chrome/specialpowers/content/specialpowers.js | 376 +----
.../specialpowers/content/specialpowersAPI.js | 1744 ++++++++++++++++++++
.../chrome/specialpowers/modules/Assert.jsm | 442 +++++
.../specialpowers/modules/MockColorPicker.jsm | 125 ++
.../specialpowers/modules/MockFilePicker.jsm | 236 +++
.../specialpowers/modules/MockPermissionPrompt.jsm | 97 ++
.../components/SpecialPowersObserver.js | 303 ++--
test/resources/firefox/prefs.js | 1 +
11 files changed, 3346 insertions(+), 518 deletions(-)
diff --git a/test/resources/firefox/extensions/special-powers at mozilla.org/chrome.manifest b/test/resources/firefox/extensions/special-powers at mozilla.org/chrome.manifest
index 614f31c..cac9fd6 100644
--- a/test/resources/firefox/extensions/special-powers at mozilla.org/chrome.manifest
+++ b/test/resources/firefox/extensions/special-powers at mozilla.org/chrome.manifest
@@ -1,4 +1,5 @@
-content specialpowers chrome/specialpowers/content/
+category profile-after-change @mozilla.org/special-powers-observer;1 @mozilla.org/special-powers-observer;1
component {59a52458-13e0-4d93-9d85-a637344f29a1} components/SpecialPowersObserver.js
+content specialpowers chrome/specialpowers/content/
contract @mozilla.org/special-powers-observer;1 {59a52458-13e0-4d93-9d85-a637344f29a1}
-category profile-after-change @mozilla.org/special-powers-observer;1 @mozilla.org/special-powers-observer;1
+resource specialpowers chrome/specialpowers/modules/
diff --git a/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/content/MozillaLogger.js b/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/content/MozillaLogger.js
new file mode 100644
index 0000000..96ad8a2
--- /dev/null
+++ b/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/content/MozillaLogger.js
@@ -0,0 +1,121 @@
+/**
+ * MozillaLogger, a base class logger that just logs to stdout.
+ */
+
+function MozillaLogger(aPath) {
+}
+
+MozillaLogger.prototype = {
+
+ init : function(path) {},
+
+ getLogCallback : function() {
+ return function (msg) {
+ var data = msg.num + " " + msg.level + " " + msg.info.join(' ') + "\n";
+ dump(data);
+ }
+ },
+
+ log : function(msg) {
+ dump(msg);
+ },
+
+ close : function() {}
+};
+
+
+/**
+ * SpecialPowersLogger, inherits from MozillaLogger and utilizes SpecialPowers.
+ * intented to be used in content scripts to write to a file
+ */
+function SpecialPowersLogger(aPath) {
+ // Call the base constructor
+ MozillaLogger.call(this);
+ this.prototype = new MozillaLogger(aPath);
+ this.init(aPath);
+}
+
+SpecialPowersLogger.prototype = {
+ init : function (path) {
+ SpecialPowers.setLogFile(path);
+ },
+
+ getLogCallback : function () {
+ return function (msg) {
+ var data = msg.num + " " + msg.level + " " + msg.info.join(' ') + "\n";
+ SpecialPowers.log(data);
+
+ if (data.indexOf("SimpleTest FINISH") >= 0) {
+ SpecialPowers.closeLogFile();
+ }
+ }
+ },
+
+ log : function (msg) {
+ SpecialPowers.log(msg);
+ },
+
+ close : function () {
+ SpecialPowers.closeLogFile();
+ }
+};
+
+
+/**
+ * MozillaFileLogger, a log listener that can write to a local file.
+ * intended to be run from chrome space
+ */
+
+/** Init the file logger with the absolute path to the file.
+ It will create and append if the file already exists **/
+function MozillaFileLogger(aPath) {
+ // Call the base constructor
+ MozillaLogger.call(this);
+ this.prototype = new MozillaLogger(aPath);
+ this.init(aPath);
+}
+
+MozillaFileLogger.prototype = {
+
+ init : function (path) {
+ var PR_WRITE_ONLY = 0x02; // Open for writing only.
+ var PR_CREATE_FILE = 0x08;
+ var PR_APPEND = 0x10;
+ this._file = Components.classes["@mozilla.org/file/local;1"].
+ createInstance(Components.interfaces.nsILocalFile);
+ this._file.initWithPath(path);
+ this._foStream = Components.classes["@mozilla.org/network/file-output-stream;1"].
+ createInstance(Components.interfaces.nsIFileOutputStream);
+ this._foStream.init(this._file, PR_WRITE_ONLY | PR_CREATE_FILE | PR_APPEND,
+ 0664, 0);
+ },
+
+ getLogCallback : function() {
+ return function (msg) {
+ var data = msg.num + " " + msg.level + " " + msg.info.join(' ') + "\n";
+ if (MozillaFileLogger._foStream)
+ this._foStream.write(data, data.length);
+
+ if (data.indexOf("SimpleTest FINISH") >= 0) {
+ MozillaFileLogger.close();
+ }
+ }
+ },
+
+ log : function(msg) {
+ if (this._foStream)
+ this._foStream.write(msg, msg.length);
+ },
+
+ close : function() {
+ if(this._foStream)
+ this._foStream.close();
+
+ this._foStream = null;
+ this._file = null;
+ }
+};
+
+this.MozillaLogger = MozillaLogger;
+this.SpecialPowersLogger = SpecialPowersLogger;
+this.MozillaFileLogger = MozillaFileLogger;
diff --git a/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/content/SpecialPowersObserverAPI.js b/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/content/SpecialPowersObserverAPI.js
new file mode 100644
index 0000000..cb87113
--- /dev/null
+++ b/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/content/SpecialPowersObserverAPI.js
@@ -0,0 +1,414 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+if (typeof(Ci) == 'undefined') {
+ var Ci = Components.interfaces;
+}
+
+if (typeof(Cc) == 'undefined') {
+ var Cc = Components.classes;
+}
+
+/**
+ * Special Powers Exception - used to throw exceptions nicely
+ **/
+function SpecialPowersException(aMsg) {
+ this.message = aMsg;
+ this.name = "SpecialPowersException";
+}
+
+SpecialPowersException.prototype.toString = function() {
+ return this.name + ': "' + this.message + '"';
+};
+
+this.SpecialPowersObserverAPI = function SpecialPowersObserverAPI() {
+ this._crashDumpDir = null;
+ this._processCrashObserversRegistered = false;
+ this._chromeScriptListeners = [];
+}
+
+function parseKeyValuePairs(text) {
+ var lines = text.split('\n');
+ var data = {};
+ for (let i = 0; i < lines.length; i++) {
+ if (lines[i] == '')
+ continue;
+
+ // can't just .split() because the value might contain = characters
+ let eq = lines[i].indexOf('=');
+ if (eq != -1) {
+ let [key, value] = [lines[i].substring(0, eq),
+ lines[i].substring(eq + 1)];
+ if (key && value)
+ data[key] = value.replace(/\\n/g, "\n").replace(/\\\\/g, "\\");
+ }
+ }
+ return data;
+}
+
+function parseKeyValuePairsFromFile(file) {
+ var fstream = Cc["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Ci.nsIFileInputStream);
+ fstream.init(file, -1, 0, 0);
+ var is = Cc["@mozilla.org/intl/converter-input-stream;1"].
+ createInstance(Ci.nsIConverterInputStream);
+ is.init(fstream, "UTF-8", 1024, Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
+ var str = {};
+ var contents = '';
+ while (is.readString(4096, str) != 0) {
+ contents += str.value;
+ }
+ is.close();
+ fstream.close();
+ return parseKeyValuePairs(contents);
+}
+
+SpecialPowersObserverAPI.prototype = {
+
+ _observe: function(aSubject, aTopic, aData) {
+ function addDumpIDToMessage(propertyName) {
+ var id = aSubject.getPropertyAsAString(propertyName);
+ if (id) {
+ message.dumpIDs.push({id: id, extension: "dmp"});
+ message.dumpIDs.push({id: id, extension: "extra"});
+ }
+ }
+
+ switch(aTopic) {
+ case "plugin-crashed":
+ case "ipc:content-shutdown":
+ var message = { type: "crash-observed", dumpIDs: [] };
+ aSubject = aSubject.QueryInterface(Ci.nsIPropertyBag2);
+ if (aTopic == "plugin-crashed") {
+ addDumpIDToMessage("pluginDumpID");
+ addDumpIDToMessage("browserDumpID");
+
+ let pluginID = aSubject.getPropertyAsAString("pluginDumpID");
+ let extra = this._getExtraData(pluginID);
+ if (extra && ("additional_minidumps" in extra)) {
+ let dumpNames = extra.additional_minidumps.split(',');
+ for (let name of dumpNames) {
+ message.dumpIDs.push({id: pluginID + "-" + name, extension: "dmp"});
+ }
+ }
+ } else { // ipc:content-shutdown
+ addDumpIDToMessage("dumpID");
+ }
+ this._sendAsyncMessage("SPProcessCrashService", message);
+ break;
+ }
+ },
+
+ _getCrashDumpDir: function() {
+ if (!this._crashDumpDir) {
+ this._crashDumpDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ this._crashDumpDir.append("minidumps");
+ }
+ return this._crashDumpDir;
+ },
+
+ _getExtraData: function(dumpId) {
+ let extraFile = this._getCrashDumpDir().clone();
+ extraFile.append(dumpId + ".extra");
+ if (!extraFile.exists()) {
+ return null;
+ }
+ return parseKeyValuePairsFromFile(extraFile);
+ },
+
+ _deleteCrashDumpFiles: function(aFilenames) {
+ var crashDumpDir = this._getCrashDumpDir();
+ if (!crashDumpDir.exists()) {
+ return false;
+ }
+
+ var success = aFilenames.length != 0;
+ aFilenames.forEach(function(crashFilename) {
+ var file = crashDumpDir.clone();
+ file.append(crashFilename);
+ if (file.exists()) {
+ file.remove(false);
+ } else {
+ success = false;
+ }
+ });
+ return success;
+ },
+
+ _findCrashDumpFiles: function(aToIgnore) {
+ var crashDumpDir = this._getCrashDumpDir();
+ var entries = crashDumpDir.exists() && crashDumpDir.directoryEntries;
+ if (!entries) {
+ return [];
+ }
+
+ var crashDumpFiles = [];
+ while (entries.hasMoreElements()) {
+ var file = entries.getNext().QueryInterface(Ci.nsIFile);
+ var path = String(file.path);
+ if (path.match(/\.(dmp|extra)$/) && !aToIgnore[path]) {
+ crashDumpFiles.push(path);
+ }
+ }
+ return crashDumpFiles.concat();
+ },
+
+ _getURI: function (url) {
+ return Services.io.newURI(url, null, null);
+ },
+
+ _readUrlAsString: function(aUrl) {
+ // Fetch script content as we can't use scriptloader's loadSubScript
+ // to evaluate http:// urls...
+ var scriptableStream = Cc["@mozilla.org/scriptableinputstream;1"]
+ .getService(Ci.nsIScriptableInputStream);
+ var channel = Services.io.newChannel(aUrl, null, null);
+ var input = channel.open();
+ scriptableStream.init(input);
+
+ var str;
+ var buffer = [];
+
+ while ((str = scriptableStream.read(4096))) {
+ buffer.push(str);
+ }
+
+ var output = buffer.join("");
+
+ scriptableStream.close();
+ input.close();
+
+ var status;
+ try {
+ channel.QueryInterface(Ci.nsIHttpChannel);
+ status = channel.responseStatus;
+ } catch(e) {
+ /* The channel is not a nsIHttpCHannel, but that's fine */
+ dump("-*- _readUrlAsString: Got an error while fetching " +
+ "chrome script '" + aUrl + "': (" + e.name + ") " + e.message + ". " +
+ "Ignoring.\n");
+ }
+
+ if (status == 404) {
+ throw new SpecialPowersException(
+ "Error while executing chrome script '" + aUrl + "':\n" +
+ "The script doesn't exists. Ensure you have registered it in " +
+ "'support-files' in your mochitest.ini.");
+ }
+
+ return output;
+ },
+
+ /**
+ * messageManager callback function
+ * This will get requests from our API in the window and process them in chrome for it
+ **/
+ _receiveMessageAPI: function(aMessage) {
+ // We explicitly return values in the below code so that this function
+ // doesn't trigger a flurry of warnings about "does not always return
+ // a value".
+ switch(aMessage.name) {
+ case "SPPrefService":
+ var prefs = Services.prefs;
+ var prefType = aMessage.json.prefType.toUpperCase();
+ var prefName = aMessage.json.prefName;
+ var prefValue = "prefValue" in aMessage.json ? aMessage.json.prefValue : null;
+
+ if (aMessage.json.op == "get") {
+ if (!prefName || !prefType)
+ throw new SpecialPowersException("Invalid parameters for get in SPPrefService");
+
+ // return null if the pref doesn't exist
+ if (prefs.getPrefType(prefName) == prefs.PREF_INVALID)
+ return null;
+ } else if (aMessage.json.op == "set") {
+ if (!prefName || !prefType || prefValue === null)
+ throw new SpecialPowersException("Invalid parameters for set in SPPrefService");
+ } else if (aMessage.json.op == "clear") {
+ if (!prefName)
+ throw new SpecialPowersException("Invalid parameters for clear in SPPrefService");
+ } else {
+ throw new SpecialPowersException("Invalid operation for SPPrefService");
+ }
+
+ // Now we make the call
+ switch(prefType) {
+ case "BOOL":
+ if (aMessage.json.op == "get")
+ return(prefs.getBoolPref(prefName));
+ else
+ return(prefs.setBoolPref(prefName, prefValue));
+ case "INT":
+ if (aMessage.json.op == "get")
+ return(prefs.getIntPref(prefName));
+ else
+ return(prefs.setIntPref(prefName, prefValue));
+ case "CHAR":
+ if (aMessage.json.op == "get")
+ return(prefs.getCharPref(prefName));
+ else
+ return(prefs.setCharPref(prefName, prefValue));
+ case "COMPLEX":
+ if (aMessage.json.op == "get")
+ return(prefs.getComplexValue(prefName, prefValue[0]));
+ else
+ return(prefs.setComplexValue(prefName, prefValue[0], prefValue[1]));
+ case "":
+ if (aMessage.json.op == "clear") {
+ prefs.clearUserPref(prefName);
+ return undefined;
+ }
+ }
+ return undefined; // See comment at the beginning of this function.
+
+ case "SPProcessCrashService":
+ switch (aMessage.json.op) {
+ case "register-observer":
+ this._addProcessCrashObservers();
+ break;
+ case "unregister-observer":
+ this._removeProcessCrashObservers();
+ break;
+ case "delete-crash-dump-files":
+ return this._deleteCrashDumpFiles(aMessage.json.filenames);
+ case "find-crash-dump-files":
+ return this._findCrashDumpFiles(aMessage.json.crashDumpFilesToIgnore);
+ default:
+ throw new SpecialPowersException("Invalid operation for SPProcessCrashService");
+ }
+ return undefined; // See comment at the beginning of this function.
+
+ case "SPPermissionManager":
+ let msg = aMessage.json;
+
+ let secMan = Services.scriptSecurityManager;
+ let principal = secMan.getAppCodebasePrincipal(this._getURI(msg.url), msg.appId, msg.isInBrowserElement);
+
+ switch (msg.op) {
+ case "add":
+ Services.perms.addFromPrincipal(principal, msg.type, msg.permission);
+ break;
+ case "remove":
+ Services.perms.removeFromPrincipal(principal, msg.type);
+ break;
+ case "has":
+ let hasPerm = Services.perms.testPermissionFromPrincipal(principal, msg.type);
+ if (hasPerm == Ci.nsIPermissionManager.ALLOW_ACTION)
+ return true;
+ return false;
+ break;
+ case "test":
+ let testPerm = Services.perms.testPermissionFromPrincipal(principal, msg.type, msg.value);
+ if (testPerm == msg.value) {
+ return true;
+ }
+ return false;
+ break;
+ default:
+ throw new SpecialPowersException("Invalid operation for " +
+ "SPPermissionManager");
+ }
+ return undefined; // See comment at the beginning of this function.
+
+ case "SPWebAppService":
+ let Webapps = {};
+ Components.utils.import("resource://gre/modules/Webapps.jsm", Webapps);
+ switch (aMessage.json.op) {
+ case "set-launchable":
+ let val = Webapps.DOMApplicationRegistry.allAppsLaunchable;
+ Webapps.DOMApplicationRegistry.allAppsLaunchable = aMessage.json.launchable;
+ return val;
+ default:
+ throw new SpecialPowersException("Invalid operation for SPWebAppsService");
+ }
+ return undefined; // See comment at the beginning of this function.
+
+ case "SPObserverService":
+ switch (aMessage.json.op) {
+ case "notify":
+ let topic = aMessage.json.observerTopic;
+ let data = aMessage.json.observerData
+ Services.obs.notifyObservers(null, topic, data);
+ break;
+ default:
+ throw new SpecialPowersException("Invalid operation for SPObserverervice");
+ }
+ return undefined; // See comment at the beginning of this function.
+
+ case "SPLoadChromeScript":
+ var url = aMessage.json.url;
+ var id = aMessage.json.id;
+
+ var jsScript = this._readUrlAsString(url);
+
+ // Setup a chrome sandbox that has access to sendAsyncMessage
+ // and addMessageListener in order to communicate with
+ // the mochitest.
+ var systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
+ var sb = Components.utils.Sandbox(systemPrincipal);
+ var mm = aMessage.target
+ .QueryInterface(Ci.nsIFrameLoaderOwner)
+ .frameLoader
+ .messageManager;
+ sb.sendAsyncMessage = (name, message) => {
+ mm.sendAsyncMessage("SPChromeScriptMessage",
+ { id: id, name: name, message: message });
+ };
+ sb.addMessageListener = (name, listener) => {
+ this._chromeScriptListeners.push({ id: id, name: name, listener: listener });
+ };
+
+ // Also expose assertion functions
+ let reporter = function (err, message, stack) {
+ // Pipe assertions back to parent process
+ mm.sendAsyncMessage("SPChromeScriptAssert",
+ { id: id, url: url, err: err, message: message,
+ stack: stack });
+ };
+ Object.defineProperty(sb, "assert", {
+ get: function () {
+ let scope = Components.utils.createObjectIn(sb);
+ Services.scriptloader.loadSubScript("resource://specialpowers/Assert.jsm",
+ scope);
+
+ let assert = new scope.Assert(reporter);
+ delete sb.assert;
+ return sb.assert = assert;
+ },
+ configurable: true
+ });
+
+ // Evaluate the chrome script
+ try {
+ Components.utils.evalInSandbox(jsScript, sb, "1.8", url, 1);
+ } catch(e) {
+ throw new SpecialPowersException("Error while executing chrome " +
+ "script '" + url + "':\n" + e + "\n" +
+ e.fileName + ":" + e.lineNumber);
+ }
+ return undefined; // See comment at the beginning of this function.
+
+ case "SPChromeScriptMessage":
+ var id = aMessage.json.id;
+ var name = aMessage.json.name;
+ var message = aMessage.json.message;
+ this._chromeScriptListeners
+ .filter(o => (o.name == name && o.id == id))
+ .forEach(o => o.listener(message));
+ return undefined; // See comment at the beginning of this function.
+
+ default:
+ throw new SpecialPowersException("Unrecognized Special Powers API");
+ }
+
+ // We throw an exception before reaching this explicit return because
+ // we should never be arriving here anyway.
+ throw new SpecialPowersException("Unreached code");
+ return undefined;
+ }
+};
+
diff --git a/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/content/specialpowers.js b/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/content/specialpowers.js
index 538b104..31ff093 100644
--- a/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/content/specialpowers.js
+++ b/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/content/specialpowers.js
@@ -1,332 +1,101 @@
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Special Powers code
- *
- * The Initial Developer of the Original Code is
- * Mozilla Foundation.
- * Portions created by the Initial Developer are Copyright (C) 2010
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- * Clint Talbert cmtalbert at gmail.com
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK *****/
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* This code is loaded in every child process that is started by mochitest in
* order to be used as a replacement for UniversalXPConnect
*/
-var Ci = Components.interfaces;
-var Cc = Components.classes;
-
function SpecialPowers(window) {
- this.window = window;
- bindDOMWindowUtils(this, window);
+ this.window = Components.utils.getWeakReference(window);
this._encounteredCrashDumpFiles = [];
this._unexpectedCrashDumpFiles = { };
this._crashDumpDir = null;
+ this.DOMWindowUtils = bindDOMWindowUtils(window);
+ Object.defineProperty(this, 'Components', {
+ configurable: true, enumerable: true, get: function() {
+ var win = this.window.get();
+ if (!win)
+ return null;
+ return getRawComponents(win);
+ }});
this._pongHandlers = [];
this._messageListener = this._messageReceived.bind(this);
addMessageListener("SPPingService", this._messageListener);
}
-function bindDOMWindowUtils(sp, window) {
- var util = window.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils);
- // This bit of magic brought to you by the letters
- // B Z, and E, S and the number 5.
- //
- // Take all of the properties on the nsIDOMWindowUtils-implementing
- // object, and rebind them onto a new object with a stub that uses
- // apply to call them from this privileged scope. This way we don't
- // have to explicitly stub out new methods that appear on
- // nsIDOMWindowUtils.
- var proto = Object.getPrototypeOf(util);
- var target = {};
- function rebind(desc, prop) {
- if (prop in desc && typeof(desc[prop]) == "function") {
- var oldval = desc[prop];
- desc[prop] = function() { return oldval.apply(util, arguments); };
- }
- }
- for (var i in proto) {
- var desc = Object.getOwnPropertyDescriptor(proto, i);
- rebind(desc, "get");
- rebind(desc, "set");
- rebind(desc, "value");
- Object.defineProperty(target, i, desc);
- }
- sp.DOMWindowUtils = target;
-}
-
-SpecialPowers.prototype = {
- toString: function() { return "[SpecialPowers]"; },
- sanityCheck: function() { return "foo"; },
-
- // This gets filled in in the constructor.
- DOMWindowUtils: undefined,
-
- // Mimic the get*Pref API
- getBoolPref: function(aPrefName) {
- return (this._getPref(aPrefName, 'BOOL'));
- },
- getIntPref: function(aPrefName) {
- return (this._getPref(aPrefName, 'INT'));
- },
- getCharPref: function(aPrefName) {
- return (this._getPref(aPrefName, 'CHAR'));
- },
- getComplexValue: function(aPrefName, aIid) {
- return (this._getPref(aPrefName, 'COMPLEX', aIid));
- },
-
- // Mimic the set*Pref API
- setBoolPref: function(aPrefName, aValue) {
- return (this._setPref(aPrefName, 'BOOL', aValue));
- },
- setIntPref: function(aPrefName, aValue) {
- return (this._setPref(aPrefName, 'INT', aValue));
- },
- setCharPref: function(aPrefName, aValue) {
- return (this._setPref(aPrefName, 'CHAR', aValue));
- },
- setComplexValue: function(aPrefName, aIid, aValue) {
- return (this._setPref(aPrefName, 'COMPLEX', aValue, aIid));
- },
+SpecialPowers.prototype = new SpecialPowersAPI();
- // Mimic the clearUserPref API
- clearUserPref: function(aPrefName) {
- var msg = {'op':'clear', 'prefName': aPrefName, 'prefType': ""};
- sendSyncMessage('SPPrefService', msg);
- },
+SpecialPowers.prototype.toString = function() { return "[SpecialPowers]"; };
+SpecialPowers.prototype.sanityCheck = function() { return "foo"; };
- // Private pref functions to communicate to chrome
- _getPref: function(aPrefName, aPrefType, aIid) {
- var msg = {};
- if (aIid) {
- // Overloading prefValue to handle complex prefs
- msg = {'op':'get', 'prefName': aPrefName, 'prefType':aPrefType, 'prefValue':[aIid]};
- } else {
- msg = {'op':'get', 'prefName': aPrefName,'prefType': aPrefType};
- }
- return(sendSyncMessage('SPPrefService', msg)[0]);
- },
- _setPref: function(aPrefName, aPrefType, aValue, aIid) {
- var msg = {};
- if (aIid) {
- msg = {'op':'set','prefName':aPrefName, 'prefType': aPrefType, 'prefValue': [aIid,aValue]};
- } else {
- msg = {'op':'set', 'prefName': aPrefName, 'prefType': aPrefType, 'prefValue': aValue};
- }
- return(sendSyncMessage('SPPrefService', msg)[0]);
- },
-
- //XXX: these APIs really ought to be removed, they're not e10s-safe.
- // (also they're pretty Firefox-specific)
- _getTopChromeWindow: function(window) {
- return window.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIWebNavigation)
- .QueryInterface(Ci.nsIDocShellTreeItem)
- .rootTreeItem
- .QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindow)
- .QueryInterface(Ci.nsIDOMChromeWindow);
- },
- _getDocShell: function(window) {
- return window.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIWebNavigation)
- .QueryInterface(Ci.nsIDocShell);
- },
- _getMUDV: function(window) {
- return this._getDocShell(window).contentViewer
- .QueryInterface(Ci.nsIMarkupDocumentViewer);
- },
- _getAutoCompletePopup: function(window) {
- return this._getTopChromeWindow(window).document
- .getElementById("PopupAutoComplete");
- },
- addAutoCompletePopupEventListener: function(window, listener) {
- this._getAutoCompletePopup(window).addEventListener("popupshowing",
- listener,
- false);
- },
- removeAutoCompletePopupEventListener: function(window, listener) {
- this._getAutoCompletePopup(window).removeEventListener("popupshowing",
- listener,
- false);
- },
- isBackButtonEnabled: function(window) {
- return !this._getTopChromeWindow(window).document
- .getElementById("Browser:Back")
- .hasAttribute("disabled");
- },
+// This gets filled in in the constructor.
+SpecialPowers.prototype.DOMWindowUtils = undefined;
+SpecialPowers.prototype.Components = undefined;
- addChromeEventListener: function(type, listener, capture, allowUntrusted) {
- addEventListener(type, listener, capture, allowUntrusted);
- },
- removeChromeEventListener: function(type, listener, capture) {
- removeEventListener(type, listener, capture);
- },
-
- getFullZoom: function(window) {
- return this._getMUDV(window).fullZoom;
- },
- setFullZoom: function(window, zoom) {
- this._getMUDV(window).fullZoom = zoom;
- },
- getTextZoom: function(window) {
- return this._getMUDV(window).textZoom;
- },
- setTextZoom: function(window, zoom) {
- this._getMUDV(window).textZoom = zoom;
- },
+SpecialPowers.prototype._sendSyncMessage = function(msgname, msg) {
+ return sendSyncMessage(msgname, msg);
+};
- createSystemXHR: function() {
- return Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
- .createInstance(Ci.nsIXMLHttpRequest);
- },
+SpecialPowers.prototype._sendAsyncMessage = function(msgname, msg) {
+ sendAsyncMessage(msgname, msg);
+};
- gc: function() {
- this.DOMWindowUtils.garbageCollect();
- },
+SpecialPowers.prototype._addMessageListener = function(msgname, listener) {
+ addMessageListener(msgname, listener);
+};
- hasContentProcesses: function() {
- try {
- var rt = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
- return rt.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
- } catch (e) {
- return true;
- }
- },
+SpecialPowers.prototype._removeMessageListener = function(msgname, listener) {
+ removeMessageListener(msgname, listener);
+};
- registerProcessCrashObservers: function() {
- addMessageListener("SPProcessCrashService", this._messageListener);
- sendSyncMessage("SPProcessCrashService", { op: "register-observer" });
- },
+SpecialPowers.prototype.registerProcessCrashObservers = function() {
+ addMessageListener("SPProcessCrashService", this._messageListener);
+ sendSyncMessage("SPProcessCrashService", { op: "register-observer" });
+};
- _messageReceived: function(aMessage) {
- switch (aMessage.name) {
- case "SPProcessCrashService":
- if (aMessage.json.type == "crash-observed") {
- var self = this;
- aMessage.json.dumpIDs.forEach(function(id) {
- self._encounteredCrashDumpFiles.push(id + ".dmp");
- self._encounteredCrashDumpFiles.push(id + ".extra");
- });
- }
- break;
+SpecialPowers.prototype.unregisterProcessCrashObservers = function() {
+ addMessageListener("SPProcessCrashService", this._messageListener);
+ sendSyncMessage("SPProcessCrashService", { op: "unregister-observer" });
+};
- case "SPPingService":
- if (aMessage.json.op == "pong") {
- var handler = this._pongHandlers.shift();
- if (handler) {
- handler();
- }
+SpecialPowers.prototype._messageReceived = function(aMessage) {
+ switch (aMessage.name) {
+ case "SPProcessCrashService":
+ if (aMessage.json.type == "crash-observed") {
+ for (let e of aMessage.json.dumpIDs) {
+ this._encounteredCrashDumpFiles.push(e.id + "." + e.extension);
}
- break;
- }
- return true;
- },
-
- removeExpectedCrashDumpFiles: function(aExpectingProcessCrash) {
- var success = true;
- if (aExpectingProcessCrash) {
- var message = {
- op: "delete-crash-dump-files",
- filenames: this._encounteredCrashDumpFiles
- };
- if (!sendSyncMessage("SPProcessCrashService", message)[0]) {
- success = false;
}
- }
- this._encounteredCrashDumpFiles.length = 0;
- return success;
- },
-
- findUnexpectedCrashDumpFiles: function() {
- var self = this;
- var message = {
- op: "find-crash-dump-files",
- crashDumpFilesToIgnore: this._unexpectedCrashDumpFiles
- };
- var crashDumpFiles = sendSyncMessage("SPProcessCrashService", message)[0];
- crashDumpFiles.forEach(function(aFilename) {
- self._unexpectedCrashDumpFiles[aFilename] = true;
- });
- return crashDumpFiles;
- },
+ break;
- executeAfterFlushingMessageQueue: function(aCallback) {
- this._pongHandlers.push(aCallback);
- sendAsyncMessage("SPPingService", { op: "ping" });
- },
-
- executeSoon: function(aFunc) {
- var tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager);
- tm.mainThread.dispatch({
- run: function() {
- aFunc();
+ case "SPPingService":
+ if (aMessage.json.op == "pong") {
+ var handler = this._pongHandlers.shift();
+ if (handler) {
+ handler();
+ }
}
- }, Ci.nsIThread.DISPATCH_NORMAL);
- },
+ break;
+ }
+ return true;
+};
- /* from http://mxr.mozilla.org/mozilla-central/source/testing/mochitest/tests/SimpleTest/quit.js
- * by Bob Clary, Jeff Walden, and Robert Sayre.
- */
- quitApplication: function() {
- function canQuitApplication()
- {
- var os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
- if (!os)
- return true;
-
- try {
- var cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
- os.notifyObservers(cancelQuit, "quit-application-requested", null);
-
- // Something aborted the quit process.
- if (cancelQuit.data)
- return false;
- } catch (ex) {}
- return true;
- }
+SpecialPowers.prototype.quit = function() {
+ sendAsyncMessage("SpecialPowers.Quit", {});
+};
- if (!canQuitApplication())
- return false;
-
- var appService = Cc['@mozilla.org/toolkit/app-startup;1'].getService(Ci.nsIAppStartup);
- appService.quit(Ci.nsIAppStartup.eForceQuit);
- return true;
- }
+SpecialPowers.prototype.executeAfterFlushingMessageQueue = function(aCallback) {
+ this._pongHandlers.push(aCallback);
+ sendAsyncMessage("SPPingService", { op: "ping" });
};
// Expose everything but internal APIs (starting with underscores) to
-// web content.
+// web content. We cannot use Object.keys to view SpecialPowers.prototype since
+// we are using the functions from SpecialPowersAPI.prototype
SpecialPowers.prototype.__exposedProps__ = {};
-for each (i in Object.keys(SpecialPowers.prototype).filter(function(v) {return v.charAt(0) != "_";})) {
- SpecialPowers.prototype.__exposedProps__[i] = "r";
+for (var i in SpecialPowers.prototype) {
+ if (i.charAt(0) != "_")
+ SpecialPowers.prototype.__exposedProps__[i] = "r";
}
// Attach our API to the window.
@@ -355,18 +124,11 @@ function SpecialPowersManager() {
SpecialPowersManager.prototype = {
handleEvent: function handleEvent(aEvent) {
var window = aEvent.target.defaultView;
-
- // Need to make sure we are called on what we care about -
- // content windows. DOMWindowCreated is called on *all* HTMLDocuments,
- // some of which belong to chrome windows or other special content.
- //
- var uri = window.document.documentURIObject;
- if (uri.scheme === "chrome" || uri.spec.split(":")[0] == "about") {
- return;
- }
-
attachSpecialPowersToWindow(window);
}
};
var specialpowersmanager = new SpecialPowersManager();
+
+this.SpecialPowers = SpecialPowers;
+this.attachSpecialPowersToWindow = attachSpecialPowersToWindow;
diff --git a/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/content/specialpowersAPI.js b/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/content/specialpowersAPI.js
new file mode 100644
index 0000000..940bb01
--- /dev/null
+++ b/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/content/specialpowersAPI.js
@@ -0,0 +1,1744 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+/* This code is loaded in every child process that is started by mochitest in
+ * order to be used as a replacement for UniversalXPConnect
+ */
+
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+var Cu = Components.utils;
+
+Cu.import("resource://specialpowers/MockFilePicker.jsm");
+Cu.import("resource://specialpowers/MockColorPicker.jsm");
+Cu.import("resource://specialpowers/MockPermissionPrompt.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+function SpecialPowersAPI() {
+ this._consoleListeners = [];
+ this._encounteredCrashDumpFiles = [];
+ this._unexpectedCrashDumpFiles = { };
+ this._crashDumpDir = null;
+ this._mfl = null;
+ this._prefEnvUndoStack = [];
+ this._pendingPrefs = [];
+ this._applyingPrefs = false;
+ this._permissionsUndoStack = [];
+ this._pendingPermissions = [];
+ this._applyingPermissions = false;
+ this._fm = null;
+ this._cb = null;
+}
+
+function bindDOMWindowUtils(aWindow) {
+ if (!aWindow)
+ return
+
+ var util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ return wrapPrivileged(util);
+}
+
+function getRawComponents(aWindow) {
+ // If we're running in automation that supports enablePrivilege, then we also
+ // provided access to the privileged Components.
+ try {
+ let win = Cu.waiveXrays(aWindow);
+ if (typeof win.netscape.security.PrivilegeManager == 'object')
+ Cu.forcePrivilegedComponentsForScope(aWindow);
+ } catch (e) {}
+ return Cu.getComponentsForScope(aWindow);
+}
+
+function isWrappable(x) {
+ if (typeof x === "object")
+ return x !== null;
+ return typeof x === "function";
+};
+
+function isWrapper(x) {
+ return isWrappable(x) && (typeof x.SpecialPowers_wrappedObject !== "undefined");
+};
+
+function unwrapIfWrapped(x) {
+ return isWrapper(x) ? unwrapPrivileged(x) : x;
+};
+
+function wrapIfUnwrapped(x) {
+ return isWrapper(x) ? x : wrapPrivileged(x);
+}
+
+function isXrayWrapper(x) {
+ return Cu.isXrayWrapper(x);
+}
+
+function callGetOwnPropertyDescriptor(obj, name) {
+ // Quickstubbed getters and setters are propertyOps, and don't get reified
+ // until someone calls __lookupGetter__ or __lookupSetter__ on them (note
+ // that there are special version of those functions for quickstubs, so
+ // apply()ing Object.prototype.__lookupGetter__ isn't good enough). Try to
+ // trigger reification before calling Object.getOwnPropertyDescriptor.
+ //
+ // See bug 764315.
+ try {
+ obj.__lookupGetter__(name);
+ obj.__lookupSetter__(name);
+ } catch(e) { }
+ return Object.getOwnPropertyDescriptor(obj, name);
+}
+
+// We can't call apply() directy on Xray-wrapped functions, so we have to be
+// clever.
+function doApply(fun, invocant, args) {
+ return Function.prototype.apply.call(fun, invocant, args);
+}
+
+function wrapPrivileged(obj) {
+
+ // Primitives pass straight through.
+ if (!isWrappable(obj))
+ return obj;
+
+ // No double wrapping.
+ if (isWrapper(obj))
+ throw "Trying to double-wrap object!";
+
+ // Make our core wrapper object.
+ var handler = new SpecialPowersHandler(obj);
+
+ // If the object is callable, make a function proxy.
+ if (typeof obj === "function") {
+ var callTrap = function() {
+ // The invocant and arguments may or may not be wrappers. Unwrap them if necessary.
+ var invocant = unwrapIfWrapped(this);
+ var unwrappedArgs = Array.prototype.slice.call(arguments).map(unwrapIfWrapped);
+
+ try {
+ return wrapPrivileged(doApply(obj, invocant, unwrappedArgs));
+ } catch (e) {
+ // Wrap exceptions and re-throw them.
+ throw wrapIfUnwrapped(e);
+ }
+ };
+ var constructTrap = function() {
+ // The arguments may or may not be wrappers. Unwrap them if necessary.
+ var unwrappedArgs = Array.prototype.slice.call(arguments).map(unwrapIfWrapped);
+
+ // We want to invoke "obj" as a constructor, but using unwrappedArgs as
+ // the arguments. Make sure to wrap and re-throw exceptions!
+ try {
+ return wrapPrivileged(new obj(...unwrappedArgs));
+ } catch (e) {
+ throw wrapIfUnwrapped(e);
+ }
+ };
+
+ return Proxy.createFunction(handler, callTrap, constructTrap);
+ }
+
+ // Otherwise, just make a regular object proxy.
+ return Proxy.create(handler);
+};
+
+function unwrapPrivileged(x) {
+
+ // We don't wrap primitives, so sometimes we have a primitive where we'd
+ // expect to have a wrapper. The proxy pretends to be the type that it's
+ // emulating, so we can just as easily check isWrappable() on a proxy as
+ // we can on an unwrapped object.
+ if (!isWrappable(x))
+ return x;
+
+ // If we have a wrappable type, make sure it's wrapped.
+ if (!isWrapper(x))
+ throw "Trying to unwrap a non-wrapped object!";
+
+ // Unwrap.
+ return x.SpecialPowers_wrappedObject;
+};
+
+function crawlProtoChain(obj, fn) {
+ var rv = fn(obj);
+ if (rv !== undefined)
+ return rv;
+ if (Object.getPrototypeOf(obj))
+ return crawlProtoChain(Object.getPrototypeOf(obj), fn);
+};
+
+/*
+ * We want to waive the __exposedProps__ security check for SpecialPowers-wrapped
+ * objects. We do this by creating a proxy singleton that just always returns 'rw'
+ * for any property name.
+ */
+function ExposedPropsWaiverHandler() {
+ // NB: XPConnect denies access if the relevant member of __exposedProps__ is not
+ // enumerable.
+ var _permit = { value: 'rw', writable: false, configurable: false, enumerable: true };
+ return {
+ getOwnPropertyDescriptor: function(name) { return _permit; },
+ getPropertyDescriptor: function(name) { return _permit; },
+ getOwnPropertyNames: function() { throw Error("Can't enumerate ExposedPropsWaiver"); },
+ getPropertyNames: function() { throw Error("Can't enumerate ExposedPropsWaiver"); },
+ enumerate: function() { throw Error("Can't enumerate ExposedPropsWaiver"); },
+ defineProperty: function(name) { throw Error("Can't define props on ExposedPropsWaiver"); },
+ delete: function(name) { throw Error("Can't delete props from ExposedPropsWaiver"); }
+ };
+};
+ExposedPropsWaiver = Proxy.create(ExposedPropsWaiverHandler());
+
+function SpecialPowersHandler(obj) {
+ this.wrappedObject = obj;
+};
+
+// Allow us to transitively maintain the membrane by wrapping descriptors
+// we return.
+SpecialPowersHandler.prototype.doGetPropertyDescriptor = function(name, own) {
+
+ // Handle our special API.
+ if (name == "SpecialPowers_wrappedObject")
+ return { value: this.wrappedObject, writeable: false, configurable: false, enumerable: false };
+
+ // Handle __exposedProps__.
+ if (name == "__exposedProps__")
+ return { value: ExposedPropsWaiver, writable: false, configurable: false, enumerable: false };
+
+ // In general, we want Xray wrappers for content DOM objects, because waiving
+ // Xray gives us Xray waiver wrappers that clamp the principal when we cross
+ // compartment boundaries. However, Xray adds some gunk to toString(), which
+ // has the potential to confuse consumers that aren't expecting Xray wrappers.
+ // Since toString() is a non-privileged method that returns only strings, we
+ // can just waive Xray for that case.
+ var obj = name == 'toString' ? XPCNativeWrapper.unwrap(this.wrappedObject)
+ : this.wrappedObject;
+
+ //
+ // Call through to the wrapped object.
+ //
+ // Note that we have several cases here, each of which requires special handling.
+ //
+ var desc;
+
+ // Case 1: Own Properties.
+ //
+ // This one is easy, thanks to Object.getOwnPropertyDescriptor().
+ if (own)
+ desc = callGetOwnPropertyDescriptor(obj, name);
+
+ // Case 2: Not own, not Xray-wrapped.
+ //
+ // Here, we can just crawl the prototype chain, calling
+ // Object.getOwnPropertyDescriptor until we find what we want.
+ //
+ // NB: Make sure to check this.wrappedObject here, rather than obj, because
+ // we may have waived Xray on obj above.
+ else if (!isXrayWrapper(this.wrappedObject))
+ desc = crawlProtoChain(obj, function(o) {return callGetOwnPropertyDescriptor(o, name);});
+
+ // Case 3: Not own, Xray-wrapped.
+ //
+ // This one is harder, because we Xray wrappers are flattened and don't have
+ // a prototype. Xray wrappers are proxies themselves, so we'd love to just call
+ // through to XrayWrapper<Base>::getPropertyDescriptor(). Unfortunately though,
+ // we don't have any way to do that. :-(
+ //
+ // So we first try with a call to getOwnPropertyDescriptor(). If that fails,
+ // we make up a descriptor, using some assumptions about what kinds of things
+ // tend to live on the prototypes of Xray-wrapped objects.
+ else {
+ desc = Object.getOwnPropertyDescriptor(obj, name);
+ if (!desc) {
+ var getter = Object.prototype.__lookupGetter__.call(obj, name);
+ var setter = Object.prototype.__lookupSetter__.call(obj, name);
+ if (getter || setter)
+ desc = {get: getter, set: setter, configurable: true, enumerable: true};
+ else if (name in obj)
+ desc = {value: obj[name], writable: false, configurable: true, enumerable: true};
+ }
+ }
+
+ // Bail if we've got nothing.
+ if (typeof desc === 'undefined')
+ return undefined;
+
+ // When accessors are implemented as JSPropertyOps rather than JSNatives (ie,
+ // QuickStubs), the js engine does the wrong thing and treats it as a value
+ // descriptor rather than an accessor descriptor. Jorendorff suggested this
+ // little hack to work around it. See bug 520882.
+ if (desc && 'value' in desc && desc.value === undefined)
+ desc.value = obj[name];
+
+ // A trapping proxy's properties must always be configurable, but sometimes
+ // this we get non-configurable properties from Object.getOwnPropertyDescriptor().
+ // Tell a white lie.
+ desc.configurable = true;
+
+ // Transitively maintain the wrapper membrane.
+ function wrapIfExists(key) { if (key in desc) desc[key] = wrapPrivileged(desc[key]); };
+ wrapIfExists('value');
+ wrapIfExists('get');
+ wrapIfExists('set');
+
+ return desc;
+};
+
+SpecialPowersHandler.prototype.getOwnPropertyDescriptor = function(name) {
+ return this.doGetPropertyDescriptor(name, true);
+};
+
+SpecialPowersHandler.prototype.getPropertyDescriptor = function(name) {
+ return this.doGetPropertyDescriptor(name, false);
+};
+
+function doGetOwnPropertyNames(obj, props) {
+
+ // Insert our special API. It's not enumerable, but getPropertyNames()
+ // includes non-enumerable properties.
+ var specialAPI = 'SpecialPowers_wrappedObject';
+ if (props.indexOf(specialAPI) == -1)
+ props.push(specialAPI);
+
+ // Do the normal thing.
+ var flt = function(a) { return props.indexOf(a) == -1; };
+ props = props.concat(Object.getOwnPropertyNames(obj).filter(flt));
+
+ // If we've got an Xray wrapper, include the expandos as well.
+ if ('wrappedJSObject' in obj)
+ props = props.concat(Object.getOwnPropertyNames(obj.wrappedJSObject)
+ .filter(flt));
+
+ return props;
+}
+
+SpecialPowersHandler.prototype.getOwnPropertyNames = function() {
+ return doGetOwnPropertyNames(this.wrappedObject, []);
+};
+
+SpecialPowersHandler.prototype.getPropertyNames = function() {
+
+ // Manually walk the prototype chain, making sure to add only property names
+ // that haven't been overridden.
+ //
+ // There's some trickiness here with Xray wrappers. Xray wrappers don't have
+ // a prototype, so we need to unwrap them if we want to get all of the names
+ // with Object.getOwnPropertyNames(). But we don't really want to unwrap the
+ // base object, because that will include expandos that are inaccessible via
+ // our implementation of get{,Own}PropertyDescriptor(). So we unwrap just
+ // before accessing the prototype. This ensures that we get Xray vision on
+ // the base object, and no Xray vision for the rest of the way up.
+ var obj = this.wrappedObject;
+ var props = [];
+ while (obj) {
+ props = doGetOwnPropertyNames(obj, props);
+ obj = Object.getPrototypeOf(XPCNativeWrapper.unwrap(obj));
+ }
+ return props;
+};
+
+SpecialPowersHandler.prototype.defineProperty = function(name, desc) {
+ return Object.defineProperty(this.wrappedObject, name, desc);
+};
+
+SpecialPowersHandler.prototype.delete = function(name) {
+ return delete this.wrappedObject[name];
+};
+
+SpecialPowersHandler.prototype.fix = function() { return undefined; /* Throws a TypeError. */ };
+
+// Per the ES5 spec this is a derived trap, but it's fundamental in spidermonkey
+// for some reason. See bug 665198.
+SpecialPowersHandler.prototype.enumerate = function() {
+ var t = this;
+ var filt = function(name) { return t.getPropertyDescriptor(name).enumerable; };
+ return this.getPropertyNames().filter(filt);
+};
+
+// SPConsoleListener reflects nsIConsoleMessage objects into JS in a
+// tidy, XPCOM-hiding way. Messages that are nsIScriptError objects
+// have their properties exposed in detail. It also auto-unregisters
+// itself when it receives a "sentinel" message.
+function SPConsoleListener(callback) {
+ this.callback = callback;
+}
+
+SPConsoleListener.prototype = {
+ observe: function(msg) {
+ let m = { message: msg.message,
+ errorMessage: null,
+ sourceName: null,
+ sourceLine: null,
+ lineNumber: null,
+ columnNumber: null,
+ category: null,
+ windowID: null,
+ isScriptError: false,
+ isWarning: false,
+ isException: false,
+ isStrict: false };
+ if (msg instanceof Ci.nsIScriptError) {
+ m.errorMessage = msg.errorMessage;
+ m.sourceName = msg.sourceName;
+ m.sourceLine = msg.sourceLine;
+ m.lineNumber = msg.lineNumber;
+ m.columnNumber = msg.columnNumber;
+ m.category = msg.category;
+ m.windowID = msg.outerWindowID;
+ m.isScriptError = true;
+ m.isWarning = ((msg.flags & Ci.nsIScriptError.warningFlag) === 1);
+ m.isException = ((msg.flags & Ci.nsIScriptError.exceptionFlag) === 1);
+ m.isStrict = ((msg.flags & Ci.nsIScriptError.strictFlag) === 1);
+ }
+
+ // expose all props of 'm' as read-only
+ let expose = {};
+ for (let prop in m)
+ expose[prop] = 'r';
+ m.__exposedProps__ = expose;
+ Object.freeze(m);
+
+ this.callback.call(undefined, m);
+
+ if (!m.isScriptError && m.message === "SENTINEL")
+ Services.console.unregisterListener(this);
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIConsoleListener])
+};
+
+function wrapCallback(cb) {
+ return function SpecialPowersCallbackWrapper() {
+ args = Array.prototype.map.call(arguments, wrapIfUnwrapped);
+ return cb.apply(this, args);
+ }
+}
+
+function wrapCallbackObject(obj) {
+ wrapper = { __exposedProps__: ExposedPropsWaiver };
+ for (var i in obj) {
+ if (typeof obj[i] == 'function')
+ wrapper[i] = wrapCallback(obj[i]);
+ else
+ wrapper[i] = obj[i];
+ }
+ return wrapper;
+}
+
+SpecialPowersAPI.prototype = {
+
+ /*
+ * Privileged object wrapping API
+ *
+ * Usage:
+ * var wrapper = SpecialPowers.wrap(obj);
+ * wrapper.privilegedMethod(); wrapper.privilegedProperty;
+ * obj === SpecialPowers.unwrap(wrapper);
+ *
+ * These functions provide transparent access to privileged objects using
+ * various pieces of deep SpiderMagic. Conceptually, a wrapper is just an
+ * object containing a reference to the underlying object, where all method
+ * calls and property accesses are transparently performed with the System
+ * Principal. Moreover, objects obtained from the wrapper (including properties
+ * and method return values) are wrapped automatically. Thus, after a single
+ * call to SpecialPowers.wrap(), the wrapper layer is transitively maintained.
+ *
+ * Known Issues:
+ *
+ * - The wrapping function does not preserve identity, so
+ * SpecialPowers.wrap(foo) !== SpecialPowers.wrap(foo). See bug 718543.
+ *
+ * - The wrapper cannot see expando properties on unprivileged DOM objects.
+ * That is to say, the wrapper uses Xray delegation.
+ *
+ * - The wrapper sometimes guesses certain ES5 attributes for returned
+ * properties. This is explained in a comment in the wrapper code above,
+ * and shouldn't be a problem.
+ */
+ wrap: wrapIfUnwrapped,
+ unwrap: unwrapIfWrapped,
+ isWrapper: isWrapper,
+
+ /*
+ * When content needs to pass a callback or a callback object to an API
+ * accessed over SpecialPowers, that API may sometimes receive arguments for
+ * whom it is forbidden to create a wrapper in content scopes. As such, we
+ * need a layer to wrap the values in SpecialPowers wrappers before they ever
+ * reach content.
+ */
+ wrapCallback: wrapCallback,
+ wrapCallbackObject: wrapCallbackObject,
+
+ /*
+ * Create blank privileged objects to use as out-params for privileged functions.
+ */
+ createBlankObject: function () {
+ var obj = new Object;
+ obj.__exposedProps__ = ExposedPropsWaiver;
+ return obj;
+ },
+
+ /*
+ * Because SpecialPowers wrappers don't preserve identity, comparing with ==
+ * can be hazardous. Sometimes we can just unwrap to compare, but sometimes
+ * wrapping the underlying object into a content scope is forbidden. This
+ * function strips any wrappers if they exist and compare the underlying
+ * values.
+ */
+ compare: function(a, b) {
+ return unwrapIfWrapped(a) === unwrapIfWrapped(b);
+ },
+
+ get MockFilePicker() {
+ return MockFilePicker
+ },
+
+ get MockColorPicker() {
+ return MockColorPicker
+ },
+
+ get MockPermissionPrompt() {
+ return MockPermissionPrompt
+ },
+
+ loadChromeScript: function (url) {
+ // Create a unique id for this chrome script
+ let uuidGenerator = Cc["@mozilla.org/uuid-generator;1"]
+ .getService(Ci.nsIUUIDGenerator);
+ let id = uuidGenerator.generateUUID().toString();
+
+ // Tells chrome code to evaluate this chrome script
+ this._sendSyncMessage("SPLoadChromeScript",
+ { url: url, id: id });
+
+ // Returns a MessageManager like API in order to be
+ // able to communicate with this chrome script
+ let listeners = [];
+ let chromeScript = {
+ addMessageListener: (name, listener) => {
+ listeners.push({ name: name, listener: listener });
+ },
+
+ removeMessageListener: (name, listener) => {
+ listeners = listeners.filter(
+ o => (o.name != name || o.listener != listener)
+ );
+ },
+
+ sendAsyncMessage: (name, message) => {
+ this._sendSyncMessage("SPChromeScriptMessage",
+ { id: id, name: name, message: message });
+ },
+
+ destroy: () => {
+ listeners = [];
+ this._removeMessageListener("SPChromeScriptMessage", chromeScript);
+ this._removeMessageListener("SPChromeScriptAssert", chromeScript);
+ },
+
+ receiveMessage: (aMessage) => {
+ let messageId = aMessage.json.id;
+ let name = aMessage.json.name;
+ let message = aMessage.json.message;
+ // Ignore message from other chrome script
+ if (messageId != id)
+ return;
+
+ if (aMessage.name == "SPChromeScriptMessage") {
+ listeners.filter(o => (o.name == name))
+ .forEach(o => o.listener(this.wrap(message)));
+ } else if (aMessage.name == "SPChromeScriptAssert") {
+ assert(aMessage.json);
+ }
+ }
+ };
+ this._addMessageListener("SPChromeScriptMessage", chromeScript);
+ this._addMessageListener("SPChromeScriptAssert", chromeScript);
+
+ let assert = json => {
+ // An assertion has been done in a mochitest chrome script
+ let {url, err, message, stack} = json;
+
+ // Try to fetch a test runner from the mochitest
+ // in order to properly log these assertions and notify
+ // all usefull log observers
+ let window = this.window.get();
+ let parentRunner, repr = function (o) o;
+ if (window) {
+ window = window.wrappedJSObject;
+ parentRunner = window.TestRunner;
+ if (window.repr) {
+ repr = window.repr;
+ }
+ }
+
+ // Craft a mochitest-like report string
+ var resultString = err ? "TEST-UNEXPECTED-FAIL" : "TEST-PASS";
+ var diagnostic =
+ message ? message :
+ ("assertion @ " + stack.filename + ":" + stack.lineNumber);
+ if (err) {
+ diagnostic +=
+ " - got " + repr(err.actual) +
+ ", expected " + repr(err.expected) +
+ " (operator " + err.operator + ")";
+ }
+ var msg = [resultString, url, diagnostic].join(" | ");
+ if (parentRunner) {
+ if (err) {
+ parentRunner.addFailedTest(url);
+ parentRunner.error(msg);
+ } else {
+ parentRunner.log(msg);
+ }
+ } else {
+ // When we are running only a single mochitest, there is no test runner
+ dump(msg + "\n");
+ }
+ };
+
+ return this.wrap(chromeScript);
+ },
+
+ get Services() {
+ return wrapPrivileged(Services);
+ },
+
+ /*
+ * In general, any Components object created for unprivileged scopes is
+ * neutered (it implements nsIXPCComponentsBase, but not nsIXPCComponents).
+ * We override this in certain legacy automation configurations (see the
+ * implementation of getRawComponents() above), but don't want to support
+ * it in cases where it isn't already required.
+ *
+ * In scopes with neutered Components, we don't have a natural referent for
+ * things like SpecialPowers.Cc. So in those cases, we fall back to the
+ * Components object from the SpecialPowers scope. This doesn't quite behave
+ * the same way (in particular, SpecialPowers.Cc[foo].createInstance() will
+ * create an instance in the SpecialPowers scope), but SpecialPowers wrapping
+ * is already a YMMV / Whatever-It-Takes-To-Get-TBPL-Green sort of thing.
+ *
+ * It probably wouldn't be too much work to just make SpecialPowers.Components
+ * unconditionally point to the Components object in the SpecialPowers scope.
+ * Try will tell what needs to be fixed up.
+ */
+ getFullComponents: function() {
+ return typeof this.Components.classes == 'object' ? this.Components
+ : Components;
+ },
+
+ /*
+ * Convenient shortcuts to the standard Components abbreviations. Note that
+ * we don't SpecialPowers-wrap Components.interfaces, because it's available
+ * to untrusted content, and wrapping it confuses QI and identity checks.
+ */
+ get Cc() { return wrapPrivileged(this.getFullComponents()).classes; },
+ get Ci() { return this.Components.interfaces; },
+ get Cu() { return wrapPrivileged(this.getFullComponents()).utils; },
+ get Cr() { return wrapPrivileged(this.Components).results; },
+
+ /*
+ * SpecialPowers.getRawComponents() allows content to get a reference to a
+ * naked (and, in certain automation configurations, privileged) Components
+ * object for its scope.
+ *
+ * SpecialPowers.getRawComponents(window) is defined as the global property
+ * window.SpecialPowers.Components for convenience.
+ */
+ getRawComponents: getRawComponents,
+
+ getDOMWindowUtils: function(aWindow) {
+ if (aWindow == this.window.get() && this.DOMWindowUtils != null)
+ return this.DOMWindowUtils;
+
+ return bindDOMWindowUtils(aWindow);
+ },
+
+ removeExpectedCrashDumpFiles: function(aExpectingProcessCrash) {
+ var success = true;
+ if (aExpectingProcessCrash) {
+ var message = {
+ op: "delete-crash-dump-files",
+ filenames: this._encounteredCrashDumpFiles
+ };
+ if (!this._sendSyncMessage("SPProcessCrashService", message)[0]) {
+ success = false;
+ }
+ }
+ this._encounteredCrashDumpFiles.length = 0;
+ return success;
+ },
+
+ findUnexpectedCrashDumpFiles: function() {
+ var self = this;
+ var message = {
+ op: "find-crash-dump-files",
+ crashDumpFilesToIgnore: this._unexpectedCrashDumpFiles
+ };
+ var crashDumpFiles = this._sendSyncMessage("SPProcessCrashService", message)[0];
+ crashDumpFiles.forEach(function(aFilename) {
+ self._unexpectedCrashDumpFiles[aFilename] = true;
+ });
+ return crashDumpFiles;
+ },
+
+ _delayCallbackTwice: function(callback) {
+ function delayedCallback() {
+ function delayAgain() {
+ content.window.setTimeout(callback, 0);
+ }
+ content.window.setTimeout(delayAgain, 0);
+ }
+ return delayedCallback;
+ },
+
+ /* apply permissions to the system and when the test case is finished (SimpleTest.finish())
+ we will revert the permission back to the original.
+
+ inPermissions is an array of objects where each object has a type, action, context, ex:
+ [{'type': 'SystemXHR', 'allow': 1, 'context': document},
+ {'type': 'SystemXHR', 'allow': Ci.nsIPermissionManager.PROMPT_ACTION, 'context': document}]
+
+ Allow can be a boolean value of true/false or ALLOW_ACTION/DENY_ACTION/PROMPT_ACTION/UNKNOWN_ACTION
+ */
+ pushPermissions: function(inPermissions, callback) {
+ var pendingPermissions = [];
+ var cleanupPermissions = [];
+
+ for (var p in inPermissions) {
+ var permission = inPermissions[p];
+ var originalValue = Ci.nsIPermissionManager.UNKNOWN_ACTION;
+ if (this.testPermission(permission.type, Ci.nsIPermissionManager.ALLOW_ACTION, permission.context)) {
+ originalValue = Ci.nsIPermissionManager.ALLOW_ACTION;
+ } else if (this.testPermission(permission.type, Ci.nsIPermissionManager.DENY_ACTION, permission.context)) {
+ originalValue = Ci.nsIPermissionManager.DENY_ACTION;
+ } else if (this.testPermission(permission.type, Ci.nsIPermissionManager.PROMPT_ACTION, permission.context)) {
+ originalValue = Ci.nsIPermissionManager.PROMPT_ACTION;
+ } else if (this.testPermission(permission.type, Ci.nsICookiePermission.ACCESS_SESSION, permission.context)) {
+ originalValue = Ci.nsICookiePermission.ACCESS_SESSION;
+ } else if (this.testPermission(permission.type, Ci.nsICookiePermission.ACCESS_ALLOW_FIRST_PARTY_ONLY, permission.context)) {
+ originalValue = Ci.nsICookiePermission.ACCESS_ALLOW_FIRST_PARTY_ONLY;
+ } else if (this.testPermission(permission.type, Ci.nsICookiePermission.ACCESS_LIMIT_THIRD_PARTY, permission.context)) {
+ originalValue = Ci.nsICookiePermission.ACCESS_LIMIT_THIRD_PARTY;
+ }
+
+ let [url, appId, isInBrowserElement] = this._getInfoFromPermissionArg(permission.context);
+
+ let perm;
+ if (typeof permission.allow !== 'boolean') {
+ perm = permission.allow;
+ } else {
+ perm = permission.allow ? Ci.nsIPermissionManager.ALLOW_ACTION
+ : Ci.nsIPermissionManager.DENY_ACTION;
+ }
+
+ if (permission.remove == true)
+ perm = Ci.nsIPermissionManager.UNKNOWN_ACTION;
+
+ if (originalValue == perm) {
+ continue;
+ }
+
+ var todo = {'op': 'add', 'type': permission.type, 'permission': perm, 'value': perm, 'url': url, 'appId': appId, 'isInBrowserElement': isInBrowserElement};
+ if (permission.remove == true)
+ todo.op = 'remove';
+
+ pendingPermissions.push(todo);
+
+ /* Push original permissions value or clear into cleanup array */
+ var cleanupTodo = {'op': 'add', 'type': permission.type, 'permission': perm, 'value': perm, 'url': url, 'appId': appId, 'isInBrowserElement': isInBrowserElement};
+ if (originalValue == Ci.nsIPermissionManager.UNKNOWN_ACTION) {
+ cleanupTodo.op = 'remove';
+ } else {
+ cleanupTodo.value = originalValue;
+ cleanupTodo.permission = originalValue;
+ }
+ cleanupPermissions.push(cleanupTodo);
+ }
+
+ if (pendingPermissions.length > 0) {
+ // The callback needs to be delayed twice. One delay is because the pref
+ // service doesn't guarantee the order it calls its observers in, so it
+ // may notify the observer holding the callback before the other
+ // observers have been notified and given a chance to make the changes
+ // that the callback checks for. The second delay is because pref
+ // observers often defer making their changes by posting an event to the
+ // event loop.
+ this._permissionsUndoStack.push(cleanupPermissions);
+ this._pendingPermissions.push([pendingPermissions,
+ this._delayCallbackTwice(callback)]);
+ this._applyPermissions();
+ } else {
+ content.window.setTimeout(callback, 0);
+ }
+ },
+
+ popPermissions: function(callback) {
+ if (this._permissionsUndoStack.length > 0) {
+ // See pushPermissions comment regarding delay.
+ let cb = callback ? this._delayCallbackTwice(callback) : null;
+ /* Each pop from the stack will yield an object {op/type/permission/value/url/appid/isInBrowserElement} or null */
+ this._pendingPermissions.push([this._permissionsUndoStack.pop(), cb]);
+ this._applyPermissions();
+ } else {
+ content.window.setTimeout(callback, 0);
+ }
+ },
+
+ flushPermissions: function(callback) {
+ while (this._permissionsUndoStack.length > 1)
+ this.popPermissions(null);
+
+ this.popPermissions(callback);
+ },
+
+
+ _permissionObserver: {
+ _lastPermission: {},
+ _callBack: null,
+ _nextCallback: null,
+
+ observe: function (aSubject, aTopic, aData)
+ {
+ if (aTopic == "perm-changed") {
+ var permission = aSubject.QueryInterface(Ci.nsIPermission);
+ if (permission.type == this._lastPermission.type) {
+ var os = Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService);
+ os.removeObserver(this, "perm-changed");
+ content.window.setTimeout(this._callback, 0);
+ content.window.setTimeout(this._nextCallback, 0);
+ }
+ }
+ }
+ },
+
+ /*
+ Iterate through one atomic set of permissions actions and perform allow/deny as appropriate.
+ All actions performed must modify the relevant permission.
+ */
+ _applyPermissions: function() {
+ if (this._applyingPermissions || this._pendingPermissions.length <= 0) {
+ return;
+ }
+
+ /* Set lock and get prefs from the _pendingPrefs queue */
+ this._applyingPermissions = true;
+ var transaction = this._pendingPermissions.shift();
+ var pendingActions = transaction[0];
+ var callback = transaction[1];
+ var lastPermission = pendingActions[pendingActions.length-1];
+
+ var self = this;
+ var os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
+ this._permissionObserver._lastPermission = lastPermission;
+ this._permissionObserver._callback = callback;
+ this._permissionObserver._nextCallback = function () {
+ self._applyingPermissions = false;
+ // Now apply any permissions that may have been queued while we were applying
+ self._applyPermissions();
+ }
+
+ os.addObserver(this._permissionObserver, "perm-changed", false);
+
+ for (var idx in pendingActions) {
+ var perm = pendingActions[idx];
+ this._sendSyncMessage('SPPermissionManager', perm)[0];
+ }
+ },
+
+ /*
+ * Take in a list of pref changes to make, and invoke |callback| once those
+ * changes have taken effect. When the test finishes, these changes are
+ * reverted.
+ *
+ * |inPrefs| must be an object with up to two properties: "set" and "clear".
+ * pushPrefEnv will set prefs as indicated in |inPrefs.set| and will unset
+ * the prefs indicated in |inPrefs.clear|.
+ *
+ * For example, you might pass |inPrefs| as:
+ *
+ * inPrefs = {'set': [['foo.bar', 2], ['magic.pref', 'baz']],
+ * 'clear': [['clear.this'], ['also.this']] };
+ *
+ * Notice that |set| and |clear| are both an array of arrays. In |set|, each
+ * of the inner arrays must have the form [pref_name, value] or [pref_name,
+ * value, iid]. (The latter form is used for prefs with "complex" values.)
+ *
+ * In |clear|, each inner array should have the form [pref_name].
+ *
+ * If you set the same pref more than once (or both set and clear a pref),
+ * the behavior of this method is undefined.
+ *
+ * (Implementation note: _prefEnvUndoStack is a stack of values to revert to,
+ * not values which have been set!)
+ *
+ * TODO: complex values for original cleanup?
+ *
+ */
+ pushPrefEnv: function(inPrefs, callback) {
+ var prefs = Components.classes["@mozilla.org/preferences-service;1"].
+ getService(Components.interfaces.nsIPrefBranch);
+
+ var pref_string = [];
+ pref_string[prefs.PREF_INT] = "INT";
+ pref_string[prefs.PREF_BOOL] = "BOOL";
+ pref_string[prefs.PREF_STRING] = "CHAR";
+
+ var pendingActions = [];
+ var cleanupActions = [];
+
+ for (var action in inPrefs) { /* set|clear */
+ for (var idx in inPrefs[action]) {
+ var aPref = inPrefs[action][idx];
+ var prefName = aPref[0];
+ var prefValue = null;
+ var prefIid = null;
+ var prefType = prefs.PREF_INVALID;
+ var originalValue = null;
+
+ if (aPref.length == 3) {
+ prefValue = aPref[1];
+ prefIid = aPref[2];
+ } else if (aPref.length == 2) {
+ prefValue = aPref[1];
+ }
+
+ /* If pref is not found or invalid it doesn't exist. */
+ if (prefs.getPrefType(prefName) != prefs.PREF_INVALID) {
+ prefType = pref_string[prefs.getPrefType(prefName)];
+ if ((prefs.prefHasUserValue(prefName) && action == 'clear') ||
+ (action == 'set'))
+ originalValue = this._getPref(prefName, prefType);
+ } else if (action == 'set') {
+ /* prefName doesn't exist, so 'clear' is pointless */
+ if (aPref.length == 3) {
+ prefType = "COMPLEX";
+ } else if (aPref.length == 2) {
+ if (typeof(prefValue) == "boolean")
+ prefType = "BOOL";
+ else if (typeof(prefValue) == "number")
+ prefType = "INT";
+ else if (typeof(prefValue) == "string")
+ prefType = "CHAR";
+ }
+ }
+
+ /* PREF_INVALID: A non existing pref which we are clearing or invalid values for a set */
+ if (prefType == prefs.PREF_INVALID)
+ continue;
+
+ /* We are not going to set a pref if the value is the same */
+ if (originalValue == prefValue)
+ continue;
+
+ pendingActions.push({'action': action, 'type': prefType, 'name': prefName, 'value': prefValue, 'Iid': prefIid});
+
+ /* Push original preference value or clear into cleanup array */
+ var cleanupTodo = {'action': action, 'type': prefType, 'name': prefName, 'value': originalValue, 'Iid': prefIid};
+ if (originalValue == null) {
+ cleanupTodo.action = 'clear';
+ } else {
+ cleanupTodo.action = 'set';
+ }
+ cleanupActions.push(cleanupTodo);
+ }
+ }
+
+ if (pendingActions.length > 0) {
+ // The callback needs to be delayed twice. One delay is because the pref
+ // service doesn't guarantee the order it calls its observers in, so it
+ // may notify the observer holding the callback before the other
+ // observers have been notified and given a chance to make the changes
+ // that the callback checks for. The second delay is because pref
+ // observers often defer making their changes by posting an event to the
+ // event loop.
+ this._prefEnvUndoStack.push(cleanupActions);
+ this._pendingPrefs.push([pendingActions,
+ this._delayCallbackTwice(callback)]);
+ this._applyPrefs();
+ } else {
+ content.window.setTimeout(callback, 0);
+ }
+ },
+
+ popPrefEnv: function(callback) {
+ if (this._prefEnvUndoStack.length > 0) {
+ // See pushPrefEnv comment regarding delay.
+ let cb = callback ? this._delayCallbackTwice(callback) : null;
+ /* Each pop will have a valid block of preferences */
+ this._pendingPrefs.push([this._prefEnvUndoStack.pop(), cb]);
+ this._applyPrefs();
+ } else {
+ content.window.setTimeout(callback, 0);
+ }
+ },
+
+ flushPrefEnv: function(callback) {
+ while (this._prefEnvUndoStack.length > 1)
+ this.popPrefEnv(null);
+
+ this.popPrefEnv(callback);
+ },
+
+ /*
+ Iterate through one atomic set of pref actions and perform sets/clears as appropriate.
+ All actions performed must modify the relevant pref.
+ */
+ _applyPrefs: function() {
+ if (this._applyingPrefs || this._pendingPrefs.length <= 0) {
+ return;
+ }
+
+ /* Set lock and get prefs from the _pendingPrefs queue */
+ this._applyingPrefs = true;
+ var transaction = this._pendingPrefs.shift();
+ var pendingActions = transaction[0];
+ var callback = transaction[1];
+
+ var lastPref = pendingActions[pendingActions.length-1];
+
+ var pb = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+ var self = this;
+ pb.addObserver(lastPref.name, function prefObs(subject, topic, data) {
+ pb.removeObserver(lastPref.name, prefObs);
+
+ content.window.setTimeout(callback, 0);
+ content.window.setTimeout(function () {
+ self._applyingPrefs = false;
+ // Now apply any prefs that may have been queued while we were applying
+ self._applyPrefs();
+ }, 0);
+ }, false);
+
+ for (var idx in pendingActions) {
+ var pref = pendingActions[idx];
+ if (pref.action == 'set') {
+ this._setPref(pref.name, pref.type, pref.value, pref.Iid);
+ } else if (pref.action == 'clear') {
+ this.clearUserPref(pref.name);
+ }
+ }
+ },
+
+ // Disables the app install prompt for the duration of this test. There is
+ // no need to re-enable the prompt at the end of the test.
+ //
+ // The provided callback is invoked once the prompt is disabled.
+ autoConfirmAppInstall: function(cb) {
+ this.pushPrefEnv({set: [['dom.mozApps.auto_confirm_install', true]]}, cb);
+ },
+
+ // Allow tests to disable the per platform app validity checks so we can
+ // test higher level WebApp functionality without full platform support.
+ setAllAppsLaunchable: function(launchable) {
+ var message = {
+ op: "set-launchable",
+ launchable: launchable
+ };
+ return this._sendSyncMessage("SPWebAppService", message);
+ },
+
+ _proxiedObservers: {
+ "specialpowers-http-notify-request": function(aMessage) {
+ let uri = aMessage.json.uri;
+ Services.obs.notifyObservers(null, "specialpowers-http-notify-request", uri);
+ },
+ },
+
+ _addObserverProxy: function(notification) {
+ if (notification in this._proxiedObservers) {
+ this._addMessageListener(notification, this._proxiedObservers[notification]);
+ }
+ },
+
+ _removeObserverProxy: function(notification) {
+ if (notification in this._proxiedObservers) {
+ this._removeMessageListener(notification, this._proxiedObservers[notification]);
+ }
+ },
+
+ addObserver: function(obs, notification, weak) {
+ this._addObserverProxy(notification);
+ if (typeof obs == 'object' && obs.observe.name != 'SpecialPowersCallbackWrapper')
+ obs.observe = wrapCallback(obs.observe);
+ var obsvc = Cc['@mozilla.org/observer-service;1']
+ .getService(Ci.nsIObserverService);
+ obsvc.addObserver(obs, notification, weak);
+ },
+ removeObserver: function(obs, notification) {
+ this._removeObserverProxy(notification);
+ var obsvc = Cc['@mozilla.org/observer-service;1']
+ .getService(Ci.nsIObserverService);
+ obsvc.removeObserver(obs, notification);
+ },
+ notifyObservers: function(subject, topic, data) {
+ var obsvc = Cc['@mozilla.org/observer-service;1']
+ .getService(Ci.nsIObserverService);
+ obsvc.notifyObservers(subject, topic, data);
+ },
+
+ can_QI: function(obj) {
+ return obj.QueryInterface !== undefined;
+ },
+ do_QueryInterface: function(obj, iface) {
+ return obj.QueryInterface(Ci[iface]);
+ },
+
+ call_Instanceof: function (obj1, obj2) {
+ obj1=unwrapIfWrapped(obj1);
+ obj2=unwrapIfWrapped(obj2);
+ return obj1 instanceof obj2;
+ },
+
+ // Returns a privileged getter from an object. GetOwnPropertyDescriptor does
+ // not work here because xray wrappers don't properly implement it.
+ //
+ // This terribleness is used by content/base/test/test_object.html because
+ // <object> and <embed> tags will spawn plugins if their prototype is touched,
+ // so we need to get and cache the getter of |hasRunningPlugin| if we want to
+ // call it without paradoxically spawning the plugin.
+ do_lookupGetter: function(obj, name) {
+ return Object.prototype.__lookupGetter__.call(obj, name);
+ },
+
+ // Mimic the get*Pref API
+ getBoolPref: function(aPrefName) {
+ return (this._getPref(aPrefName, 'BOOL'));
+ },
+ getIntPref: function(aPrefName) {
+ return (this._getPref(aPrefName, 'INT'));
+ },
+ getCharPref: function(aPrefName) {
+ return (this._getPref(aPrefName, 'CHAR'));
+ },
+ getComplexValue: function(aPrefName, aIid) {
+ return (this._getPref(aPrefName, 'COMPLEX', aIid));
+ },
+
+ // Mimic the set*Pref API
+ setBoolPref: function(aPrefName, aValue) {
+ return (this._setPref(aPrefName, 'BOOL', aValue));
+ },
+ setIntPref: function(aPrefName, aValue) {
+ return (this._setPref(aPrefName, 'INT', aValue));
+ },
+ setCharPref: function(aPrefName, aValue) {
+ return (this._setPref(aPrefName, 'CHAR', aValue));
+ },
+ setComplexValue: function(aPrefName, aIid, aValue) {
+ return (this._setPref(aPrefName, 'COMPLEX', aValue, aIid));
+ },
+
+ // Mimic the clearUserPref API
+ clearUserPref: function(aPrefName) {
+ var msg = {'op':'clear', 'prefName': aPrefName, 'prefType': ""};
+ this._sendSyncMessage('SPPrefService', msg);
+ },
+
+ // Private pref functions to communicate to chrome
+ _getPref: function(aPrefName, aPrefType, aIid) {
+ var msg = {};
+ if (aIid) {
+ // Overloading prefValue to handle complex prefs
+ msg = {'op':'get', 'prefName': aPrefName, 'prefType':aPrefType, 'prefValue':[aIid]};
+ } else {
+ msg = {'op':'get', 'prefName': aPrefName,'prefType': aPrefType};
+ }
+ var val = this._sendSyncMessage('SPPrefService', msg);
+
+ if (val == null || val[0] == null)
+ throw "Error getting pref";
+ return val[0];
+ },
+ _setPref: function(aPrefName, aPrefType, aValue, aIid) {
+ var msg = {};
+ if (aIid) {
+ msg = {'op':'set','prefName':aPrefName, 'prefType': aPrefType, 'prefValue': [aIid,aValue]};
+ } else {
+ msg = {'op':'set', 'prefName': aPrefName, 'prefType': aPrefType, 'prefValue': aValue};
+ }
+ return(this._sendSyncMessage('SPPrefService', msg)[0]);
+ },
+
+ _getDocShell: function(window) {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell);
+ },
+ _getMUDV: function(window) {
+ return this._getDocShell(window).contentViewer
+ .QueryInterface(Ci.nsIMarkupDocumentViewer);
+ },
+ //XXX: these APIs really ought to be removed, they're not e10s-safe.
+ // (also they're pretty Firefox-specific)
+ _getTopChromeWindow: function(window) {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem)
+ .rootTreeItem
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow)
+ .QueryInterface(Ci.nsIDOMChromeWindow);
+ },
+ _getAutoCompletePopup: function(window) {
+ return this._getTopChromeWindow(window).document
+ .getElementById("PopupAutoComplete");
+ },
+ addAutoCompletePopupEventListener: function(window, eventname, listener) {
+ this._getAutoCompletePopup(window).addEventListener(eventname,
+ listener,
+ false);
+ },
+ removeAutoCompletePopupEventListener: function(window, eventname, listener) {
+ this._getAutoCompletePopup(window).removeEventListener(eventname,
+ listener,
+ false);
+ },
+ get formHistory() {
+ let tmp = {};
+ Cu.import("resource://gre/modules/FormHistory.jsm", tmp);
+ return wrapPrivileged(tmp.FormHistory);
+ },
+ getFormFillController: function(window) {
+ return Components.classes["@mozilla.org/satchel/form-fill-controller;1"]
+ .getService(Components.interfaces.nsIFormFillController);
+ },
+ attachFormFillControllerTo: function(window) {
+ this.getFormFillController()
+ .attachToBrowser(this._getDocShell(window),
+ this._getAutoCompletePopup(window));
+ },
+ detachFormFillControllerFrom: function(window) {
+ this.getFormFillController().detachFromBrowser(this._getDocShell(window));
+ },
+ isBackButtonEnabled: function(window) {
+ return !this._getTopChromeWindow(window).document
+ .getElementById("Browser:Back")
+ .hasAttribute("disabled");
+ },
+ //XXX end of problematic APIs
+
+ addChromeEventListener: function(type, listener, capture, allowUntrusted) {
+ addEventListener(type, listener, capture, allowUntrusted);
+ },
+ removeChromeEventListener: function(type, listener, capture) {
+ removeEventListener(type, listener, capture);
+ },
+
+ // Note: each call to registerConsoleListener MUST be paired with a
+ // call to postConsoleSentinel; when the callback receives the
+ // sentinel it will unregister itself (_after_ calling the
+ // callback). SimpleTest.expectConsoleMessages does this for you.
+ // If you register more than one console listener, a call to
+ // postConsoleSentinel will zap all of them.
+ registerConsoleListener: function(callback) {
+ let listener = new SPConsoleListener(callback);
+ Services.console.registerListener(listener);
+ },
+ postConsoleSentinel: function() {
+ Services.console.logStringMessage("SENTINEL");
+ },
+ resetConsole: function() {
+ Services.console.reset();
+ },
+
+ getMaxLineBoxWidth: function(window) {
+ return this._getMUDV(window).maxLineBoxWidth;
+ },
+
+ setMaxLineBoxWidth: function(window, width) {
+ this._getMUDV(window).changeMaxLineBoxWidth(width);
+ },
+
+ getFullZoom: function(window) {
+ return this._getMUDV(window).fullZoom;
+ },
+ setFullZoom: function(window, zoom) {
+ this._getMUDV(window).fullZoom = zoom;
+ },
+ getTextZoom: function(window) {
+ return this._getMUDV(window).textZoom;
+ },
+ setTextZoom: function(window, zoom) {
+ this._getMUDV(window).textZoom = zoom;
+ },
+
+ emulateMedium: function(window, mediaType) {
+ this._getMUDV(window).emulateMedium(mediaType);
+ },
+ stopEmulatingMedium: function(window) {
+ this._getMUDV(window).stopEmulatingMedium();
+ },
+
+ snapshotWindowWithOptions: function (win, rect, bgcolor, options) {
+ var el = this.window.get().document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
+ if (rect === undefined) {
+ rect = { top: win.scrollY, left: win.scrollX,
+ width: win.innerWidth, height: win.innerHeight };
+ }
+ if (bgcolor === undefined) {
+ bgcolor = "rgb(255,255,255)";
+ }
+ if (options === undefined) {
+ options = { };
+ }
+
+ el.width = rect.width;
+ el.height = rect.height;
+ var ctx = el.getContext("2d");
+ var flags = 0;
+
+ for (var option in options) {
+ flags |= options[option] && ctx[option];
+ }
+
+ ctx.drawWindow(win,
+ rect.left, rect.top, rect.width, rect.height,
+ bgcolor,
+ flags);
+ return el;
+ },
+
+ snapshotWindow: function (win, withCaret, rect, bgcolor) {
+ return this.snapshotWindowWithOptions(win, rect, bgcolor,
+ { DRAWWINDOW_DRAW_CARET: withCaret });
+ },
+
+ snapshotRect: function (win, rect, bgcolor) {
+ return this.snapshotWindowWithOptions(win, rect, bgcolor);
+ },
+
+ gc: function() {
+ this.DOMWindowUtils.garbageCollect();
+ },
+
+ forceGC: function() {
+ Cu.forceGC();
+ },
+
+ forceCC: function() {
+ Cu.forceCC();
+ },
+
+ // Due to various dependencies between JS objects and C++ objects, an ordinary
+ // forceGC doesn't necessarily clear all unused objects, thus the GC and CC
+ // needs to run several times and when no other JS is running.
+ // The current number of iterations has been determined according to massive
+ // cross platform testing.
+ exactGC: function(win, callback) {
+ var self = this;
+ let count = 0;
+
+ function doPreciseGCandCC() {
+ function scheduledGCCallback() {
+ self.getDOMWindowUtils(win).cycleCollect();
+
+ if (++count < 2) {
+ doPreciseGCandCC();
+ } else {
+ callback();
+ }
+ }
+
+ Cu.schedulePreciseGC(scheduledGCCallback);
+ }
+
+ doPreciseGCandCC();
+ },
+
+ setGCZeal: function(zeal) {
+ Cu.setGCZeal(zeal);
+ },
+
+ isMainProcess: function() {
+ try {
+ return Cc["@mozilla.org/xre/app-info;1"].
+ getService(Ci.nsIXULRuntime).
+ processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+ } catch (e) { }
+ return true;
+ },
+
+ _xpcomabi: null,
+
+ get XPCOMABI() {
+ if (this._xpcomabi != null)
+ return this._xpcomabi;
+
+ var xulRuntime = Cc["@mozilla.org/xre/app-info;1"]
+ .getService(Components.interfaces.nsIXULAppInfo)
+ .QueryInterface(Components.interfaces.nsIXULRuntime);
+
+ this._xpcomabi = xulRuntime.XPCOMABI;
+ return this._xpcomabi;
+ },
+
+ // The optional aWin parameter allows the caller to specify a given window in
+ // whose scope the runnable should be dispatched. If aFun throws, the
+ // exception will be reported to aWin.
+ executeSoon: function(aFun, aWin) {
+ // Create the runnable in the scope of aWin to avoid running into COWs.
+ var runnable = {};
+ if (aWin)
+ runnable = Cu.createObjectIn(aWin);
+ runnable.run = aFun;
+ Cu.dispatch(runnable, aWin);
+ },
+
+ _os: null,
+
+ get OS() {
+ if (this._os != null)
+ return this._os;
+
+ var xulRuntime = Cc["@mozilla.org/xre/app-info;1"]
+ .getService(Components.interfaces.nsIXULAppInfo)
+ .QueryInterface(Components.interfaces.nsIXULRuntime);
+
+ this._os = xulRuntime.OS;
+ return this._os;
+ },
+
+ addSystemEventListener: function(target, type, listener, useCapture) {
+ Cc["@mozilla.org/eventlistenerservice;1"].
+ getService(Ci.nsIEventListenerService).
+ addSystemEventListener(target, type, listener, useCapture);
+ },
+ removeSystemEventListener: function(target, type, listener, useCapture) {
+ Cc["@mozilla.org/eventlistenerservice;1"].
+ getService(Ci.nsIEventListenerService).
+ removeSystemEventListener(target, type, listener, useCapture);
+ },
+
+ getDOMRequestService: function() {
+ var serv = Cc["@mozilla.org/dom/dom-request-service;1"].
+ getService(Ci.nsIDOMRequestService);
+ var res = { __exposedProps__: {} };
+ var props = ["createRequest", "createCursor", "fireError", "fireSuccess",
+ "fireDone", "fireDetailedError"];
+ for (i in props) {
+ let prop = props[i];
+ res[prop] = function() { return serv[prop].apply(serv, arguments) };
+ res.__exposedProps__[prop] = "r";
+ }
+ return res;
+ },
+
+ setLogFile: function(path) {
+ this._mfl = new MozillaFileLogger(path);
+ },
+
+ log: function(data) {
+ this._mfl.log(data);
+ },
+
+ closeLogFile: function() {
+ this._mfl.close();
+ },
+
+ addCategoryEntry: function(category, entry, value, persists, replace) {
+ Components.classes["@mozilla.org/categorymanager;1"].
+ getService(Components.interfaces.nsICategoryManager).
+ addCategoryEntry(category, entry, value, persists, replace);
+ },
+
+ deleteCategoryEntry: function(category, entry, persists) {
+ Components.classes["@mozilla.org/categorymanager;1"].
+ getService(Components.interfaces.nsICategoryManager).
+ deleteCategoryEntry(category, entry, persists);
+ },
+
+ copyString: function(str, doc) {
+ Components.classes["@mozilla.org/widget/clipboardhelper;1"].
+ getService(Components.interfaces.nsIClipboardHelper).
+ copyString(str, doc);
+ },
+
+ openDialog: function(win, args) {
+ return win.openDialog.apply(win, args);
+ },
+
+ // :jdm gets credit for this. ex: getPrivilegedProps(window, 'location.href');
+ getPrivilegedProps: function(obj, props) {
+ var parts = props.split('.');
+
+ for (var i = 0; i < parts.length; i++) {
+ var p = parts[i];
+ if (obj[p]) {
+ obj = obj[p];
+ } else {
+ return null;
+ }
+ }
+ return obj;
+ },
+
+ get focusManager() {
+ if (this._fm != null)
+ return this._fm;
+
+ this._fm = Components.classes["@mozilla.org/focus-manager;1"].
+ getService(Components.interfaces.nsIFocusManager);
+
+ return this._fm;
+ },
+
+ getFocusedElementForWindow: function(targetWindow, aDeep, childTargetWindow) {
+ return this.focusManager.getFocusedElementForWindow(targetWindow, aDeep, childTargetWindow);
+ },
+
+ activeWindow: function() {
+ return this.focusManager.activeWindow;
+ },
+
+ focusedWindow: function() {
+ return this.focusManager.focusedWindow;
+ },
+
+ focus: function(aWindow) {
+ // This is called inside TestRunner._makeIframe without aWindow, because of assertions in oop mochitests
+ // With aWindow, it is called in SimpleTest.waitForFocus to allow popup window opener focus switching
+ if (aWindow)
+ aWindow.focus();
+ sendAsyncMessage("SpecialPowers.Focus", {});
+ },
+
+ getClipboardData: function(flavor, whichClipboard) {
+ if (this._cb == null)
+ this._cb = Components.classes["@mozilla.org/widget/clipboard;1"].
+ getService(Components.interfaces.nsIClipboard);
+ if (whichClipboard === undefined)
+ whichClipboard = this._cb.kGlobalClipboard;
+
+ var xferable = Components.classes["@mozilla.org/widget/transferable;1"].
+ createInstance(Components.interfaces.nsITransferable);
+ xferable.init(this._getDocShell(content.window)
+ .QueryInterface(Components.interfaces.nsILoadContext));
+ xferable.addDataFlavor(flavor);
+ this._cb.getData(xferable, whichClipboard);
+ var data = {};
+ try {
+ xferable.getTransferData(flavor, data, {});
+ } catch (e) {}
+ data = data.value || null;
+ if (data == null)
+ return "";
+
+ return data.QueryInterface(Components.interfaces.nsISupportsString).data;
+ },
+
+ clipboardCopyString: function(preExpectedVal, doc) {
+ var cbHelperSvc = Components.classes["@mozilla.org/widget/clipboardhelper;1"].
+ getService(Components.interfaces.nsIClipboardHelper);
+ cbHelperSvc.copyString(preExpectedVal, doc);
+ },
+
+ supportsSelectionClipboard: function() {
+ if (this._cb == null) {
+ this._cb = Components.classes["@mozilla.org/widget/clipboard;1"].
+ getService(Components.interfaces.nsIClipboard);
+ }
+ return this._cb.supportsSelectionClipboard();
+ },
+
+ swapFactoryRegistration: function(cid, contractID, newFactory, oldFactory) {
+ var componentRegistrar = Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar);
+
+ var unregisterFactory = newFactory;
+ var registerFactory = oldFactory;
+
+ if (cid == null) {
+ if (contractID != null) {
+ cid = componentRegistrar.contractIDToCID(contractID);
+ oldFactory = Components.manager.getClassObject(Components.classes[contractID],
+ Components.interfaces.nsIFactory);
+ } else {
+ return {'error': "trying to register a new contract ID: Missing contractID"};
+ }
+
+ unregisterFactory = oldFactory;
+ registerFactory = newFactory;
+ }
+ componentRegistrar.unregisterFactory(cid,
+ unregisterFactory);
+
+ // Restore the original factory.
+ componentRegistrar.registerFactory(cid,
+ "",
+ contractID,
+ registerFactory);
+ return {'cid':cid, 'originalFactory':oldFactory};
+ },
+
+ _getElement: function(aWindow, id) {
+ return ((typeof(id) == "string") ?
+ aWindow.document.getElementById(id) : id);
+ },
+
+ dispatchEvent: function(aWindow, target, event) {
+ var el = this._getElement(aWindow, target);
+ return el.dispatchEvent(event);
+ },
+
+ get isDebugBuild() {
+ delete this.isDebugBuild;
+ var debug = Cc["@mozilla.org/xpcom/debug;1"].getService(Ci.nsIDebug2);
+ return this.isDebugBuild = debug.isDebugBuild;
+ },
+ assertionCount: function() {
+ var debugsvc = Cc['@mozilla.org/xpcom/debug;1'].getService(Ci.nsIDebug2);
+ return debugsvc.assertionCount;
+ },
+
+ /**
+ * Get the message manager associated with an <iframe mozbrowser>.
+ */
+ getBrowserFrameMessageManager: function(aFrameElement) {
+ return this.wrap(aFrameElement.QueryInterface(Ci.nsIFrameLoaderOwner)
+ .frameLoader
+ .messageManager);
+ },
+
+ setFullscreenAllowed: function(document) {
+ var pm = Cc["@mozilla.org/permissionmanager;1"].getService(Ci.nsIPermissionManager);
+ pm.addFromPrincipal(document.nodePrincipal, "fullscreen", Ci.nsIPermissionManager.ALLOW_ACTION);
+ var obsvc = Cc['@mozilla.org/observer-service;1']
+ .getService(Ci.nsIObserverService);
+ obsvc.notifyObservers(document, "fullscreen-approved", null);
+ },
+
+ removeFullscreenAllowed: function(document) {
+ var pm = Cc["@mozilla.org/permissionmanager;1"].getService(Ci.nsIPermissionManager);
+ pm.removeFromPrincipal(document.nodePrincipal, "fullscreen");
+ },
+
+ _getInfoFromPermissionArg: function(arg) {
+ let url = "";
+ let appId = Ci.nsIScriptSecurityManager.NO_APP_ID;
+ let isInBrowserElement = false;
+
+ if (typeof(arg) == "string") {
+ // It's an URL.
+ url = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService)
+ .newURI(arg, null, null)
+ .spec;
+ } else if (arg.manifestURL) {
+ // It's a thing representing an app.
+ let appsSvc = Cc["@mozilla.org/AppsService;1"]
+ .getService(Ci.nsIAppsService)
+ let app = appsSvc.getAppByManifestURL(arg.manifestURL);
+
+ if (!app) {
+ throw "No app for this manifest!";
+ }
+
+ appId = appsSvc.getAppLocalIdByManifestURL(arg.manifestURL);
+ url = app.origin;
+ isInBrowserElement = arg.isInBrowserElement || false;
+ } else if (arg.nodePrincipal) {
+ // It's a document.
+ url = arg.nodePrincipal.URI.spec;
+ appId = arg.nodePrincipal.appId;
+ isInBrowserElement = arg.nodePrincipal.isInBrowserElement;
+ } else {
+ url = arg.url;
+ appId = arg.appId;
+ isInBrowserElement = arg.isInBrowserElement;
+ }
+
+ return [ url, appId, isInBrowserElement ];
+ },
+
+ addPermission: function(type, allow, arg) {
+ let [url, appId, isInBrowserElement] = this._getInfoFromPermissionArg(arg);
+
+ let permission;
+ if (typeof allow !== 'boolean') {
+ permission = allow;
+ } else {
+ permission = allow ? Ci.nsIPermissionManager.ALLOW_ACTION
+ : Ci.nsIPermissionManager.DENY_ACTION;
+ }
+
+ var msg = {
+ 'op': 'add',
+ 'type': type,
+ 'permission': permission,
+ 'url': url,
+ 'appId': appId,
+ 'isInBrowserElement': isInBrowserElement
+ };
+
+ this._sendSyncMessage('SPPermissionManager', msg);
+ },
+
+ removePermission: function(type, arg) {
+ let [url, appId, isInBrowserElement] = this._getInfoFromPermissionArg(arg);
+
+ var msg = {
+ 'op': 'remove',
+ 'type': type,
+ 'url': url,
+ 'appId': appId,
+ 'isInBrowserElement': isInBrowserElement
+ };
+
+ this._sendSyncMessage('SPPermissionManager', msg);
+ },
+
+ hasPermission: function (type, arg) {
+ let [url, appId, isInBrowserElement] = this._getInfoFromPermissionArg(arg);
+
+ var msg = {
+ 'op': 'has',
+ 'type': type,
+ 'url': url,
+ 'appId': appId,
+ 'isInBrowserElement': isInBrowserElement
+ };
+
+ return this._sendSyncMessage('SPPermissionManager', msg)[0];
+ },
+ testPermission: function (type, value, arg) {
+ let [url, appId, isInBrowserElement] = this._getInfoFromPermissionArg(arg);
+
+ var msg = {
+ 'op': 'test',
+ 'type': type,
+ 'value': value,
+ 'url': url,
+ 'appId': appId,
+ 'isInBrowserElement': isInBrowserElement
+ };
+ return this._sendSyncMessage('SPPermissionManager', msg)[0];
+ },
+
+ getMozFullPath: function(file) {
+ return file.mozFullPath;
+ },
+
+ isWindowPrivate: function(win) {
+ return PrivateBrowsingUtils.isWindowPrivate(win);
+ },
+
+ notifyObserversInParentProcess: function(subject, topic, data) {
+ if (subject) {
+ throw new Error("Can't send subject to another process!");
+ }
+ if (this.isMainProcess()) {
+ this.notifyObservers(subject, topic, data);
+ return;
+ }
+ var msg = {
+ 'op': 'notify',
+ 'observerTopic': topic,
+ 'observerData': data
+ };
+ this._sendSyncMessage('SPObserverService', msg);
+ },
+};
+
+this.SpecialPowersAPI = SpecialPowersAPI;
+this.bindDOMWindowUtils = bindDOMWindowUtils;
+this.getRawComponents = getRawComponents;
diff --git a/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/modules/Assert.jsm b/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/modules/Assert.jsm
new file mode 100644
index 0000000..a80c407
--- /dev/null
+++ b/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/modules/Assert.jsm
@@ -0,0 +1,442 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://wiki.commonjs.org/wiki/Unit_Testing/1.0
+// When you see a javadoc comment that contains a number, it's a reference to a
+// specific section of the CommonJS spec.
+//
+// Originally from narwhal.js (http://narwhaljs.org)
+// Copyright (c) 2009 Thomas Robinson <280north.com>
+// MIT license: http://opensource.org/licenses/MIT
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [
+ "Assert"
+];
+
+/**
+ * 1. The assert module provides functions that throw AssertionError's when
+ * particular conditions are not met.
+ *
+ * To use the module you'll need to instantiate it first, which allows consumers
+ * to override certain behavior on the newly obtained instance. For examples,
+ * see the javadoc comments for the `report` member function.
+ */
+let Assert = this.Assert = function(reporterFunc) {
+ if (reporterFunc)
+ this.setReporter(reporterFunc);
+};
+
+function instanceOf(object, type) {
+ return Object.prototype.toString.call(object) == "[object " + type + "]";
+}
+
+function replacer(key, value) {
+ if (value === undefined) {
+ return "" + value;
+ }
+ if (typeof value === "number" && (isNaN(value) || !isFinite(value))) {
+ return value.toString();
+ }
+ if (typeof value === "function" || instanceOf(value, "RegExp")) {
+ return value.toString();
+ }
+ return value;
+}
+
+const kTruncateLength = 128;
+
+function truncate(text, newLength = kTruncateLength) {
+ if (typeof text == "string") {
+ return text.length < newLength ? text : text.slice(0, newLength);
+ } else {
+ return text;
+ }
+}
+
+function getMessage(error, prefix = "") {
+ let actual, expected;
+ // Wrap calls to JSON.stringify in try...catch blocks, as they may throw. If
+ // so, fall back to toString().
+ try {
+ actual = JSON.stringify(error.actual, replacer);
+ } catch (ex) {
+ actual = Object.prototype.toString.call(error.actual);
+ }
+ try {
+ expected = JSON.stringify(error.expected, replacer);
+ } catch (ex) {
+ expected = Object.prototype.toString.call(error.expected);
+ }
+ let message = prefix;
+ if (error.operator) {
+ message += (prefix ? " - " : "") + truncate(actual) + " " + error.operator +
+ " " + truncate(expected);
+ }
+ return message;
+}
+
+/**
+ * 2. The AssertionError is defined in assert.
+ *
+ * Example:
+ * new assert.AssertionError({
+ * message: message,
+ * actual: actual,
+ * expected: expected,
+ * operator: operator
+ * });
+ *
+ * At present only the four keys mentioned above are used and
+ * understood by the spec. Implementations or sub modules can pass
+ * other keys to the AssertionError's constructor - they will be
+ * ignored.
+ */
+Assert.AssertionError = function(options) {
+ this.name = "AssertionError";
+ this.actual = options.actual;
+ this.expected = options.expected;
+ this.operator = options.operator;
+ this.message = getMessage(this, options.message);
+ // The part of the stack that comes from this module is not interesting.
+ let stack = Components.stack;
+ do {
+ stack = stack.caller;
+ } while(stack.filename && stack.filename.contains("Assert.jsm"))
+ this.stack = stack;
+};
+
+// assert.AssertionError instanceof Error
+Assert.AssertionError.prototype = Object.create(Error.prototype, {
+ constructor: {
+ value: Assert.AssertionError,
+ enumerable: false,
+ writable: true,
+ configurable: true
+ }
+});
+
+let proto = Assert.prototype;
+
+proto._reporter = null;
+/**
+ * Set a custom assertion report handler function. Arguments passed in to this
+ * function are:
+ * err (AssertionError|null) An error object when the assertion failed or null
+ * when it passed
+ * message (string) Message describing the assertion
+ * stack (stack) Stack trace of the assertion function
+ *
+ * Example:
+ * ```js
+ * Assert.setReporter(function customReporter(err, message, stack) {
+ * if (err) {
+ * do_report_result(false, err.message, err.stack);
+ * } else {
+ * do_report_result(true, message, stack);
+ * }
+ * });
+ * ```
+ *
+ * @param reporterFunc
+ * (function) Report handler function
+ */
+proto.setReporter = function(reporterFunc) {
+ this._reporter = reporterFunc;
+};
+
+/**
+ * 3. All of the following functions must throw an AssertionError when a
+ * corresponding condition is not met, with a message that may be undefined if
+ * not provided. All assertion methods provide both the actual and expected
+ * values to the assertion error for display purposes.
+ *
+ * This report method only throws errors on assertion failures, as per spec,
+ * but consumers of this module (think: xpcshell-test, mochitest) may want to
+ * override this default implementation.
+ *
+ * Example:
+ * ```js
+ * // The following will report an assertion failure.
+ * this.report(1 != 2, 1, 2, "testing JS number math!", "==");
+ * ```
+ *
+ * @param failed
+ * (boolean) Indicates if the assertion failed or not
+ * @param actual
+ * (mixed) The result of evaluating the assertion
+ * @param expected (optional)
+ * (mixed) Expected result from the test author
+ * @param message (optional)
+ * (string) Short explanation of the expected result
+ * @param operator (optional)
+ * (string) Operation qualifier used by the assertion method (ex: '==')
+ */
+proto.report = function(failed, actual, expected, message, operator) {
+ let err = new Assert.AssertionError({
+ message: message,
+ actual: actual,
+ expected: expected,
+ operator: operator
+ });
+ if (!this._reporter) {
+ // If no custom reporter is set, throw the error.
+ if (failed) {
+ throw err;
+ }
+ } else {
+ this._reporter(failed ? err : null, message, err.stack);
+ }
+};
+
+/**
+ * 4. Pure assertion tests whether a value is truthy, as determined by !!guard.
+ * assert.ok(guard, message_opt);
+ * This statement is equivalent to assert.equal(true, !!guard, message_opt);.
+ * To test strictly for the value true, use assert.strictEqual(true, guard,
+ * message_opt);.
+ *
+ * @param value
+ * (mixed) Test subject to be evaluated as truthy
+ * @param message (optional)
+ * (string) Short explanation of the expected result
+ */
+proto.ok = function(value, message) {
+ this.report(!value, value, true, message, "==");
+};
+
+/**
+ * 5. The equality assertion tests shallow, coercive equality with ==.
+ * assert.equal(actual, expected, message_opt);
+ *
+ * @param actual
+ * (mixed) Test subject to be evaluated as equivalent to `expected`
+ * @param expected
+ * (mixed) Test reference to evaluate against `actual`
+ * @param message (optional)
+ * (string) Short explanation of the expected result
+ */
+proto.equal = function equal(actual, expected, message) {
+ this.report(actual != expected, actual, expected, message, "==");
+};
+
+/**
+ * 6. The non-equality assertion tests for whether two objects are not equal
+ * with != assert.notEqual(actual, expected, message_opt);
+ *
+ * @param actual
+ * (mixed) Test subject to be evaluated as NOT equivalent to `expected`
+ * @param expected
+ * (mixed) Test reference to evaluate against `actual`
+ * @param message (optional)
+ * (string) Short explanation of the expected result
+ */
+proto.notEqual = function notEqual(actual, expected, message) {
+ this.report(actual == expected, actual, expected, message, "!=");
+};
+
+/**
+ * 7. The equivalence assertion tests a deep equality relation.
+ * assert.deepEqual(actual, expected, message_opt);
+ *
+ * We check using the most exact approximation of equality between two objects
+ * to keep the chance of false positives to a minimum.
+ * `JSON.stringify` is not designed to be used for this purpose; objects may
+ * have ambiguous `toJSON()` implementations that would influence the test.
+ *
+ * @param actual
+ * (mixed) Test subject to be evaluated as equivalent to `expected`, including nested properties
+ * @param expected
+ * (mixed) Test reference to evaluate against `actual`
+ * @param message (optional)
+ * (string) Short explanation of the expected result
+ */
+proto.deepEqual = function deepEqual(actual, expected, message) {
+ this.report(!_deepEqual(actual, expected), actual, expected, message, "deepEqual");
+};
+
+function _deepEqual(actual, expected) {
+ // 7.1. All identical values are equivalent, as determined by ===.
+ if (actual === expected) {
+ return true;
+ // 7.2. If the expected value is a Date object, the actual value is
+ // equivalent if it is also a Date object that refers to the same time.
+ } else if (instanceOf(actual, "Date") && instanceOf(expected, "Date")) {
+ return actual.getTime() === expected.getTime();
+ // 7.3 If the expected value is a RegExp object, the actual value is
+ // equivalent if it is also a RegExp object with the same source and
+ // properties (`global`, `multiline`, `lastIndex`, `ignoreCase`).
+ } else if (instanceOf(actual, "RegExp") && instanceOf(expected, "RegExp")) {
+ return actual.source === expected.source &&
+ actual.global === expected.global &&
+ actual.multiline === expected.multiline &&
+ actual.lastIndex === expected.lastIndex &&
+ actual.ignoreCase === expected.ignoreCase;
+ // 7.4. Other pairs that do not both pass typeof value == "object",
+ // equivalence is determined by ==.
+ } else if (typeof actual != "object" && typeof expected != "object") {
+ return actual == expected;
+ // 7.5 For all other Object pairs, including Array objects, equivalence is
+ // determined by having the same number of owned properties (as verified
+ // with Object.prototype.hasOwnProperty.call), the same set of keys
+ // (although not necessarily the same order), equivalent values for every
+ // corresponding key, and an identical 'prototype' property. Note: this
+ // accounts for both named and indexed properties on Arrays.
+ } else {
+ return objEquiv(actual, expected);
+ }
+}
+
+function isUndefinedOrNull(value) {
+ return value === null || value === undefined;
+}
+
+function isArguments(object) {
+ return instanceOf(object, "Arguments");
+}
+
+function objEquiv(a, b) {
+ if (isUndefinedOrNull(a) || isUndefinedOrNull(b)) {
+ return false;
+ }
+ // An identical 'prototype' property.
+ if (a.prototype !== b.prototype) {
+ return false;
+ }
+ // Object.keys may be broken through screwy arguments passing. Converting to
+ // an array solves the problem.
+ if (isArguments(a)) {
+ if (!isArguments(b)) {
+ return false;
+ }
+ a = pSlice.call(a);
+ b = pSlice.call(b);
+ return _deepEqual(a, b);
+ }
+ let ka, kb, key, i;
+ try {
+ ka = Object.keys(a);
+ kb = Object.keys(b);
+ } catch (e) {
+ // Happens when one is a string literal and the other isn't
+ return false;
+ }
+ // Having the same number of owned properties (keys incorporates
+ // hasOwnProperty)
+ if (ka.length != kb.length)
+ return false;
+ // The same set of keys (although not necessarily the same order),
+ ka.sort();
+ kb.sort();
+ // Equivalent values for every corresponding key, and possibly expensive deep
+ // test
+ for (i = ka.length - 1; i >= 0; i--) {
+ key = ka[i];
+ if (!_deepEqual(a[key], b[key])) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/**
+ * 8. The non-equivalence assertion tests for any deep inequality.
+ * assert.notDeepEqual(actual, expected, message_opt);
+ *
+ * @param actual
+ * (mixed) Test subject to be evaluated as NOT equivalent to `expected`, including nested properties
+ * @param expected
+ * (mixed) Test reference to evaluate against `actual`
+ * @param message (optional)
+ * (string) Short explanation of the expected result
+ */
+proto.notDeepEqual = function notDeepEqual(actual, expected, message) {
+ this.report(_deepEqual(actual, expected), actual, expected, message, "notDeepEqual");
+};
+
+/**
+ * 9. The strict equality assertion tests strict equality, as determined by ===.
+ * assert.strictEqual(actual, expected, message_opt);
+ *
+ * @param actual
+ * (mixed) Test subject to be evaluated as strictly equivalent to `expected`
+ * @param expected
+ * (mixed) Test reference to evaluate against `actual`
+ * @param message (optional)
+ * (string) Short explanation of the expected result
+ */
+proto.strictEqual = function strictEqual(actual, expected, message) {
+ this.report(actual !== expected, actual, expected, message, "===");
+};
+
+/**
+ * 10. The strict non-equality assertion tests for strict inequality, as
+ * determined by !==. assert.notStrictEqual(actual, expected, message_opt);
+ *
+ * @param actual
+ * (mixed) Test subject to be evaluated as NOT strictly equivalent to `expected`
+ * @param expected
+ * (mixed) Test reference to evaluate against `actual`
+ * @param message (optional)
+ * (string) Short explanation of the expected result
+ */
+proto.notStrictEqual = function notStrictEqual(actual, expected, message) {
+ this.report(actual === expected, actual, expected, message, "!==");
+};
+
+function expectedException(actual, expected) {
+ if (!actual || !expected) {
+ return false;
+ }
+
+ if (instanceOf(expected, "RegExp")) {
+ return expected.test(actual);
+ } else if (actual instanceof expected) {
+ return true;
+ } else if (expected.call({}, actual) === true) {
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * 11. Expected to throw an error:
+ * assert.throws(block, Error_opt, message_opt);
+ *
+ * @param block
+ * (function) Function block to evaluate and catch eventual thrown errors
+ * @param expected (optional)
+ * (mixed) Test reference to evaluate against the thrown result from `block`
+ * @param message (optional)
+ * (string) Short explanation of the expected result
+ */
+proto.throws = function(block, expected, message) {
+ let actual;
+
+ if (typeof expected === "string") {
+ message = expected;
+ expected = null;
+ }
+
+ try {
+ block();
+ } catch (e) {
+ actual = e;
+ }
+
+ message = (expected && expected.name ? " (" + expected.name + ")." : ".") +
+ (message ? " " + message : ".");
+
+ if (!actual) {
+ this.report(true, actual, expected, "Missing expected exception" + message);
+ }
+
+ if ((actual && expected && !expectedException(actual, expected))) {
+ throw actual;
+ }
+
+ this.report(false, expected, expected, message);
+};
diff --git a/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/modules/MockColorPicker.jsm b/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/modules/MockColorPicker.jsm
new file mode 100644
index 0000000..ab9f00b
--- /dev/null
+++ b/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/modules/MockColorPicker.jsm
@@ -0,0 +1,125 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = ["MockColorPicker"];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cm = Components.manager;
+const Cu = Components.utils;
+
+const CONTRACT_ID = "@mozilla.org/colorpicker;1";
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
+var oldClassID = "", oldFactory = null;
+var newClassID = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator).generateUUID();
+var newFactory = function (window) {
+ return {
+ createInstance: function(aOuter, aIID) {
+ if (aOuter)
+ throw Components.results.NS_ERROR_NO_AGGREGATION;
+ return new MockColorPickerInstance(window).QueryInterface(aIID);
+ },
+ lockFactory: function(aLock) {
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory])
+ };
+}
+
+this.MockColorPicker = {
+ init: function(window) {
+ this.reset();
+ this.factory = newFactory(window);
+ if (!registrar.isCIDRegistered(newClassID)) {
+ try {
+ oldClassID = registrar.contractIDToCID(CONTRACT_ID);
+ oldFactory = Cm.getClassObject(Cc[CONTRACT_ID], Ci.nsIFactory);
+ } catch(ex) {
+ oldClassID = "";
+ oldFactory = null;
+ dump("TEST-INFO | can't get colorpicker registered component, " +
+ "assuming there is none");
+ }
+ if (oldClassID != "" && oldFactory != null) {
+ registrar.unregisterFactory(oldClassID, oldFactory);
+ }
+ registrar.registerFactory(newClassID, "", CONTRACT_ID, this.factory);
+ }
+ },
+
+ reset: function() {
+ this.returnColor = "";
+ this.showCallback = null;
+ this.shown = false;
+ this.showing = false;
+ },
+
+ cleanup: function() {
+ var previousFactory = this.factory;
+ this.reset();
+ this.factory = null;
+
+ registrar.unregisterFactory(newClassID, previousFactory);
+ if (oldClassID != "" && oldFactory != null) {
+ registrar.registerFactory(oldClassID, "", CONTRACT_ID, oldFactory);
+ }
+ }
+};
+
+function MockColorPickerInstance(window) {
+ this.window = window;
+};
+MockColorPickerInstance.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIColorPicker]),
+ init: function(aParent, aTitle, aInitialColor) {
+ this.parent = aParent;
+ this.initialColor = aInitialColor;
+ },
+ initialColor: "",
+ parent: null,
+ open: function(aColorPickerShownCallback) {
+ MockColorPicker.showing = true;
+ MockColorPicker.shown = true;
+
+ this.window.setTimeout(function() {
+ let result = "";
+ try {
+ if (typeof MockColorPicker.showCallback == "function") {
+ var updateCb = function(color) {
+ result = color;
+ aColorPickerShownCallback.update(color);
+ };
+ let returnColor = MockColorPicker.showCallback(this, updateCb);
+ if (typeof returnColor === "string") {
+ result = returnColor;
+ }
+ } else if (typeof MockColorPicker.returnColor === "string") {
+ result = MockColorPicker.returnColor;
+ }
+ } catch(ex) {
+ dump("TEST-UNEXPECTED-FAIL | Exception in MockColorPicker.jsm open() " +
+ "method: " + ex + "\n");
+ }
+ if (aColorPickerShownCallback) {
+ aColorPickerShownCallback.done(result);
+ }
+ }.bind(this), 0);
+ }
+};
+
+// Expose everything to content. We call reset() here so that all of the
+// relevant lazy expandos get added.
+MockColorPicker.reset();
+function exposeAll(obj) {
+ var props = {};
+ for (var prop in obj)
+ props[prop] = 'rw';
+ obj.__exposedProps__ = props;
+}
+exposeAll(MockColorPicker);
+exposeAll(MockColorPickerInstance.prototype);
diff --git a/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/modules/MockFilePicker.jsm b/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/modules/MockFilePicker.jsm
new file mode 100644
index 0000000..f7b130e
--- /dev/null
+++ b/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/modules/MockFilePicker.jsm
@@ -0,0 +1,236 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = ["MockFilePicker"];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cm = Components.manager;
+const Cu = Components.utils;
+
+const CONTRACT_ID = "@mozilla.org/filepicker;1";
+
+Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
+var oldClassID, oldFactory;
+var newClassID = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator).generateUUID();
+var newFactory = function (window) {
+ return {
+ createInstance: function(aOuter, aIID) {
+ if (aOuter)
+ throw Components.results.NS_ERROR_NO_AGGREGATION;
+ return new MockFilePickerInstance(window).QueryInterface(aIID);
+ },
+ lockFactory: function(aLock) {
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory])
+ };
+}
+
+this.MockFilePicker = {
+ returnOK: Ci.nsIFilePicker.returnOK,
+ returnCancel: Ci.nsIFilePicker.returnCancel,
+ returnReplace: Ci.nsIFilePicker.returnReplace,
+
+ filterAll: Ci.nsIFilePicker.filterAll,
+ filterHTML: Ci.nsIFilePicker.filterHTML,
+ filterText: Ci.nsIFilePicker.filterText,
+ filterImages: Ci.nsIFilePicker.filterImages,
+ filterXML: Ci.nsIFilePicker.filterXML,
+ filterXUL: Ci.nsIFilePicker.filterXUL,
+ filterApps: Ci.nsIFilePicker.filterApps,
+ filterAllowURLs: Ci.nsIFilePicker.filterAllowURLs,
+ filterAudio: Ci.nsIFilePicker.filterAudio,
+ filterVideo: Ci.nsIFilePicker.filterVideo,
+
+ window: null,
+
+ init: function(window) {
+ this.window = window;
+
+ this.reset();
+ this.factory = newFactory(window);
+ if (!registrar.isCIDRegistered(newClassID)) {
+ oldClassID = registrar.contractIDToCID(CONTRACT_ID);
+ oldFactory = Cm.getClassObject(Cc[CONTRACT_ID], Ci.nsIFactory);
+ registrar.unregisterFactory(oldClassID, oldFactory);
+ registrar.registerFactory(newClassID, "", CONTRACT_ID, this.factory);
+ }
+ },
+
+ reset: function() {
+ this.appendFilterCallback = null;
+ this.appendFiltersCallback = null;
+ this.displayDirectory = null;
+ this.filterIndex = 0;
+ this.mode = null;
+ this.returnFiles = [];
+ this.returnValue = null;
+ this.showCallback = null;
+ this.shown = false;
+ this.showing = false;
+ },
+
+ cleanup: function() {
+ var previousFactory = this.factory;
+ this.reset();
+ this.factory = null;
+ if (oldFactory) {
+ registrar.unregisterFactory(newClassID, previousFactory);
+ registrar.registerFactory(oldClassID, "", CONTRACT_ID, oldFactory);
+ }
+ },
+
+ useAnyFile: function() {
+ var file = FileUtils.getDir("TmpD", [], false);
+ file.append("testfile");
+ file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0644);
+ this.returnFiles = [file];
+ },
+
+ useBlobFile: function() {
+ var blob = new this.window.Blob([]);
+ var file = new this.window.File(blob, { name: 'helloworld.txt', type: 'plain/text' });
+ this.returnFiles = [file];
+ },
+
+ isNsIFile: function(aFile) {
+ let ret = false;
+ try {
+ if (aFile.QueryInterface(Ci.nsIFile))
+ ret = true;
+ } catch(e) {}
+
+ return ret;
+ }
+};
+
+function MockFilePickerInstance(window) {
+ this.window = window;
+};
+MockFilePickerInstance.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIFilePicker]),
+ init: function(aParent, aTitle, aMode) {
+ MockFilePicker.mode = aMode;
+ this.filterIndex = MockFilePicker.filterIndex;
+ this.parent = aParent;
+ },
+ appendFilter: function(aTitle, aFilter) {
+ if (typeof MockFilePicker.appendFilterCallback == "function")
+ MockFilePicker.appendFilterCallback(this, aTitle, aFilter);
+ },
+ appendFilters: function(aFilterMask) {
+ if (typeof MockFilePicker.appendFiltersCallback == "function")
+ MockFilePicker.appendFiltersCallback(this, aFilterMask);
+ },
+ defaultString: "",
+ defaultExtension: "",
+ parent: null,
+ filterIndex: 0,
+ displayDirectory: null,
+ get file() {
+ if (MockFilePicker.returnFiles.length >= 1 &&
+ // window.File does not implement nsIFile
+ MockFilePicker.isNsIFile(MockFilePicker.returnFiles[0])) {
+ return MockFilePicker.returnFiles[0];
+ }
+
+ return null;
+ },
+ get domfile() {
+ if (MockFilePicker.returnFiles.length >= 1) {
+ // window.File does not implement nsIFile
+ if (!MockFilePicker.isNsIFile(MockFilePicker.returnFiles[0])) {
+ return MockFilePicker.returnFiles[0];
+ }
+
+ let utils = this.parent.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ return utils.wrapDOMFile(MockFilePicker.returnFiles[0]);
+ }
+ return null;
+ },
+ get fileURL() {
+ if (MockFilePicker.returnFiles.length >= 1 &&
+ // window.File does not implement nsIFile
+ MockFilePicker.isNsIFile(MockFilePicker.returnFiles[0])) {
+ return Services.io.newFileURI(MockFilePicker.returnFiles[0]);
+ }
+
+ return null;
+ },
+ get files() {
+ return {
+ index: 0,
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsISimpleEnumerator]),
+ hasMoreElements: function() {
+ return this.index < MockFilePicker.returnFiles.length;
+ },
+ getNext: function() {
+ // window.File does not implement nsIFile
+ if (!MockFilePicker.isNsIFile(MockFilePicker.returnFiles[this.index])) {
+ return null;
+ }
+ return MockFilePicker.returnFiles[this.index++];
+ }
+ };
+ },
+ get domfiles() {
+ let utils = this.parent.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ return {
+ index: 0,
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsISimpleEnumerator]),
+ hasMoreElements: function() {
+ return this.index < MockFilePicker.returnFiles.length;
+ },
+ getNext: function() {
+ // window.File does not implement nsIFile
+ if (!MockFilePicker.isNsIFile(MockFilePicker.returnFiles[this.index])) {
+ return MockFilePicker.returnFiles[this.index++];
+ }
+ return utils.wrapDOMFile(MockFilePicker.returnFiles[this.index++]);
+ }
+ };
+ },
+ show: function() {
+ MockFilePicker.displayDirectory = this.displayDirectory;
+ MockFilePicker.shown = true;
+ if (typeof MockFilePicker.showCallback == "function") {
+ var returnValue = MockFilePicker.showCallback(this);
+ if (typeof returnValue != "undefined")
+ return returnValue;
+ }
+ return MockFilePicker.returnValue;
+ },
+ open: function(aFilePickerShownCallback) {
+ MockFilePicker.showing = true;
+ this.window.setTimeout(function() {
+ let result = Components.interfaces.nsIFilePicker.returnCancel;
+ try {
+ result = this.show();
+ } catch(ex) {
+ }
+ if (aFilePickerShownCallback) {
+ aFilePickerShownCallback.done(result);
+ }
+ }.bind(this), 0);
+ }
+};
+
+// Expose everything to content. We call reset() here so that all of the relevant
+// lazy expandos get added.
+MockFilePicker.reset();
+function exposeAll(obj) {
+ var props = {};
+ for (var prop in obj)
+ props[prop] = 'rw';
+ obj.__exposedProps__ = props;
+}
+exposeAll(MockFilePicker);
+exposeAll(MockFilePickerInstance.prototype);
diff --git a/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/modules/MockPermissionPrompt.jsm b/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/modules/MockPermissionPrompt.jsm
new file mode 100644
index 0000000..e07f8e0
--- /dev/null
+++ b/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/modules/MockPermissionPrompt.jsm
@@ -0,0 +1,97 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = ["MockPermissionPrompt"];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cm = Components.manager;
+const Cu = Components.utils;
+
+const CONTRACT_ID = "@mozilla.org/content-permission/prompt;1";
+
+Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
+var oldClassID, oldFactory;
+var newClassID = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator).generateUUID();
+var newFactory = {
+ createInstance: function(aOuter, aIID) {
+ if (aOuter)
+ throw Components.results.NS_ERROR_NO_AGGREGATION;
+ return new MockPermissionPromptInstance().QueryInterface(aIID);
+ },
+ lockFactory: function(aLock) {
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory])
+};
+
+this.MockPermissionPrompt = {
+ init: function() {
+ this.reset();
+ if (!registrar.isCIDRegistered(newClassID)) {
+ try {
+ oldClassID = registrar.contractIDToCID(CONTRACT_ID);
+ oldFactory = Cm.getClassObject(Cc[CONTRACT_ID], Ci.nsIFactory);
+ } catch (ex) {
+ oldClassID = "";
+ oldFactory = null;
+ dump("TEST-INFO | can't get permission prompt registered component, " +
+ "assuming there is none");
+ }
+ if (oldFactory) {
+ registrar.unregisterFactory(oldClassID, oldFactory);
+ }
+ registrar.registerFactory(newClassID, "", CONTRACT_ID, newFactory);
+ }
+ },
+
+ reset: function() {
+ },
+
+ cleanup: function() {
+ this.reset();
+ if (oldFactory) {
+ registrar.unregisterFactory(newClassID, newFactory);
+ registrar.registerFactory(oldClassID, "", CONTRACT_ID, oldFactory);
+ }
+ },
+};
+
+function MockPermissionPromptInstance() { };
+MockPermissionPromptInstance.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPermissionPrompt]),
+
+ promptResult: Ci.nsIPermissionManager.UNKNOWN_ACTION,
+
+ prompt: function(request) {
+
+ let perms = request.types.QueryInterface(Ci.nsIArray);
+ for (let idx = 0; idx < perms.length; idx++) {
+ let perm = perms.queryElementAt(idx, Ci.nsIContentPermissionType);
+ if (Services.perms.testExactPermissionFromPrincipal(
+ request.principal, perm.type) != Ci.nsIPermissionManager.ALLOW_ACTION) {
+ request.cancel();
+ return;
+ }
+ }
+
+ request.allow();
+ }
+};
+
+// Expose everything to content. We call reset() here so that all of the relevant
+// lazy expandos get added.
+MockPermissionPrompt.reset();
+function exposeAll(obj) {
+ var props = {};
+ for (var prop in obj)
+ props[prop] = 'rw';
+ obj.__exposedProps__ = props;
+}
+exposeAll(MockPermissionPrompt);
+exposeAll(MockPermissionPromptInstance.prototype);
diff --git a/test/resources/firefox/extensions/special-powers at mozilla.org/components/SpecialPowersObserver.js b/test/resources/firefox/extensions/special-powers at mozilla.org/components/SpecialPowersObserver.js
old mode 100755
new mode 100644
index 90655e2..7adc637
--- a/test/resources/firefox/extensions/special-powers at mozilla.org/components/SpecialPowersObserver.js
+++ b/test/resources/firefox/extensions/special-powers at mozilla.org/components/SpecialPowersObserver.js
@@ -1,40 +1,6 @@
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Special Powers code
- *
- * The Initial Developer of the Original Code is
- * Mozilla Foundation.
- * Portions created by the Initial Developer are Copyright (C) 2010
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- * Jesse Ruderman <jruderman at mozilla.com>
- * Robert Sayre <sayrer at gmail.com>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK *****/
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Based on:
// https://bugzilla.mozilla.org/show_bug.cgi?id=549539
@@ -50,34 +16,33 @@ const Cc = Components.classes;
const Ci = Components.interfaces;
const CHILD_SCRIPT = "chrome://specialpowers/content/specialpowers.js"
+const CHILD_SCRIPT_API = "chrome://specialpowers/content/specialpowersAPI.js"
+const CHILD_LOGGER_SCRIPT = "chrome://specialpowers/content/MozillaLogger.js"
-/**
- * Special Powers Exception - used to throw exceptions nicely
- **/
-function SpecialPowersException(aMsg) {
- this.message = aMsg;
- this.name = "SpecialPowersException";
-}
-SpecialPowersException.prototype.toString = function() {
- return this.name + ': "' + this.message + '"';
-};
+// Glue to add in the observer API to this object. This allows us to share code with chrome tests
+var loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Components.interfaces.mozIJSSubScriptLoader);
+loader.loadSubScript("chrome://specialpowers/content/SpecialPowersObserverAPI.js");
/* XPCOM gunk */
-function SpecialPowersObserver() {
+this.SpecialPowersObserver = function SpecialPowersObserver() {
this._isFrameScriptLoaded = false;
+ this._mmIsGlobal = true;
this._messageManager = Cc["@mozilla.org/globalmessagemanager;1"].
- getService(Ci.nsIChromeFrameMessageManager);
+ getService(Ci.nsIMessageBroadcaster);
}
-SpecialPowersObserver.prototype = {
- classDescription: "Special powers Observer for use in testing.",
- classID: Components.ID("{59a52458-13e0-4d93-9d85-a637344f29a1}"),
- contractID: "@mozilla.org/special-powers-observer;1",
- QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIObserver]),
- _xpcom_categories: [{category: "profile-after-change", service: true }],
- observe: function(aSubject, aTopic, aData)
+SpecialPowersObserver.prototype = new SpecialPowersObserverAPI();
+
+ SpecialPowersObserver.prototype.classDescription = "Special powers Observer for use in testing.";
+ SpecialPowersObserver.prototype.classID = Components.ID("{59a52458-13e0-4d93-9d85-a637344f29a1}");
+ SpecialPowersObserver.prototype.contractID = "@mozilla.org/special-powers-observer;1";
+ SpecialPowersObserver.prototype.QueryInterface = XPCOMUtils.generateQI([Components.interfaces.nsIObserver]);
+ SpecialPowersObserver.prototype._xpcom_categories = [{category: "profile-after-change", service: true }];
+
+ SpecialPowersObserver.prototype.observe = function(aSubject, aTopic, aData)
{
switch (aTopic) {
case "profile-after-change":
@@ -90,190 +55,105 @@ SpecialPowersObserver.prototype = {
this._messageManager.addMessageListener("SPPrefService", this);
this._messageManager.addMessageListener("SPProcessCrashService", this);
this._messageManager.addMessageListener("SPPingService", this);
-
+ this._messageManager.addMessageListener("SpecialPowers.Quit", this);
+ this._messageManager.addMessageListener("SpecialPowers.Focus", this);
+ this._messageManager.addMessageListener("SPPermissionManager", this);
+ this._messageManager.addMessageListener("SPWebAppService", this);
+ this._messageManager.addMessageListener("SPObserverService", this);
+ this._messageManager.addMessageListener("SPLoadChromeScript", this);
+ this._messageManager.addMessageListener("SPChromeScriptMessage", this);
+
+ this._messageManager.loadFrameScript(CHILD_LOGGER_SCRIPT, true);
+ this._messageManager.loadFrameScript(CHILD_SCRIPT_API, true);
this._messageManager.loadFrameScript(CHILD_SCRIPT, true);
this._isFrameScriptLoaded = true;
}
break;
+ case "http-on-modify-request":
+ if (aSubject instanceof Ci.nsIChannel) {
+ let uri = aSubject.URI.spec;
+ this._sendAsyncMessage("specialpowers-http-notify-request", { uri: uri });
+ }
+ break;
+
case "xpcom-shutdown":
this.uninit();
break;
- case "plugin-crashed":
- case "ipc:content-shutdown":
- function addDumpIDToMessage(propertyName) {
- var id = aSubject.getPropertyAsAString(propertyName);
- if (id) {
- message.dumpIDs.push(id);
- }
- }
-
- var message = { type: "crash-observed", dumpIDs: [] };
- aSubject = aSubject.QueryInterface(Ci.nsIPropertyBag2);
- if (aTopic == "plugin-crashed") {
- addDumpIDToMessage("pluginDumpID");
- addDumpIDToMessage("browserDumpID");
- } else { // ipc:content-shutdown
- addDumpIDToMessage("dumpID");
- }
- this._messageManager.sendAsyncMessage("SPProcessCrashService", message);
+ default:
+ this._observe(aSubject, aTopic, aData);
break;
}
- },
+ };
- init: function()
+ SpecialPowersObserver.prototype._sendAsyncMessage = function(msgname, msg)
+ {
+ if (this._mmIsGlobal) {
+ this._messageManager.broadcastAsyncMessage(msgname, msg);
+ }
+ else {
+ this._messageManager.sendAsyncMessage(msgname, msg);
+ }
+ };
+
+ SpecialPowersObserver.prototype._receiveMessage = function(aMessage) {
+ return this._receiveMessageAPI(aMessage);
+ };
+
+ SpecialPowersObserver.prototype.init = function(messageManager)
{
var obs = Services.obs;
obs.addObserver(this, "xpcom-shutdown", false);
obs.addObserver(this, "chrome-document-global-created", false);
- },
+ obs.addObserver(this, "http-on-modify-request", false);
- uninit: function()
+ if (messageManager) {
+ this._messageManager = messageManager;
+ this._mmIsGlobal = false;
+ }
+ };
+
+ SpecialPowersObserver.prototype.uninit = function()
{
var obs = Services.obs;
- obs.removeObserver(this, "chrome-document-global-created", false);
- this.removeProcessCrashObservers();
- },
-
- addProcessCrashObservers: function() {
+ obs.removeObserver(this, "chrome-document-global-created");
+ obs.removeObserver(this, "http-on-modify-request");
+ this._removeProcessCrashObservers();
+ };
+
+ SpecialPowersObserver.prototype._addProcessCrashObservers = function() {
if (this._processCrashObserversRegistered) {
return;
}
- Services.obs.addObserver(this, "plugin-crashed", false);
- Services.obs.addObserver(this, "ipc:content-shutdown", false);
+ var obs = Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService);
+
+ obs.addObserver(this, "plugin-crashed", false);
+ obs.addObserver(this, "ipc:content-shutdown", false);
this._processCrashObserversRegistered = true;
- },
+ };
- removeProcessCrashObservers: function() {
+ SpecialPowersObserver.prototype._removeProcessCrashObservers = function() {
if (!this._processCrashObserversRegistered) {
return;
}
- Services.obs.removeObserver(this, "plugin-crashed");
- Services.obs.removeObserver(this, "ipc:content-shutdown");
- this._processCrashObserversRegistered = false;
- },
+ var obs = Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService);
- getCrashDumpDir: function() {
- if (!this._crashDumpDir) {
- var directoryService = Cc["@mozilla.org/file/directory_service;1"]
- .getService(Ci.nsIProperties);
- this._crashDumpDir = directoryService.get("ProfD", Ci.nsIFile);
- this._crashDumpDir.append("minidumps");
- }
- return this._crashDumpDir;
- },
-
- deleteCrashDumpFiles: function(aFilenames) {
- var crashDumpDir = this.getCrashDumpDir();
- if (!crashDumpDir.exists()) {
- return false;
- }
-
- var success = aFilenames.length != 0;
- aFilenames.forEach(function(crashFilename) {
- var file = crashDumpDir.clone();
- file.append(crashFilename);
- if (file.exists()) {
- file.remove(false);
- } else {
- success = false;
- }
- });
- return success;
- },
-
- findCrashDumpFiles: function(aToIgnore) {
- var crashDumpDir = this.getCrashDumpDir();
- var entries = crashDumpDir.exists() && crashDumpDir.directoryEntries;
- if (!entries) {
- return [];
- }
-
- var crashDumpFiles = [];
- while (entries.hasMoreElements()) {
- var file = entries.getNext().QueryInterface(Ci.nsIFile);
- var path = String(file.path);
- if (path.match(/\.(dmp|extra)$/) && !aToIgnore[path]) {
- crashDumpFiles.push(path);
- }
- }
- return crashDumpFiles.concat();
- },
+ obs.removeObserver(this, "plugin-crashed");
+ obs.removeObserver(this, "ipc:content-shutdown");
+ this._processCrashObserversRegistered = false;
+ };
/**
* messageManager callback function
* This will get requests from our API in the window and process them in chrome for it
**/
- receiveMessage: function(aMessage) {
+ SpecialPowersObserver.prototype.receiveMessage = function(aMessage) {
switch(aMessage.name) {
- case "SPPrefService":
- var prefs = Services.prefs;
- var prefType = aMessage.json.prefType.toUpperCase();
- var prefName = aMessage.json.prefName;
- var prefValue = "prefValue" in aMessage.json ? aMessage.json.prefValue : null;
-
- if (aMessage.json.op == "get") {
- if (!prefName || !prefType)
- throw new SpecialPowersException("Invalid parameters for get in SPPrefService");
- } else if (aMessage.json.op == "set") {
- if (!prefName || !prefType || prefValue === null)
- throw new SpecialPowersException("Invalid parameters for set in SPPrefService");
- } else if (aMessage.json.op == "clear") {
- if (!prefName)
- throw new SpecialPowersException("Invalid parameters for clear in SPPrefService");
- } else {
- throw new SpecialPowersException("Invalid operation for SPPrefService");
- }
- // Now we make the call
- switch(prefType) {
- case "BOOL":
- if (aMessage.json.op == "get")
- return(prefs.getBoolPref(prefName));
- else
- return(prefs.setBoolPref(prefName, prefValue));
- case "INT":
- if (aMessage.json.op == "get")
- return(prefs.getIntPref(prefName));
- else
- return(prefs.setIntPref(prefName, prefValue));
- case "CHAR":
- if (aMessage.json.op == "get")
- return(prefs.getCharPref(prefName));
- else
- return(prefs.setCharPref(prefName, prefValue));
- case "COMPLEX":
- if (aMessage.json.op == "get")
- return(prefs.getComplexValue(prefName, prefValue[0]));
- else
- return(prefs.setComplexValue(prefName, prefValue[0], prefValue[1]));
- case "":
- if (aMessage.json.op == "clear") {
- prefs.clearUserPref(prefName);
- return;
- }
- }
- break;
-
- case "SPProcessCrashService":
- switch (aMessage.json.op) {
- case "register-observer":
- this.addProcessCrashObservers();
- break;
- case "unregister-observer":
- this.removeProcessCrashObservers();
- break;
- case "delete-crash-dump-files":
- return this.deleteCrashDumpFiles(aMessage.json.filenames);
- case "find-crash-dump-files":
- return this.findCrashDumpFiles(aMessage.json.crashDumpFilesToIgnore);
- default:
- throw new SpecialPowersException("Invalid operation for SPProcessCrashService");
- }
- break;
-
case "SPPingService":
if (aMessage.json.op == "ping") {
aMessage.target
@@ -283,11 +163,16 @@ SpecialPowersObserver.prototype = {
.sendAsyncMessage("SPPingService", { op: "pong" });
}
break;
-
+ case "SpecialPowers.Quit":
+ let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"].getService(Ci.nsIAppStartup);
+ appStartup.quit(Ci.nsIAppStartup.eForceQuit);
+ break;
+ case "SpecialPowers.Focus":
+ aMessage.target.focus();
+ break;
default:
- throw new SpecialPowersException("Unrecognized Special Powers API");
+ return this._receiveMessage(aMessage);
}
- }
-};
+ };
-const NSGetFactory = XPCOMUtils.generateNSGetFactory([SpecialPowersObserver]);
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SpecialPowersObserver]);
diff --git a/test/resources/firefox/prefs.js b/test/resources/firefox/prefs.js
index 8d59d66..3433843 100644
--- a/test/resources/firefox/prefs.js
+++ b/test/resources/firefox/prefs.js
@@ -1 +1,2 @@
user_pref("browser.shell.checkDefaultBrowser", false);
+user_pref('extensions.autoDisableScopes', 14);
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/pdf.js.git
More information about the Pkg-javascript-commits
mailing list