1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/testing/specialpowers/content/SpecialPowersObserverAPI.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,418 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 +"use strict"; 1.8 + 1.9 +Components.utils.import("resource://gre/modules/Services.jsm"); 1.10 + 1.11 +if (typeof(Ci) == 'undefined') { 1.12 + var Ci = Components.interfaces; 1.13 +} 1.14 + 1.15 +if (typeof(Cc) == 'undefined') { 1.16 + var Cc = Components.classes; 1.17 +} 1.18 + 1.19 +/** 1.20 + * Special Powers Exception - used to throw exceptions nicely 1.21 + **/ 1.22 +function SpecialPowersException(aMsg) { 1.23 + this.message = aMsg; 1.24 + this.name = "SpecialPowersException"; 1.25 +} 1.26 + 1.27 +SpecialPowersException.prototype.toString = function() { 1.28 + return this.name + ': "' + this.message + '"'; 1.29 +}; 1.30 + 1.31 +this.SpecialPowersObserverAPI = function SpecialPowersObserverAPI() { 1.32 + this._crashDumpDir = null; 1.33 + this._processCrashObserversRegistered = false; 1.34 + this._chromeScriptListeners = []; 1.35 +} 1.36 + 1.37 +function parseKeyValuePairs(text) { 1.38 + var lines = text.split('\n'); 1.39 + var data = {}; 1.40 + for (let i = 0; i < lines.length; i++) { 1.41 + if (lines[i] == '') 1.42 + continue; 1.43 + 1.44 + // can't just .split() because the value might contain = characters 1.45 + let eq = lines[i].indexOf('='); 1.46 + if (eq != -1) { 1.47 + let [key, value] = [lines[i].substring(0, eq), 1.48 + lines[i].substring(eq + 1)]; 1.49 + if (key && value) 1.50 + data[key] = value.replace(/\\n/g, "\n").replace(/\\\\/g, "\\"); 1.51 + } 1.52 + } 1.53 + return data; 1.54 +} 1.55 + 1.56 +function parseKeyValuePairsFromFile(file) { 1.57 + var fstream = Cc["@mozilla.org/network/file-input-stream;1"]. 1.58 + createInstance(Ci.nsIFileInputStream); 1.59 + fstream.init(file, -1, 0, 0); 1.60 + var is = Cc["@mozilla.org/intl/converter-input-stream;1"]. 1.61 + createInstance(Ci.nsIConverterInputStream); 1.62 + is.init(fstream, "UTF-8", 1024, Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER); 1.63 + var str = {}; 1.64 + var contents = ''; 1.65 + while (is.readString(4096, str) != 0) { 1.66 + contents += str.value; 1.67 + } 1.68 + is.close(); 1.69 + fstream.close(); 1.70 + return parseKeyValuePairs(contents); 1.71 +} 1.72 + 1.73 +SpecialPowersObserverAPI.prototype = { 1.74 + 1.75 + _observe: function(aSubject, aTopic, aData) { 1.76 + function addDumpIDToMessage(propertyName) { 1.77 + try { 1.78 + var id = aSubject.getPropertyAsAString(propertyName); 1.79 + } catch(ex) { 1.80 + var id = null; 1.81 + } 1.82 + if (id) { 1.83 + message.dumpIDs.push({id: id, extension: "dmp"}); 1.84 + message.dumpIDs.push({id: id, extension: "extra"}); 1.85 + } 1.86 + } 1.87 + 1.88 + switch(aTopic) { 1.89 + case "plugin-crashed": 1.90 + case "ipc:content-shutdown": 1.91 + var message = { type: "crash-observed", dumpIDs: [] }; 1.92 + aSubject = aSubject.QueryInterface(Ci.nsIPropertyBag2); 1.93 + if (aTopic == "plugin-crashed") { 1.94 + addDumpIDToMessage("pluginDumpID"); 1.95 + addDumpIDToMessage("browserDumpID"); 1.96 + 1.97 + let pluginID = aSubject.getPropertyAsAString("pluginDumpID"); 1.98 + let extra = this._getExtraData(pluginID); 1.99 + if (extra && ("additional_minidumps" in extra)) { 1.100 + let dumpNames = extra.additional_minidumps.split(','); 1.101 + for (let name of dumpNames) { 1.102 + message.dumpIDs.push({id: pluginID + "-" + name, extension: "dmp"}); 1.103 + } 1.104 + } 1.105 + } else { // ipc:content-shutdown 1.106 + addDumpIDToMessage("dumpID"); 1.107 + } 1.108 + this._sendAsyncMessage("SPProcessCrashService", message); 1.109 + break; 1.110 + } 1.111 + }, 1.112 + 1.113 + _getCrashDumpDir: function() { 1.114 + if (!this._crashDumpDir) { 1.115 + this._crashDumpDir = Services.dirsvc.get("ProfD", Ci.nsIFile); 1.116 + this._crashDumpDir.append("minidumps"); 1.117 + } 1.118 + return this._crashDumpDir; 1.119 + }, 1.120 + 1.121 + _getExtraData: function(dumpId) { 1.122 + let extraFile = this._getCrashDumpDir().clone(); 1.123 + extraFile.append(dumpId + ".extra"); 1.124 + if (!extraFile.exists()) { 1.125 + return null; 1.126 + } 1.127 + return parseKeyValuePairsFromFile(extraFile); 1.128 + }, 1.129 + 1.130 + _deleteCrashDumpFiles: function(aFilenames) { 1.131 + var crashDumpDir = this._getCrashDumpDir(); 1.132 + if (!crashDumpDir.exists()) { 1.133 + return false; 1.134 + } 1.135 + 1.136 + var success = aFilenames.length != 0; 1.137 + aFilenames.forEach(function(crashFilename) { 1.138 + var file = crashDumpDir.clone(); 1.139 + file.append(crashFilename); 1.140 + if (file.exists()) { 1.141 + file.remove(false); 1.142 + } else { 1.143 + success = false; 1.144 + } 1.145 + }); 1.146 + return success; 1.147 + }, 1.148 + 1.149 + _findCrashDumpFiles: function(aToIgnore) { 1.150 + var crashDumpDir = this._getCrashDumpDir(); 1.151 + var entries = crashDumpDir.exists() && crashDumpDir.directoryEntries; 1.152 + if (!entries) { 1.153 + return []; 1.154 + } 1.155 + 1.156 + var crashDumpFiles = []; 1.157 + while (entries.hasMoreElements()) { 1.158 + var file = entries.getNext().QueryInterface(Ci.nsIFile); 1.159 + var path = String(file.path); 1.160 + if (path.match(/\.(dmp|extra)$/) && !aToIgnore[path]) { 1.161 + crashDumpFiles.push(path); 1.162 + } 1.163 + } 1.164 + return crashDumpFiles.concat(); 1.165 + }, 1.166 + 1.167 + _getURI: function (url) { 1.168 + return Services.io.newURI(url, null, null); 1.169 + }, 1.170 + 1.171 + _readUrlAsString: function(aUrl) { 1.172 + // Fetch script content as we can't use scriptloader's loadSubScript 1.173 + // to evaluate http:// urls... 1.174 + var scriptableStream = Cc["@mozilla.org/scriptableinputstream;1"] 1.175 + .getService(Ci.nsIScriptableInputStream); 1.176 + var channel = Services.io.newChannel(aUrl, null, null); 1.177 + var input = channel.open(); 1.178 + scriptableStream.init(input); 1.179 + 1.180 + var str; 1.181 + var buffer = []; 1.182 + 1.183 + while ((str = scriptableStream.read(4096))) { 1.184 + buffer.push(str); 1.185 + } 1.186 + 1.187 + var output = buffer.join(""); 1.188 + 1.189 + scriptableStream.close(); 1.190 + input.close(); 1.191 + 1.192 + var status; 1.193 + try { 1.194 + channel.QueryInterface(Ci.nsIHttpChannel); 1.195 + status = channel.responseStatus; 1.196 + } catch(e) { 1.197 + /* The channel is not a nsIHttpCHannel, but that's fine */ 1.198 + dump("-*- _readUrlAsString: Got an error while fetching " + 1.199 + "chrome script '" + aUrl + "': (" + e.name + ") " + e.message + ". " + 1.200 + "Ignoring.\n"); 1.201 + } 1.202 + 1.203 + if (status == 404) { 1.204 + throw new SpecialPowersException( 1.205 + "Error while executing chrome script '" + aUrl + "':\n" + 1.206 + "The script doesn't exists. Ensure you have registered it in " + 1.207 + "'support-files' in your mochitest.ini."); 1.208 + } 1.209 + 1.210 + return output; 1.211 + }, 1.212 + 1.213 + /** 1.214 + * messageManager callback function 1.215 + * This will get requests from our API in the window and process them in chrome for it 1.216 + **/ 1.217 + _receiveMessageAPI: function(aMessage) { 1.218 + // We explicitly return values in the below code so that this function 1.219 + // doesn't trigger a flurry of warnings about "does not always return 1.220 + // a value". 1.221 + switch(aMessage.name) { 1.222 + case "SPPrefService": 1.223 + var prefs = Services.prefs; 1.224 + var prefType = aMessage.json.prefType.toUpperCase(); 1.225 + var prefName = aMessage.json.prefName; 1.226 + var prefValue = "prefValue" in aMessage.json ? aMessage.json.prefValue : null; 1.227 + 1.228 + if (aMessage.json.op == "get") { 1.229 + if (!prefName || !prefType) 1.230 + throw new SpecialPowersException("Invalid parameters for get in SPPrefService"); 1.231 + 1.232 + // return null if the pref doesn't exist 1.233 + if (prefs.getPrefType(prefName) == prefs.PREF_INVALID) 1.234 + return null; 1.235 + } else if (aMessage.json.op == "set") { 1.236 + if (!prefName || !prefType || prefValue === null) 1.237 + throw new SpecialPowersException("Invalid parameters for set in SPPrefService"); 1.238 + } else if (aMessage.json.op == "clear") { 1.239 + if (!prefName) 1.240 + throw new SpecialPowersException("Invalid parameters for clear in SPPrefService"); 1.241 + } else { 1.242 + throw new SpecialPowersException("Invalid operation for SPPrefService"); 1.243 + } 1.244 + 1.245 + // Now we make the call 1.246 + switch(prefType) { 1.247 + case "BOOL": 1.248 + if (aMessage.json.op == "get") 1.249 + return(prefs.getBoolPref(prefName)); 1.250 + else 1.251 + return(prefs.setBoolPref(prefName, prefValue)); 1.252 + case "INT": 1.253 + if (aMessage.json.op == "get") 1.254 + return(prefs.getIntPref(prefName)); 1.255 + else 1.256 + return(prefs.setIntPref(prefName, prefValue)); 1.257 + case "CHAR": 1.258 + if (aMessage.json.op == "get") 1.259 + return(prefs.getCharPref(prefName)); 1.260 + else 1.261 + return(prefs.setCharPref(prefName, prefValue)); 1.262 + case "COMPLEX": 1.263 + if (aMessage.json.op == "get") 1.264 + return(prefs.getComplexValue(prefName, prefValue[0])); 1.265 + else 1.266 + return(prefs.setComplexValue(prefName, prefValue[0], prefValue[1])); 1.267 + case "": 1.268 + if (aMessage.json.op == "clear") { 1.269 + prefs.clearUserPref(prefName); 1.270 + return undefined; 1.271 + } 1.272 + } 1.273 + return undefined; // See comment at the beginning of this function. 1.274 + 1.275 + case "SPProcessCrashService": 1.276 + switch (aMessage.json.op) { 1.277 + case "register-observer": 1.278 + this._addProcessCrashObservers(); 1.279 + break; 1.280 + case "unregister-observer": 1.281 + this._removeProcessCrashObservers(); 1.282 + break; 1.283 + case "delete-crash-dump-files": 1.284 + return this._deleteCrashDumpFiles(aMessage.json.filenames); 1.285 + case "find-crash-dump-files": 1.286 + return this._findCrashDumpFiles(aMessage.json.crashDumpFilesToIgnore); 1.287 + default: 1.288 + throw new SpecialPowersException("Invalid operation for SPProcessCrashService"); 1.289 + } 1.290 + return undefined; // See comment at the beginning of this function. 1.291 + 1.292 + case "SPPermissionManager": 1.293 + let msg = aMessage.json; 1.294 + 1.295 + let secMan = Services.scriptSecurityManager; 1.296 + let principal = secMan.getAppCodebasePrincipal(this._getURI(msg.url), msg.appId, msg.isInBrowserElement); 1.297 + 1.298 + switch (msg.op) { 1.299 + case "add": 1.300 + Services.perms.addFromPrincipal(principal, msg.type, msg.permission); 1.301 + break; 1.302 + case "remove": 1.303 + Services.perms.removeFromPrincipal(principal, msg.type); 1.304 + break; 1.305 + case "has": 1.306 + let hasPerm = Services.perms.testPermissionFromPrincipal(principal, msg.type); 1.307 + if (hasPerm == Ci.nsIPermissionManager.ALLOW_ACTION) 1.308 + return true; 1.309 + return false; 1.310 + break; 1.311 + case "test": 1.312 + let testPerm = Services.perms.testPermissionFromPrincipal(principal, msg.type, msg.value); 1.313 + if (testPerm == msg.value) { 1.314 + return true; 1.315 + } 1.316 + return false; 1.317 + break; 1.318 + default: 1.319 + throw new SpecialPowersException("Invalid operation for " + 1.320 + "SPPermissionManager"); 1.321 + } 1.322 + return undefined; // See comment at the beginning of this function. 1.323 + 1.324 + case "SPWebAppService": 1.325 + let Webapps = {}; 1.326 + Components.utils.import("resource://gre/modules/Webapps.jsm", Webapps); 1.327 + switch (aMessage.json.op) { 1.328 + case "set-launchable": 1.329 + let val = Webapps.DOMApplicationRegistry.allAppsLaunchable; 1.330 + Webapps.DOMApplicationRegistry.allAppsLaunchable = aMessage.json.launchable; 1.331 + return val; 1.332 + default: 1.333 + throw new SpecialPowersException("Invalid operation for SPWebAppsService"); 1.334 + } 1.335 + return undefined; // See comment at the beginning of this function. 1.336 + 1.337 + case "SPObserverService": 1.338 + switch (aMessage.json.op) { 1.339 + case "notify": 1.340 + let topic = aMessage.json.observerTopic; 1.341 + let data = aMessage.json.observerData 1.342 + Services.obs.notifyObservers(null, topic, data); 1.343 + break; 1.344 + default: 1.345 + throw new SpecialPowersException("Invalid operation for SPObserverervice"); 1.346 + } 1.347 + return undefined; // See comment at the beginning of this function. 1.348 + 1.349 + case "SPLoadChromeScript": 1.350 + var url = aMessage.json.url; 1.351 + var id = aMessage.json.id; 1.352 + 1.353 + var jsScript = this._readUrlAsString(url); 1.354 + 1.355 + // Setup a chrome sandbox that has access to sendAsyncMessage 1.356 + // and addMessageListener in order to communicate with 1.357 + // the mochitest. 1.358 + var systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal(); 1.359 + var sb = Components.utils.Sandbox(systemPrincipal); 1.360 + var mm = aMessage.target 1.361 + .QueryInterface(Ci.nsIFrameLoaderOwner) 1.362 + .frameLoader 1.363 + .messageManager; 1.364 + sb.sendAsyncMessage = (name, message) => { 1.365 + mm.sendAsyncMessage("SPChromeScriptMessage", 1.366 + { id: id, name: name, message: message }); 1.367 + }; 1.368 + sb.addMessageListener = (name, listener) => { 1.369 + this._chromeScriptListeners.push({ id: id, name: name, listener: listener }); 1.370 + }; 1.371 + 1.372 + // Also expose assertion functions 1.373 + let reporter = function (err, message, stack) { 1.374 + // Pipe assertions back to parent process 1.375 + mm.sendAsyncMessage("SPChromeScriptAssert", 1.376 + { id: id, url: url, err: err, message: message, 1.377 + stack: stack }); 1.378 + }; 1.379 + Object.defineProperty(sb, "assert", { 1.380 + get: function () { 1.381 + let scope = Components.utils.createObjectIn(sb); 1.382 + Services.scriptloader.loadSubScript("resource://specialpowers/Assert.jsm", 1.383 + scope); 1.384 + 1.385 + let assert = new scope.Assert(reporter); 1.386 + delete sb.assert; 1.387 + return sb.assert = assert; 1.388 + }, 1.389 + configurable: true 1.390 + }); 1.391 + 1.392 + // Evaluate the chrome script 1.393 + try { 1.394 + Components.utils.evalInSandbox(jsScript, sb, "1.8", url, 1); 1.395 + } catch(e) { 1.396 + throw new SpecialPowersException("Error while executing chrome " + 1.397 + "script '" + url + "':\n" + e + "\n" + 1.398 + e.fileName + ":" + e.lineNumber); 1.399 + } 1.400 + return undefined; // See comment at the beginning of this function. 1.401 + 1.402 + case "SPChromeScriptMessage": 1.403 + var id = aMessage.json.id; 1.404 + var name = aMessage.json.name; 1.405 + var message = aMessage.json.message; 1.406 + this._chromeScriptListeners 1.407 + .filter(o => (o.name == name && o.id == id)) 1.408 + .forEach(o => o.listener(message)); 1.409 + return undefined; // See comment at the beginning of this function. 1.410 + 1.411 + default: 1.412 + throw new SpecialPowersException("Unrecognized Special Powers API"); 1.413 + } 1.414 + 1.415 + // We throw an exception before reaching this explicit return because 1.416 + // we should never be arriving here anyway. 1.417 + throw new SpecialPowersException("Unreached code"); 1.418 + return undefined; 1.419 + } 1.420 +}; 1.421 +