diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /testing/specialpowers/content/SpecialPowersObserverAPI.js | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'testing/specialpowers/content/SpecialPowersObserverAPI.js')
-rw-r--r-- | testing/specialpowers/content/SpecialPowersObserverAPI.js | 635 |
1 files changed, 635 insertions, 0 deletions
diff --git a/testing/specialpowers/content/SpecialPowersObserverAPI.js b/testing/specialpowers/content/SpecialPowersObserverAPI.js new file mode 100644 index 000000000..f37f7bf0e --- /dev/null +++ b/testing/specialpowers/content/SpecialPowersObserverAPI.js @@ -0,0 +1,635 @@ +/* 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"); +Components.utils.import("resource://gre/modules/NetUtil.jsm"); + +if (typeof(Ci) == 'undefined') { + var Ci = Components.interfaces; +} + +if (typeof(Cc) == 'undefined') { + var Cc = Components.classes; +} + +this.SpecialPowersError = function(aMsg) { + Error.call(this); + let {stack} = new Error(); + this.message = aMsg; + this.name = "SpecialPowersError"; +} +SpecialPowersError.prototype = Object.create(Error.prototype); + +SpecialPowersError.prototype.toString = function() { + return `${this.name}: ${this.message}`; +}; + +this.SpecialPowersObserverAPI = function SpecialPowersObserverAPI() { + this._crashDumpDir = null; + this._processCrashObserversRegistered = false; + this._chromeScriptListeners = []; + this._extensions = new Map(); +} + +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); +} + +function getTestPlugin(pluginName) { + var ph = Cc["@mozilla.org/plugin/host;1"] + .getService(Ci.nsIPluginHost); + var tags = ph.getPluginTags(); + var name = pluginName || "Test Plug-in"; + for (var tag of tags) { + if (tag.name == name) { + return tag; + } + } + + return null; +} + +SpecialPowersObserverAPI.prototype = { + + _observe: function(aSubject, aTopic, aData) { + function addDumpIDToMessage(propertyName) { + try { + var id = aSubject.getPropertyAsAString(propertyName); + } catch(ex) { + var id = null; + } + 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 = NetUtil.newChannel({ + uri: aUrl, + loadUsingSystemPrincipal: true + }); + var input = channel.open2(); + 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 SpecialPowersError( + "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; + }, + + _sendReply: function(aMessage, aReplyName, aReplyMsg) { + let mm = aMessage.target + .QueryInterface(Ci.nsIFrameLoaderOwner) + .frameLoader + .messageManager; + mm.sendAsyncMessage(aReplyName, aReplyMsg); + }, + + _notifyCategoryAndObservers: function(subject, topic, data) { + const serviceMarker = "service,"; + + // First create observers from the category manager. + let cm = + Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager); + let enumerator = cm.enumerateCategory(topic); + + let observers = []; + + while (enumerator.hasMoreElements()) { + let entry = + enumerator.getNext().QueryInterface(Ci.nsISupportsCString).data; + let contractID = cm.getCategoryEntry(topic, entry); + + let factoryFunction; + if (contractID.substring(0, serviceMarker.length) == serviceMarker) { + contractID = contractID.substring(serviceMarker.length); + factoryFunction = "getService"; + } + else { + factoryFunction = "createInstance"; + } + + try { + let handler = Cc[contractID][factoryFunction](); + if (handler) { + let observer = handler.QueryInterface(Ci.nsIObserver); + observers.push(observer); + } + } catch(e) { } + } + + // Next enumerate the registered observers. + enumerator = Services.obs.enumerateObservers(topic); + while (enumerator.hasMoreElements()) { + try { + let observer = enumerator.getNext().QueryInterface(Ci.nsIObserver); + if (observers.indexOf(observer) == -1) { + observers.push(observer); + } + } catch (e) { } + } + + observers.forEach(function (observer) { + try { + observer.observe(subject, topic, data); + } catch(e) { } + }); + }, + + /** + * 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": { + let prefs = Services.prefs; + let prefType = aMessage.json.prefType.toUpperCase(); + let prefName = aMessage.json.prefName; + let prefValue = "prefValue" in aMessage.json ? aMessage.json.prefValue : null; + + if (aMessage.json.op == "get") { + if (!prefName || !prefType) + throw new SpecialPowersError("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 SpecialPowersError("Invalid parameters for set in SPPrefService"); + } else if (aMessage.json.op == "clear") { + if (!prefName) + throw new SpecialPowersError("Invalid parameters for clear in SPPrefService"); + } else { + throw new SpecialPowersError("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 SpecialPowersError("Invalid operation for SPProcessCrashService"); + } + return undefined; // See comment at the beginning of this function. + } + + case "SPPermissionManager": { + let msg = aMessage.json; + let principal = msg.principal; + + switch (msg.op) { + case "add": + Services.perms.addFromPrincipal(principal, msg.type, msg.permission, msg.expireType, msg.expireTime); + break; + case "remove": + Services.perms.removeFromPrincipal(principal, msg.type); + break; + case "has": + let hasPerm = Services.perms.testPermissionFromPrincipal(principal, msg.type); + return hasPerm == Ci.nsIPermissionManager.ALLOW_ACTION; + case "test": + let testPerm = Services.perms.testPermissionFromPrincipal(principal, msg.type, msg.value); + return testPerm == msg.value; + default: + throw new SpecialPowersError( + "Invalid operation for SPPermissionManager"); + } + return undefined; // See comment at the beginning of this function. + } + + case "SPSetTestPluginEnabledState": { + var plugin = getTestPlugin(aMessage.data.pluginName); + if (!plugin) { + return undefined; + } + var oldEnabledState = plugin.enabledState; + plugin.enabledState = aMessage.data.newEnabledState; + return oldEnabledState; + } + + case "SPObserverService": { + let topic = aMessage.json.observerTopic; + switch (aMessage.json.op) { + case "notify": + let data = aMessage.json.observerData + Services.obs.notifyObservers(null, topic, data); + break; + case "add": + this._registerObservers._self = this; + this._registerObservers._add(topic); + break; + default: + throw new SpecialPowersError("Invalid operation for SPObserverervice"); + } + return undefined; // See comment at the beginning of this function. + } + + case "SPLoadChromeScript": { + let id = aMessage.json.id; + let jsScript; + let scriptName; + + if (aMessage.json.url) { + jsScript = this._readUrlAsString(aMessage.json.url); + scriptName = aMessage.json.url; + } else if (aMessage.json.function) { + jsScript = aMessage.json.function.body; + scriptName = aMessage.json.function.name + || "<loadChromeScript anonymous function>"; + } else { + throw new SpecialPowersError("SPLoadChromeScript: Invalid script"); + } + + // Setup a chrome sandbox that has access to sendAsyncMessage + // and addMessageListener in order to communicate with + // the mochitest. + let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal(); + let sb = Components.utils.Sandbox(systemPrincipal); + let 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 }); + }; + sb.browserElement = aMessage.target; + + // Also expose assertion functions + let reporter = function (err, message, stack) { + // Pipe assertions back to parent process + mm.sendAsyncMessage("SPChromeScriptAssert", + { id, name: scriptName, err, message, + stack }); + }; + Object.defineProperty(sb, "assert", { + get: function () { + let scope = Components.utils.createObjectIn(sb); + Services.scriptloader.loadSubScript("chrome://specialpowers/content/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", scriptName, 1); + } catch(e) { + throw new SpecialPowersError( + "Error while executing chrome script '" + scriptName + "':\n" + + e + "\n" + + e.fileName + ":" + e.lineNumber); + } + return undefined; // See comment at the beginning of this function. + } + + case "SPChromeScriptMessage": { + let id = aMessage.json.id; + let name = aMessage.json.name; + let message = aMessage.json.message; + return this._chromeScriptListeners + .filter(o => (o.name == name && o.id == id)) + .map(o => o.listener(message)); + } + + case "SPImportInMainProcess": { + var message = { hadError: false, errorMessage: null }; + try { + Components.utils.import(aMessage.data); + } catch (e) { + message.hadError = true; + message.errorMessage = e.toString(); + } + return message; + } + + case "SPCleanUpSTSData": { + let origin = aMessage.data.origin; + let flags = aMessage.data.flags; + let uri = Services.io.newURI(origin, null, null); + let sss = Cc["@mozilla.org/ssservice;1"]. + getService(Ci.nsISiteSecurityService); + sss.removeState(Ci.nsISiteSecurityService.HEADER_HSTS, uri, flags); + return undefined; + } + + case "SPLoadExtension": { + let {Extension} = Components.utils.import("resource://gre/modules/Extension.jsm", {}); + + let id = aMessage.data.id; + let ext = aMessage.data.ext; + let extension = Extension.generate(ext); + + let resultListener = (...args) => { + this._sendReply(aMessage, "SPExtensionMessage", {id, type: "testResult", args}); + }; + + let messageListener = (...args) => { + args.shift(); + this._sendReply(aMessage, "SPExtensionMessage", {id, type: "testMessage", args}); + }; + + // Register pass/fail handlers. + extension.on("test-result", resultListener); + extension.on("test-eq", resultListener); + extension.on("test-log", resultListener); + extension.on("test-done", resultListener); + + extension.on("test-message", messageListener); + + this._extensions.set(id, extension); + return undefined; + } + + case "SPStartupExtension": { + let {ExtensionData, Management} = Components.utils.import("resource://gre/modules/Extension.jsm", {}); + + let id = aMessage.data.id; + let extension = this._extensions.get(id); + let startupListener = (msg, ext) => { + if (ext == extension) { + this._sendReply(aMessage, "SPExtensionMessage", {id, type: "extensionSetId", args: [extension.id]}); + Management.off("startup", startupListener); + } + }; + Management.on("startup", startupListener); + + // Make sure the extension passes the packaging checks when + // they're run on a bare archive rather than a running instance, + // as the add-on manager runs them. + let extensionData = new ExtensionData(extension.rootURI); + extensionData.readManifest().then( + () => { + return extensionData.initAllLocales().then(() => { + if (extensionData.errors.length) { + return Promise.reject("Extension contains packaging errors"); + } + }); + }, + () => { + // readManifest() will throw if we're loading an embedded + // extension, so don't worry about locale errors in that + // case. + } + ).then(() => { + return extension.startup(); + }).then(() => { + this._sendReply(aMessage, "SPExtensionMessage", {id, type: "extensionStarted", args: []}); + }).catch(e => { + dump(`Extension startup failed: ${e}\n${e.stack}`); + Management.off("startup", startupListener); + this._sendReply(aMessage, "SPExtensionMessage", {id, type: "extensionFailed", args: []}); + }); + return undefined; + } + + case "SPExtensionMessage": { + let id = aMessage.data.id; + let extension = this._extensions.get(id); + extension.testMessage(...aMessage.data.args); + return undefined; + } + + case "SPUnloadExtension": { + let id = aMessage.data.id; + let extension = this._extensions.get(id); + this._extensions.delete(id); + extension.shutdown(); + this._sendReply(aMessage, "SPExtensionMessage", {id, type: "extensionUnloaded", args: []}); + return undefined; + } + + case "SPClearAppPrivateData": { + let appId = aMessage.data.appId; + let browserOnly = aMessage.data.browserOnly; + + let attributes = { appId: appId }; + if (browserOnly) { + attributes.inIsolatedMozBrowser = true; + } + this._notifyCategoryAndObservers(null, + "clear-origin-attributes-data", + JSON.stringify(attributes)); + + let subject = { + appId: appId, + browserOnly: browserOnly, + QueryInterface: XPCOMUtils.generateQI([Ci.mozIApplicationClearPrivateDataParams]) + }; + this._notifyCategoryAndObservers(subject, "webapps-clear-data", null); + + return undefined; + } + + default: + throw new SpecialPowersError("Unrecognized Special Powers API"); + } + + // We throw an exception before reaching this explicit return because + // we should never be arriving here anyway. + throw new SpecialPowersError("Unreached code"); + return undefined; + } +}; |