testing/specialpowers/content/SpecialPowersObserverAPI.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4 "use strict";
michael@0 5
michael@0 6 Components.utils.import("resource://gre/modules/Services.jsm");
michael@0 7
michael@0 8 if (typeof(Ci) == 'undefined') {
michael@0 9 var Ci = Components.interfaces;
michael@0 10 }
michael@0 11
michael@0 12 if (typeof(Cc) == 'undefined') {
michael@0 13 var Cc = Components.classes;
michael@0 14 }
michael@0 15
michael@0 16 /**
michael@0 17 * Special Powers Exception - used to throw exceptions nicely
michael@0 18 **/
michael@0 19 function SpecialPowersException(aMsg) {
michael@0 20 this.message = aMsg;
michael@0 21 this.name = "SpecialPowersException";
michael@0 22 }
michael@0 23
michael@0 24 SpecialPowersException.prototype.toString = function() {
michael@0 25 return this.name + ': "' + this.message + '"';
michael@0 26 };
michael@0 27
michael@0 28 this.SpecialPowersObserverAPI = function SpecialPowersObserverAPI() {
michael@0 29 this._crashDumpDir = null;
michael@0 30 this._processCrashObserversRegistered = false;
michael@0 31 this._chromeScriptListeners = [];
michael@0 32 }
michael@0 33
michael@0 34 function parseKeyValuePairs(text) {
michael@0 35 var lines = text.split('\n');
michael@0 36 var data = {};
michael@0 37 for (let i = 0; i < lines.length; i++) {
michael@0 38 if (lines[i] == '')
michael@0 39 continue;
michael@0 40
michael@0 41 // can't just .split() because the value might contain = characters
michael@0 42 let eq = lines[i].indexOf('=');
michael@0 43 if (eq != -1) {
michael@0 44 let [key, value] = [lines[i].substring(0, eq),
michael@0 45 lines[i].substring(eq + 1)];
michael@0 46 if (key && value)
michael@0 47 data[key] = value.replace(/\\n/g, "\n").replace(/\\\\/g, "\\");
michael@0 48 }
michael@0 49 }
michael@0 50 return data;
michael@0 51 }
michael@0 52
michael@0 53 function parseKeyValuePairsFromFile(file) {
michael@0 54 var fstream = Cc["@mozilla.org/network/file-input-stream;1"].
michael@0 55 createInstance(Ci.nsIFileInputStream);
michael@0 56 fstream.init(file, -1, 0, 0);
michael@0 57 var is = Cc["@mozilla.org/intl/converter-input-stream;1"].
michael@0 58 createInstance(Ci.nsIConverterInputStream);
michael@0 59 is.init(fstream, "UTF-8", 1024, Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
michael@0 60 var str = {};
michael@0 61 var contents = '';
michael@0 62 while (is.readString(4096, str) != 0) {
michael@0 63 contents += str.value;
michael@0 64 }
michael@0 65 is.close();
michael@0 66 fstream.close();
michael@0 67 return parseKeyValuePairs(contents);
michael@0 68 }
michael@0 69
michael@0 70 SpecialPowersObserverAPI.prototype = {
michael@0 71
michael@0 72 _observe: function(aSubject, aTopic, aData) {
michael@0 73 function addDumpIDToMessage(propertyName) {
michael@0 74 try {
michael@0 75 var id = aSubject.getPropertyAsAString(propertyName);
michael@0 76 } catch(ex) {
michael@0 77 var id = null;
michael@0 78 }
michael@0 79 if (id) {
michael@0 80 message.dumpIDs.push({id: id, extension: "dmp"});
michael@0 81 message.dumpIDs.push({id: id, extension: "extra"});
michael@0 82 }
michael@0 83 }
michael@0 84
michael@0 85 switch(aTopic) {
michael@0 86 case "plugin-crashed":
michael@0 87 case "ipc:content-shutdown":
michael@0 88 var message = { type: "crash-observed", dumpIDs: [] };
michael@0 89 aSubject = aSubject.QueryInterface(Ci.nsIPropertyBag2);
michael@0 90 if (aTopic == "plugin-crashed") {
michael@0 91 addDumpIDToMessage("pluginDumpID");
michael@0 92 addDumpIDToMessage("browserDumpID");
michael@0 93
michael@0 94 let pluginID = aSubject.getPropertyAsAString("pluginDumpID");
michael@0 95 let extra = this._getExtraData(pluginID);
michael@0 96 if (extra && ("additional_minidumps" in extra)) {
michael@0 97 let dumpNames = extra.additional_minidumps.split(',');
michael@0 98 for (let name of dumpNames) {
michael@0 99 message.dumpIDs.push({id: pluginID + "-" + name, extension: "dmp"});
michael@0 100 }
michael@0 101 }
michael@0 102 } else { // ipc:content-shutdown
michael@0 103 addDumpIDToMessage("dumpID");
michael@0 104 }
michael@0 105 this._sendAsyncMessage("SPProcessCrashService", message);
michael@0 106 break;
michael@0 107 }
michael@0 108 },
michael@0 109
michael@0 110 _getCrashDumpDir: function() {
michael@0 111 if (!this._crashDumpDir) {
michael@0 112 this._crashDumpDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
michael@0 113 this._crashDumpDir.append("minidumps");
michael@0 114 }
michael@0 115 return this._crashDumpDir;
michael@0 116 },
michael@0 117
michael@0 118 _getExtraData: function(dumpId) {
michael@0 119 let extraFile = this._getCrashDumpDir().clone();
michael@0 120 extraFile.append(dumpId + ".extra");
michael@0 121 if (!extraFile.exists()) {
michael@0 122 return null;
michael@0 123 }
michael@0 124 return parseKeyValuePairsFromFile(extraFile);
michael@0 125 },
michael@0 126
michael@0 127 _deleteCrashDumpFiles: function(aFilenames) {
michael@0 128 var crashDumpDir = this._getCrashDumpDir();
michael@0 129 if (!crashDumpDir.exists()) {
michael@0 130 return false;
michael@0 131 }
michael@0 132
michael@0 133 var success = aFilenames.length != 0;
michael@0 134 aFilenames.forEach(function(crashFilename) {
michael@0 135 var file = crashDumpDir.clone();
michael@0 136 file.append(crashFilename);
michael@0 137 if (file.exists()) {
michael@0 138 file.remove(false);
michael@0 139 } else {
michael@0 140 success = false;
michael@0 141 }
michael@0 142 });
michael@0 143 return success;
michael@0 144 },
michael@0 145
michael@0 146 _findCrashDumpFiles: function(aToIgnore) {
michael@0 147 var crashDumpDir = this._getCrashDumpDir();
michael@0 148 var entries = crashDumpDir.exists() && crashDumpDir.directoryEntries;
michael@0 149 if (!entries) {
michael@0 150 return [];
michael@0 151 }
michael@0 152
michael@0 153 var crashDumpFiles = [];
michael@0 154 while (entries.hasMoreElements()) {
michael@0 155 var file = entries.getNext().QueryInterface(Ci.nsIFile);
michael@0 156 var path = String(file.path);
michael@0 157 if (path.match(/\.(dmp|extra)$/) && !aToIgnore[path]) {
michael@0 158 crashDumpFiles.push(path);
michael@0 159 }
michael@0 160 }
michael@0 161 return crashDumpFiles.concat();
michael@0 162 },
michael@0 163
michael@0 164 _getURI: function (url) {
michael@0 165 return Services.io.newURI(url, null, null);
michael@0 166 },
michael@0 167
michael@0 168 _readUrlAsString: function(aUrl) {
michael@0 169 // Fetch script content as we can't use scriptloader's loadSubScript
michael@0 170 // to evaluate http:// urls...
michael@0 171 var scriptableStream = Cc["@mozilla.org/scriptableinputstream;1"]
michael@0 172 .getService(Ci.nsIScriptableInputStream);
michael@0 173 var channel = Services.io.newChannel(aUrl, null, null);
michael@0 174 var input = channel.open();
michael@0 175 scriptableStream.init(input);
michael@0 176
michael@0 177 var str;
michael@0 178 var buffer = [];
michael@0 179
michael@0 180 while ((str = scriptableStream.read(4096))) {
michael@0 181 buffer.push(str);
michael@0 182 }
michael@0 183
michael@0 184 var output = buffer.join("");
michael@0 185
michael@0 186 scriptableStream.close();
michael@0 187 input.close();
michael@0 188
michael@0 189 var status;
michael@0 190 try {
michael@0 191 channel.QueryInterface(Ci.nsIHttpChannel);
michael@0 192 status = channel.responseStatus;
michael@0 193 } catch(e) {
michael@0 194 /* The channel is not a nsIHttpCHannel, but that's fine */
michael@0 195 dump("-*- _readUrlAsString: Got an error while fetching " +
michael@0 196 "chrome script '" + aUrl + "': (" + e.name + ") " + e.message + ". " +
michael@0 197 "Ignoring.\n");
michael@0 198 }
michael@0 199
michael@0 200 if (status == 404) {
michael@0 201 throw new SpecialPowersException(
michael@0 202 "Error while executing chrome script '" + aUrl + "':\n" +
michael@0 203 "The script doesn't exists. Ensure you have registered it in " +
michael@0 204 "'support-files' in your mochitest.ini.");
michael@0 205 }
michael@0 206
michael@0 207 return output;
michael@0 208 },
michael@0 209
michael@0 210 /**
michael@0 211 * messageManager callback function
michael@0 212 * This will get requests from our API in the window and process them in chrome for it
michael@0 213 **/
michael@0 214 _receiveMessageAPI: function(aMessage) {
michael@0 215 // We explicitly return values in the below code so that this function
michael@0 216 // doesn't trigger a flurry of warnings about "does not always return
michael@0 217 // a value".
michael@0 218 switch(aMessage.name) {
michael@0 219 case "SPPrefService":
michael@0 220 var prefs = Services.prefs;
michael@0 221 var prefType = aMessage.json.prefType.toUpperCase();
michael@0 222 var prefName = aMessage.json.prefName;
michael@0 223 var prefValue = "prefValue" in aMessage.json ? aMessage.json.prefValue : null;
michael@0 224
michael@0 225 if (aMessage.json.op == "get") {
michael@0 226 if (!prefName || !prefType)
michael@0 227 throw new SpecialPowersException("Invalid parameters for get in SPPrefService");
michael@0 228
michael@0 229 // return null if the pref doesn't exist
michael@0 230 if (prefs.getPrefType(prefName) == prefs.PREF_INVALID)
michael@0 231 return null;
michael@0 232 } else if (aMessage.json.op == "set") {
michael@0 233 if (!prefName || !prefType || prefValue === null)
michael@0 234 throw new SpecialPowersException("Invalid parameters for set in SPPrefService");
michael@0 235 } else if (aMessage.json.op == "clear") {
michael@0 236 if (!prefName)
michael@0 237 throw new SpecialPowersException("Invalid parameters for clear in SPPrefService");
michael@0 238 } else {
michael@0 239 throw new SpecialPowersException("Invalid operation for SPPrefService");
michael@0 240 }
michael@0 241
michael@0 242 // Now we make the call
michael@0 243 switch(prefType) {
michael@0 244 case "BOOL":
michael@0 245 if (aMessage.json.op == "get")
michael@0 246 return(prefs.getBoolPref(prefName));
michael@0 247 else
michael@0 248 return(prefs.setBoolPref(prefName, prefValue));
michael@0 249 case "INT":
michael@0 250 if (aMessage.json.op == "get")
michael@0 251 return(prefs.getIntPref(prefName));
michael@0 252 else
michael@0 253 return(prefs.setIntPref(prefName, prefValue));
michael@0 254 case "CHAR":
michael@0 255 if (aMessage.json.op == "get")
michael@0 256 return(prefs.getCharPref(prefName));
michael@0 257 else
michael@0 258 return(prefs.setCharPref(prefName, prefValue));
michael@0 259 case "COMPLEX":
michael@0 260 if (aMessage.json.op == "get")
michael@0 261 return(prefs.getComplexValue(prefName, prefValue[0]));
michael@0 262 else
michael@0 263 return(prefs.setComplexValue(prefName, prefValue[0], prefValue[1]));
michael@0 264 case "":
michael@0 265 if (aMessage.json.op == "clear") {
michael@0 266 prefs.clearUserPref(prefName);
michael@0 267 return undefined;
michael@0 268 }
michael@0 269 }
michael@0 270 return undefined; // See comment at the beginning of this function.
michael@0 271
michael@0 272 case "SPProcessCrashService":
michael@0 273 switch (aMessage.json.op) {
michael@0 274 case "register-observer":
michael@0 275 this._addProcessCrashObservers();
michael@0 276 break;
michael@0 277 case "unregister-observer":
michael@0 278 this._removeProcessCrashObservers();
michael@0 279 break;
michael@0 280 case "delete-crash-dump-files":
michael@0 281 return this._deleteCrashDumpFiles(aMessage.json.filenames);
michael@0 282 case "find-crash-dump-files":
michael@0 283 return this._findCrashDumpFiles(aMessage.json.crashDumpFilesToIgnore);
michael@0 284 default:
michael@0 285 throw new SpecialPowersException("Invalid operation for SPProcessCrashService");
michael@0 286 }
michael@0 287 return undefined; // See comment at the beginning of this function.
michael@0 288
michael@0 289 case "SPPermissionManager":
michael@0 290 let msg = aMessage.json;
michael@0 291
michael@0 292 let secMan = Services.scriptSecurityManager;
michael@0 293 let principal = secMan.getAppCodebasePrincipal(this._getURI(msg.url), msg.appId, msg.isInBrowserElement);
michael@0 294
michael@0 295 switch (msg.op) {
michael@0 296 case "add":
michael@0 297 Services.perms.addFromPrincipal(principal, msg.type, msg.permission);
michael@0 298 break;
michael@0 299 case "remove":
michael@0 300 Services.perms.removeFromPrincipal(principal, msg.type);
michael@0 301 break;
michael@0 302 case "has":
michael@0 303 let hasPerm = Services.perms.testPermissionFromPrincipal(principal, msg.type);
michael@0 304 if (hasPerm == Ci.nsIPermissionManager.ALLOW_ACTION)
michael@0 305 return true;
michael@0 306 return false;
michael@0 307 break;
michael@0 308 case "test":
michael@0 309 let testPerm = Services.perms.testPermissionFromPrincipal(principal, msg.type, msg.value);
michael@0 310 if (testPerm == msg.value) {
michael@0 311 return true;
michael@0 312 }
michael@0 313 return false;
michael@0 314 break;
michael@0 315 default:
michael@0 316 throw new SpecialPowersException("Invalid operation for " +
michael@0 317 "SPPermissionManager");
michael@0 318 }
michael@0 319 return undefined; // See comment at the beginning of this function.
michael@0 320
michael@0 321 case "SPWebAppService":
michael@0 322 let Webapps = {};
michael@0 323 Components.utils.import("resource://gre/modules/Webapps.jsm", Webapps);
michael@0 324 switch (aMessage.json.op) {
michael@0 325 case "set-launchable":
michael@0 326 let val = Webapps.DOMApplicationRegistry.allAppsLaunchable;
michael@0 327 Webapps.DOMApplicationRegistry.allAppsLaunchable = aMessage.json.launchable;
michael@0 328 return val;
michael@0 329 default:
michael@0 330 throw new SpecialPowersException("Invalid operation for SPWebAppsService");
michael@0 331 }
michael@0 332 return undefined; // See comment at the beginning of this function.
michael@0 333
michael@0 334 case "SPObserverService":
michael@0 335 switch (aMessage.json.op) {
michael@0 336 case "notify":
michael@0 337 let topic = aMessage.json.observerTopic;
michael@0 338 let data = aMessage.json.observerData
michael@0 339 Services.obs.notifyObservers(null, topic, data);
michael@0 340 break;
michael@0 341 default:
michael@0 342 throw new SpecialPowersException("Invalid operation for SPObserverervice");
michael@0 343 }
michael@0 344 return undefined; // See comment at the beginning of this function.
michael@0 345
michael@0 346 case "SPLoadChromeScript":
michael@0 347 var url = aMessage.json.url;
michael@0 348 var id = aMessage.json.id;
michael@0 349
michael@0 350 var jsScript = this._readUrlAsString(url);
michael@0 351
michael@0 352 // Setup a chrome sandbox that has access to sendAsyncMessage
michael@0 353 // and addMessageListener in order to communicate with
michael@0 354 // the mochitest.
michael@0 355 var systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
michael@0 356 var sb = Components.utils.Sandbox(systemPrincipal);
michael@0 357 var mm = aMessage.target
michael@0 358 .QueryInterface(Ci.nsIFrameLoaderOwner)
michael@0 359 .frameLoader
michael@0 360 .messageManager;
michael@0 361 sb.sendAsyncMessage = (name, message) => {
michael@0 362 mm.sendAsyncMessage("SPChromeScriptMessage",
michael@0 363 { id: id, name: name, message: message });
michael@0 364 };
michael@0 365 sb.addMessageListener = (name, listener) => {
michael@0 366 this._chromeScriptListeners.push({ id: id, name: name, listener: listener });
michael@0 367 };
michael@0 368
michael@0 369 // Also expose assertion functions
michael@0 370 let reporter = function (err, message, stack) {
michael@0 371 // Pipe assertions back to parent process
michael@0 372 mm.sendAsyncMessage("SPChromeScriptAssert",
michael@0 373 { id: id, url: url, err: err, message: message,
michael@0 374 stack: stack });
michael@0 375 };
michael@0 376 Object.defineProperty(sb, "assert", {
michael@0 377 get: function () {
michael@0 378 let scope = Components.utils.createObjectIn(sb);
michael@0 379 Services.scriptloader.loadSubScript("resource://specialpowers/Assert.jsm",
michael@0 380 scope);
michael@0 381
michael@0 382 let assert = new scope.Assert(reporter);
michael@0 383 delete sb.assert;
michael@0 384 return sb.assert = assert;
michael@0 385 },
michael@0 386 configurable: true
michael@0 387 });
michael@0 388
michael@0 389 // Evaluate the chrome script
michael@0 390 try {
michael@0 391 Components.utils.evalInSandbox(jsScript, sb, "1.8", url, 1);
michael@0 392 } catch(e) {
michael@0 393 throw new SpecialPowersException("Error while executing chrome " +
michael@0 394 "script '" + url + "':\n" + e + "\n" +
michael@0 395 e.fileName + ":" + e.lineNumber);
michael@0 396 }
michael@0 397 return undefined; // See comment at the beginning of this function.
michael@0 398
michael@0 399 case "SPChromeScriptMessage":
michael@0 400 var id = aMessage.json.id;
michael@0 401 var name = aMessage.json.name;
michael@0 402 var message = aMessage.json.message;
michael@0 403 this._chromeScriptListeners
michael@0 404 .filter(o => (o.name == name && o.id == id))
michael@0 405 .forEach(o => o.listener(message));
michael@0 406 return undefined; // See comment at the beginning of this function.
michael@0 407
michael@0 408 default:
michael@0 409 throw new SpecialPowersException("Unrecognized Special Powers API");
michael@0 410 }
michael@0 411
michael@0 412 // We throw an exception before reaching this explicit return because
michael@0 413 // we should never be arriving here anyway.
michael@0 414 throw new SpecialPowersException("Unreached code");
michael@0 415 return undefined;
michael@0 416 }
michael@0 417 };
michael@0 418

mercurial