Merge pull request #4640 from yurydelendik/special-powers

Fix special powers add-on for firefox.
This commit is contained in:
Yury Delendik 2014-04-17 08:06:54 -05:00
commit 2c6692050b
14 changed files with 3378 additions and 538 deletions

View File

@ -352,9 +352,16 @@ function snapshotCurrentPage(task, failure) {
});
}
function sendQuitRequest() {
function sendQuitRequest(cb) {
var r = new XMLHttpRequest();
r.open('POST', '/tellMeToQuit?path=' + escape(appPath), false);
r.onreadystatechange = function sendQuitRequestOnreadystatechange(e) {
if (r.readyState == 4) {
if (cb) {
cb();
}
}
};
r.send(null);
}
@ -362,12 +369,13 @@ function quitApp() {
log('Done !');
document.body.innerHTML = 'Tests are finished. <h1>CLOSE ME!</h1>' +
document.body.innerHTML;
if (window.SpecialPowers) {
SpecialPowers.quitApplication();
} else {
sendQuitRequest();
window.close();
}
sendQuitRequest(function () {
if (window.SpecialPowers) {
SpecialPowers.quit();
} else {
window.close();
}
});
}
function done() {

View File

@ -1,4 +1,5 @@
content specialpowers chrome/specialpowers/content/
component {59a52458-13e0-4d93-9d85-a637344f29a1} components/SpecialPowersObserver.js
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
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}
resource specialpowers chrome/specialpowers/modules/

View File

@ -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;

View File

@ -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;
}
};

View File

@ -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@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 = new SpecialPowersAPI();
SpecialPowers.prototype = {
toString: function() { return "[SpecialPowers]"; },
sanityCheck: function() { return "foo"; },
SpecialPowers.prototype.toString = function() { return "[SpecialPowers]"; };
SpecialPowers.prototype.sanityCheck = function() { return "foo"; };
// This gets filled in in the constructor.
DOMWindowUtils: undefined,
// This gets filled in in the constructor.
SpecialPowers.prototype.DOMWindowUtils = undefined;
SpecialPowers.prototype.Components = 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));
},
SpecialPowers.prototype._sendSyncMessage = function(msgname, msg) {
return sendSyncMessage(msgname, msg);
};
// 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._sendAsyncMessage = function(msgname, msg) {
sendAsyncMessage(msgname, msg);
};
// Mimic the clearUserPref API
clearUserPref: function(aPrefName) {
var msg = {'op':'clear', 'prefName': aPrefName, 'prefType': ""};
sendSyncMessage('SPPrefService', msg);
},
SpecialPowers.prototype._addMessageListener = function(msgname, listener) {
addMessageListener(msgname, listener);
};
// 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]);
},
SpecialPowers.prototype._removeMessageListener = function(msgname, listener) {
removeMessageListener(msgname, listener);
};
//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");
},
SpecialPowers.prototype.registerProcessCrashObservers = function() {
addMessageListener("SPProcessCrashService", this._messageListener);
sendSyncMessage("SPProcessCrashService", { op: "register-observer" });
};
addChromeEventListener: function(type, listener, capture, allowUntrusted) {
addEventListener(type, listener, capture, allowUntrusted);
},
removeChromeEventListener: function(type, listener, capture) {
removeEventListener(type, listener, capture);
},
SpecialPowers.prototype.unregisterProcessCrashObservers = function() {
addMessageListener("SPProcessCrashService", this._messageListener);
sendSyncMessage("SPProcessCrashService", { op: "unregister-observer" });
};
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;
},
createSystemXHR: function() {
return Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance(Ci.nsIXMLHttpRequest);
},
gc: function() {
this.DOMWindowUtils.garbageCollect();
},
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;
}
},
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");
});
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;
}
break;
case "SPPingService":
if (aMessage.json.op == "pong") {
var handler = this._pongHandlers.shift();
if (handler) {
handler();
}
case "SPPingService":
if (aMessage.json.op == "pong") {
var handler = this._pongHandlers.shift();
if (handler) {
handler();
}
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;
},
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();
}
}, Ci.nsIThread.DISPATCH_NORMAL);
},
/* 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;
}
if (!canQuitApplication())
return false;
var appService = Cc['@mozilla.org/toolkit/app-startup;1'].getService(Ci.nsIAppStartup);
appService.quit(Ci.nsIAppStartup.eForceQuit);
return true;
break;
}
return true;
};
SpecialPowers.prototype.quit = function() {
sendAsyncMessage("SpecialPowers.Quit", {});
};
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;

View File

@ -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);
};

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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@mozilla.com>
* Robert Sayre <sayrer@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);
this._processCrashObserversRegistered = true;
},
var obs = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
removeProcessCrashObservers: function() {
obs.addObserver(this, "plugin-crashed", false);
obs.addObserver(this, "ipc:content-shutdown", false);
this._processCrashObserversRegistered = true;
};
SpecialPowersObserver.prototype._removeProcessCrashObservers = function() {
if (!this._processCrashObserversRegistered) {
return;
}
Services.obs.removeObserver(this, "plugin-crashed");
Services.obs.removeObserver(this, "ipc:content-shutdown");
var obs = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
obs.removeObserver(this, "plugin-crashed");
obs.removeObserver(this, "ipc:content-shutdown");
this._processCrashObserversRegistered = false;
},
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();
},
};
/**
* 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]);

View File

@ -1 +1,2 @@
user_pref("browser.shell.checkDefaultBrowser", false);
user_pref('extensions.autoDisableScopes', 14);

View File

@ -5,7 +5,7 @@
var TestReporter = function(browser, appPath) {
'use strict';
function send(action, json) {
function send(action, json, cb) {
var r = new XMLHttpRequest();
// (The POST URI is ignored atm.)
r.open('POST', action, true);
@ -14,7 +14,11 @@ var TestReporter = function(browser, appPath) {
if (r.readyState == 4) {
// Retry until successful
if (r.status !== 200) {
send(action, json);
send(action, json, cb);
} else {
if (cb) {
cb();
}
}
}
};
@ -38,7 +42,11 @@ var TestReporter = function(browser, appPath) {
}
function sendQuitRequest() {
send('/tellMeToQuit?path=' + escape(appPath), {});
send('/tellMeToQuit?path=' + escape(appPath), {}, function () {
if (window.SpecialPowers) {
SpecialPowers.quit();
}
});
}
this.now = function() {

View File

@ -74,18 +74,14 @@ WebBrowser.prototype = {
try {
testUtils.removeDirSync(this.tmpDir);
this.process = null;
callback();
if (callback) {
callback();
}
} catch (e) {
console.error('Unable to cleanup after the process: ' + e);
try {
if (this.process) {
var pid = this.process.pid;
if (process.platform === 'win32') {
// kill does not really work on windows
spawn('taskkill', ['-F', '-PID', pid]).on('exit', callback);
} else {
spawn('kill', ['-s', 'SIGKILL', pid]).on('exit', callback);
}
this.process.kill('SIGKILL');
}
} catch (e) {}
}