testing/specialpowers/content/SpecialPowersObserverAPI.js

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

mercurial