11 changed files with 3346 additions and 518 deletions
@ -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 |
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} |
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/ |
||||||
|
@ -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; |
@ -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; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
File diff suppressed because it is too large
Load Diff
@ -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); |
||||||
|
}; |
@ -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); |
@ -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); |
@ -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); |
Loading…
Reference in new issue