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 /* This code is loaded in every child process that is started by mochitest.
9 var EXPORTED_SYMBOLS = ["SpecialPowersChild"];
11 var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
13 const { ExtensionUtils } = ChromeUtils.import(
14 "resource://gre/modules/ExtensionUtils.jsm"
17 ChromeUtils.defineModuleGetter(
20 "resource://specialpowers/MockFilePicker.jsm"
22 ChromeUtils.defineModuleGetter(
25 "resource://specialpowers/MockColorPicker.jsm"
27 ChromeUtils.defineModuleGetter(
29 "MockPermissionPrompt",
30 "resource://specialpowers/MockPermissionPrompt.jsm"
32 ChromeUtils.defineModuleGetter(
34 "SpecialPowersSandbox",
35 "resource://specialpowers/SpecialPowersSandbox.jsm"
37 ChromeUtils.defineModuleGetter(
40 "resource://specialpowers/WrapPrivileged.jsm"
42 ChromeUtils.defineModuleGetter(
44 "PrivateBrowsingUtils",
45 "resource://gre/modules/PrivateBrowsingUtils.jsm"
47 ChromeUtils.defineModuleGetter(
50 "resource://gre/modules/NetUtil.jsm"
52 ChromeUtils.defineModuleGetter(
55 "resource://gre/modules/AppConstants.jsm"
57 ChromeUtils.defineModuleGetter(
59 "PerTestCoverageUtils",
60 "resource://testing-common/PerTestCoverageUtils.jsm"
62 ChromeUtils.defineModuleGetter(
65 "resource://testing-common/ContentTaskUtils.jsm"
68 Cu.crashIfNotInAutomation();
70 function bindDOMWindowUtils(aWindow) {
71 return aWindow && WrapPrivileged.wrap(aWindow.windowUtils, aWindow);
74 function defineSpecialPowers(sp) {
75 let window = sp.contentWindow;
76 window.SpecialPowers = sp;
77 if (window === window.wrappedJSObject) {
80 // We can't use a generic |defineLazyGetter| because it does not
81 // allow customizing the re-definition behavior.
82 Object.defineProperty(window.wrappedJSObject, "SpecialPowers", {
84 let value = WrapPrivileged.wrap(sp, window);
85 // If we bind |window.wrappedJSObject| when defining the getter
86 // and use it here, it might become a dead wrapper.
87 // We have to retrieve |wrappedJSObject| again.
88 Object.defineProperty(window.wrappedJSObject, "SpecialPowers", {
101 // SPConsoleListener reflects nsIConsoleMessage objects into JS in a
102 // tidy, XPCOM-hiding way. Messages that are nsIScriptError objects
103 // have their properties exposed in detail. It also auto-unregisters
104 // itself when it receives a "sentinel" message.
105 function SPConsoleListener(callback, contentWindow) {
106 this.callback = callback;
107 this.contentWindow = contentWindow;
110 SPConsoleListener.prototype = {
111 // Overload the observe method for both nsIConsoleListener and nsIObserver.
112 // The topic will be null for nsIConsoleListener.
113 observe(msg, topic) {
115 message: msg.message,
124 isScriptError: false,
125 isConsoleEvent: false,
128 if (msg instanceof Ci.nsIScriptError) {
129 m.errorMessage = msg.errorMessage;
130 m.cssSelectors = msg.cssSelectors;
131 m.sourceName = msg.sourceName;
132 m.sourceLine = msg.sourceLine;
133 m.lineNumber = msg.lineNumber;
134 m.columnNumber = msg.columnNumber;
135 m.category = msg.category;
136 m.windowID = msg.outerWindowID;
137 m.innerWindowID = msg.innerWindowID;
138 m.isScriptError = true;
139 m.isWarning = (msg.flags & Ci.nsIScriptError.warningFlag) === 1;
140 } else if (topic === "console-api-log-event") {
141 // This is a dom/console event.
142 let unwrapped = msg.wrappedJSObject;
143 m.errorMessage = unwrapped.arguments[0];
144 m.sourceName = unwrapped.filename;
145 m.lineNumber = unwrapped.lineNumber;
146 m.columnNumber = unwrapped.columnNumber;
147 m.windowID = unwrapped.ID;
148 m.innerWindowID = unwrapped.innerID;
149 m.isConsoleEvent = true;
150 m.isWarning = unwrapped.level === "warning";
155 // Run in a separate runnable since console listeners aren't
156 // supposed to touch content and this one might.
157 Services.tm.dispatchToMainThread(() => {
158 this.callback.call(undefined, Cu.cloneInto(m, this.contentWindow));
161 if (!m.isScriptError && !m.isConsoleEvent && m.message === "SENTINEL") {
162 Services.obs.removeObserver(this, "console-api-log-event");
163 Services.console.unregisterListener(this);
167 QueryInterface: ChromeUtils.generateQI(["nsIConsoleListener", "nsIObserver"]),
170 class SpecialPowersChild extends JSWindowActorChild {
174 this._windowID = null;
176 this._encounteredCrashDumpFiles = [];
177 this._unexpectedCrashDumpFiles = {};
178 this._crashDumpDir = null;
179 this._serviceWorkerRegistered = false;
180 this._serviceWorkerCleanUpRequests = new Map();
181 Object.defineProperty(this, "Components", {
186 this._createFilesOnError = null;
187 this._createFilesOnSuccess = null;
189 this._messageListeners = new ExtensionUtils.DefaultMap(() => new Set());
191 this._consoleListeners = [];
192 this._spawnTaskImports = {};
193 this._encounteredCrashDumpFiles = [];
194 this._unexpectedCrashDumpFiles = {};
195 this._crashDumpDir = null;
197 this._asyncObservers = new WeakMap();
198 this._xpcomabi = null;
202 this._nextExtensionID = 0;
203 this._extensionListeners = null;
205 WrapPrivileged.disableAutoWrap(
209 this.wrapCallbackObject,
211 this.nondeterministicGetWeakMapKeys,
212 this.snapshotWindowWithOptions,
215 this.getDOMRequestService
219 observe(aSubject, aTopic, aData) {
220 // Ignore the "{chrome/content}-document-global-created" event. It
221 // is only observed to force creation of the actor.
225 this.attachToWindow();
229 let window = this.contentWindow;
230 // We should not invoke the getter.
231 if (!("SpecialPowers" in window.wrappedJSObject)) {
232 this._windowID = window.windowGlobalChild.innerWindowId;
234 defineSpecialPowers(this);
239 return this.contentWindow;
242 // Hack around devtools sometimes trying to JSON stringify us.
248 return "[SpecialPowers]";
254 _addMessageListener(msgname, listener) {
255 this._messageListeners.get(msgname).add(listener);
258 _removeMessageListener(msgname, listener) {
259 this._messageListeners.get(msgname).delete(listener);
262 receiveMessage(message) {
263 if (this._messageListeners.has(message.name)) {
264 for (let listener of this._messageListeners.get(message.name)) {
266 if (typeof listener === "function") {
269 listener.receiveMessage(message);
277 switch (message.name) {
278 case "SPProcessCrashService":
279 if (message.json.type == "crash-observed") {
280 for (let e of message.json.dumpIDs) {
281 this._encounteredCrashDumpFiles.push(e.id + "." + e.extension);
286 case "SPServiceWorkerRegistered":
287 this._serviceWorkerRegistered = message.data.registered;
290 case "SpecialPowers.FilesCreated":
291 var createdHandler = this._createFilesOnSuccess;
292 this._createFilesOnSuccess = null;
293 this._createFilesOnError = null;
294 if (createdHandler) {
295 createdHandler(Cu.cloneInto(message.data, this.contentWindow));
299 case "SpecialPowers.FilesError":
300 var errorHandler = this._createFilesOnError;
301 this._createFilesOnSuccess = null;
302 this._createFilesOnError = null;
304 errorHandler(message.data);
309 let { task, args, caller, taskId, imports } = message.data;
310 return this._spawnTask(task, args, caller, taskId, imports);
314 if ("info" in message.data) {
315 this.SimpleTest.info(message.data.info);
319 // An assertion has been done in a mochitest chrome script
320 let { name, passed, stack, diag, expectFail } = message.data;
322 let { SimpleTest } = this;
324 let expected = expectFail ? "fail" : "pass";
325 SimpleTest.record(passed, name, diag, stack, expected);
327 // Well, this is unexpected.
336 registerProcessCrashObservers() {
337 this.sendAsyncMessage("SPProcessCrashService", { op: "register-observer" });
340 unregisterProcessCrashObservers() {
341 this.sendAsyncMessage("SPProcessCrashService", {
342 op: "unregister-observer",
347 * Privileged object wrapping API
350 * var wrapper = SpecialPowers.wrap(obj);
351 * wrapper.privilegedMethod(); wrapper.privilegedProperty;
352 * obj === SpecialPowers.unwrap(wrapper);
354 * These functions provide transparent access to privileged objects using
355 * various pieces of deep SpiderMagic. Conceptually, a wrapper is just an
356 * object containing a reference to the underlying object, where all method
357 * calls and property accesses are transparently performed with the System
358 * Principal. Moreover, objects obtained from the wrapper (including properties
359 * and method return values) are wrapped automatically. Thus, after a single
360 * call to SpecialPowers.wrap(), the wrapper layer is transitively maintained.
364 * - The wrapping function does not preserve identity, so
365 * SpecialPowers.wrap(foo) !== SpecialPowers.wrap(foo). See bug 718543.
367 * - The wrapper cannot see expando properties on unprivileged DOM objects.
368 * That is to say, the wrapper uses Xray delegation.
370 * - The wrapper sometimes guesses certain ES5 attributes for returned
371 * properties. This is explained in a comment in the wrapper code above,
372 * and shouldn't be a problem.
378 return WrapPrivileged.unwrap(obj);
381 return WrapPrivileged.isWrapper(val);
385 * Wrap objects on a specified global.
388 return WrapPrivileged.wrap(obj, win);
392 * When content needs to pass a callback or a callback object to an API
393 * accessed over SpecialPowers, that API may sometimes receive arguments for
394 * whom it is forbidden to create a wrapper in content scopes. As such, we
395 * need a layer to wrap the values in SpecialPowers wrappers before they ever
399 return WrapPrivileged.wrapCallback(func, this.contentWindow);
401 wrapCallbackObject(obj) {
402 return WrapPrivileged.wrapCallbackObject(obj, this.contentWindow);
406 * Used for assigning a property to a SpecialPowers wrapper, without unwrapping
407 * the value that is assigned.
409 setWrapped(obj, prop, val) {
410 if (!WrapPrivileged.isWrapper(obj)) {
412 "You only need to use this for SpecialPowers wrapped objects"
416 obj = WrapPrivileged.unwrap(obj);
417 return Reflect.set(obj, prop, val);
421 * Create blank privileged objects to use as out-params for privileged functions.
423 createBlankObject() {
428 * Because SpecialPowers wrappers don't preserve identity, comparing with ==
429 * can be hazardous. Sometimes we can just unwrap to compare, but sometimes
430 * wrapping the underlying object into a content scope is forbidden. This
431 * function strips any wrappers if they exist and compare the underlying
435 return WrapPrivileged.unwrap(a) === WrapPrivileged.unwrap(b);
438 get MockFilePicker() {
439 return MockFilePicker;
442 get MockColorPicker() {
443 return MockColorPicker;
446 get MockPermissionPrompt() {
447 return MockPermissionPrompt;
451 this.sendAsyncMessage("SpecialPowers.Quit", {});
454 // fileRequests is an array of file requests. Each file request is an object.
455 // A request must have a field |name|, which gives the base of the name of the
456 // file to be created in the profile directory. If the request has a |data| field
457 // then that data will be written to the file.
458 createFiles(fileRequests, onCreation, onError) {
459 return this.sendQuery("SpecialPowers.CreateFiles", fileRequests).then(
460 files => onCreation(Cu.cloneInto(files, this.contentWindow)),
465 // Remove the files that were created using |SpecialPowers.createFiles()|.
466 // This will be automatically called by |SimpleTest.finish()|.
468 this.sendAsyncMessage("SpecialPowers.RemoveFiles", {});
471 executeAfterFlushingMessageQueue(aCallback) {
472 return this.sendQuery("Ping").then(aCallback);
475 async registeredServiceWorkers() {
476 // For the time being, if parent_intercept is false, we can assume that
477 // ServiceWorkers registered by the current test are all known to the SWM in
480 !Services.prefs.getBoolPref("dom.serviceWorkers.parent_intercept", false)
482 let swm = Cc["@mozilla.org/serviceworkers/manager;1"].getService(
483 Ci.nsIServiceWorkerManager
485 let regs = swm.getAllRegistrations();
487 // XXX This is shared with SpecialPowersAPIParent.jsm
488 let workers = new Array(regs.length);
489 for (let i = 0; i < workers.length; ++i) {
490 let { scope, scriptSpec } = regs.queryElementAt(
492 Ci.nsIServiceWorkerRegistrationInfo
494 workers[i] = { scope, scriptSpec };
500 // Please see the comment in SpecialPowersObserver.jsm above
501 // this._serviceWorkerListener's assignment for what this returns.
502 if (this._serviceWorkerRegistered) {
503 // This test registered at least one service worker. Send a synchronous
504 // call to the parent to make sure that it called unregister on all of its
506 let { workers } = await this.sendQuery("SPCheckServiceWorkers");
514 * Load a privileged script that runs same-process. This is different from
515 * |loadChromeScript|, which will run in the parent process in e10s mode.
517 loadPrivilegedScript(aFunction) {
518 var str = "(" + aFunction.toString() + ")();";
519 let gGlobalObject = Cu.getGlobalForObject(this);
520 let sb = Cu.Sandbox(gGlobalObject);
521 var window = this.contentWindow;
522 var mc = new window.MessageChannel();
524 let blob = new Blob([str], { type: "application/javascript" });
525 let blobUrl = URL.createObjectURL(blob);
526 Services.scriptloader.loadSubScript(blobUrl, sb);
531 _readUrlAsString(aUrl) {
532 // Fetch script content as we can't use scriptloader's loadSubScript
533 // to evaluate http:// urls...
534 var scriptableStream = Cc[
535 "@mozilla.org/scriptableinputstream;1"
536 ].getService(Ci.nsIScriptableInputStream);
538 var channel = NetUtil.newChannel({
540 loadUsingSystemPrincipal: true,
542 var input = channel.open();
543 scriptableStream.init(input);
548 while ((str = scriptableStream.read(4096))) {
552 var output = buffer.join("");
554 scriptableStream.close();
558 if (channel instanceof Ci.nsIHttpChannel) {
559 status = channel.responseStatus;
564 `Error while executing chrome script '${aUrl}':\n` +
565 "The script doesn't exist. Ensure you have registered it in " +
566 "'support-files' in your mochitest.ini."
573 loadChromeScript(urlOrFunction, sandboxOptions) {
574 // Create a unique id for this chrome script
575 let uuidGenerator = Cc["@mozilla.org/uuid-generator;1"].getService(
578 let id = uuidGenerator.generateUUID().toString();
580 // Tells chrome code to evaluate this chrome script
581 let scriptArgs = { id, sandboxOptions };
582 if (typeof urlOrFunction == "function") {
583 scriptArgs.function = {
584 body: "(" + urlOrFunction.toString() + ")();",
585 name: urlOrFunction.name,
588 // Note: We need to do this in the child since, even though
589 // `_readUrlAsString` pretends to be synchronous, its channel
590 // winds up spinning the event loop when loading HTTP URLs. That
591 // leads to unexpected out-of-order operations if the child sends
592 // a message immediately after loading the script.
593 scriptArgs.function = {
594 body: this._readUrlAsString(urlOrFunction),
596 scriptArgs.url = urlOrFunction;
598 this.sendAsyncMessage("SPLoadChromeScript", scriptArgs);
600 // Returns a MessageManager like API in order to be
601 // able to communicate with this chrome script
604 addMessageListener: (name, listener) => {
605 listeners.push({ name, listener });
608 promiseOneMessage: name =>
609 new Promise(resolve => {
610 chromeScript.addMessageListener(name, function listener(message) {
611 chromeScript.removeMessageListener(name, listener);
616 removeMessageListener: (name, listener) => {
617 listeners = listeners.filter(
618 o => o.name != name || o.listener != listener
622 sendAsyncMessage: (name, message) => {
623 this.sendAsyncMessage("SPChromeScriptMessage", { id, name, message });
626 sendQuery: (name, message) => {
627 return this.sendQuery("SPChromeScriptMessage", { id, name, message });
632 this._removeMessageListener("SPChromeScriptMessage", chromeScript);
635 receiveMessage: aMessage => {
636 let messageId = aMessage.json.id;
637 let name = aMessage.json.name;
638 let message = aMessage.json.message;
639 if (this.contentWindow) {
640 message = new StructuredCloneHolder(message).deserialize(
644 // Ignore message from other chrome script
645 if (messageId != id) {
650 if (aMessage.name == "SPChromeScriptMessage") {
651 for (let listener of listeners.filter(o => o.name == name)) {
652 result = listener.listener(message);
658 this._addMessageListener("SPChromeScriptMessage", chromeScript);
663 async importInMainProcess(importString) {
664 var message = await this.sendQuery("SPImportInMainProcess", importString);
665 if (message.hadError) {
667 "SpecialPowers.importInMainProcess failed with error " +
678 * Convenient shortcuts to the standard Components abbreviations.
693 get addProfilerMarker() {
694 return ChromeUtils.addProfilerMarker;
697 get DOMWindowUtils() {
698 return this.contentWindow.windowUtils;
701 getDOMWindowUtils(aWindow) {
702 if (aWindow == this.contentWindow) {
703 return aWindow.windowUtils;
706 return bindDOMWindowUtils(Cu.unwaiveXrays(aWindow));
709 async toggleMuteState(aMuted, aWindow) {
711 ? aWindow.windowGlobalChild.getActor("SpecialPowers")
713 return actor.sendQuery("SPToggleMuteAudio", { mute: aMuted });
717 * A method to get a DOMParser that can't parse XUL.
719 getNoXULDOMParser() {
720 // If we create it with a system subject principal (so it gets a
721 // nullprincipal), it won't be able to parse XUL by default.
722 return new DOMParser();
725 get InspectorUtils() {
726 return InspectorUtils;
729 get PromiseDebugging() {
730 return PromiseDebugging;
733 async waitForCrashes(aExpectingProcessCrash) {
734 if (!aExpectingProcessCrash) {
738 var crashIds = this._encounteredCrashDumpFiles
739 .filter(filename => {
740 return filename.length === 40 && filename.endsWith(".dmp");
743 return id.slice(0, -4); // Strip the .dmp extension to get the ID
746 await this.sendQuery("SPProcessCrashManagerWait", {
751 async removeExpectedCrashDumpFiles(aExpectingProcessCrash) {
753 if (aExpectingProcessCrash) {
755 op: "delete-crash-dump-files",
756 filenames: this._encounteredCrashDumpFiles,
758 if (!(await this.sendQuery("SPProcessCrashService", message))) {
762 this._encounteredCrashDumpFiles.length = 0;
766 async findUnexpectedCrashDumpFiles() {
769 op: "find-crash-dump-files",
770 crashDumpFilesToIgnore: this._unexpectedCrashDumpFiles,
772 var crashDumpFiles = await this.sendQuery("SPProcessCrashService", message);
773 crashDumpFiles.forEach(function(aFilename) {
774 self._unexpectedCrashDumpFiles[aFilename] = true;
776 return crashDumpFiles;
779 removePendingCrashDumpFiles() {
781 op: "delete-pending-crash-dump-files",
783 return this.sendQuery("SPProcessCrashService", message);
786 _setTimeout(callback, delay = 0) {
787 // for mochitest-browser
788 if (typeof this.chromeWindow != "undefined") {
789 this.chromeWindow.setTimeout(callback, delay);
791 // for mochitest-plain
793 this.contentWindow.setTimeout(callback, delay);
797 promiseTimeout(delay) {
798 return new Promise(resolve => {
799 this._setTimeout(resolve, delay);
803 _delayCallbackTwice(callback) {
804 let delayedCallback = () => {
805 let delayAgain = aCallback => {
806 // Using this._setTimeout doesn't work here
807 // It causes failures in mochtests that use
808 // multiple pushPrefEnv calls
809 // For chrome/browser-chrome mochitests
810 this._setTimeout(aCallback);
812 delayAgain(delayAgain.bind(this, callback));
814 return delayedCallback;
817 /* apply permissions to the system and when the test case is finished (SimpleTest.finish())
818 we will revert the permission back to the original.
820 inPermissions is an array of objects where each object has a type, action, context, ex:
821 [{'type': 'SystemXHR', 'allow': 1, 'context': document},
822 {'type': 'SystemXHR', 'allow': Ci.nsIPermissionManager.PROMPT_ACTION, 'context': document}]
824 Allow can be a boolean value of true/false or ALLOW_ACTION/DENY_ACTION/PROMPT_ACTION/UNKNOWN_ACTION
826 async pushPermissions(inPermissions, callback) {
827 let permissions = [];
828 for (let perm of inPermissions) {
829 let principal = this._getPrincipalFromArg(perm.context);
837 await this.sendQuery("PushPermissions", permissions).then(callback);
838 await this.promiseTimeout(0);
841 async popPermissions(callback = null) {
842 await this.sendQuery("PopPermissions").then(callback);
843 await this.promiseTimeout(0);
846 async flushPermissions(callback = null) {
847 await this.sendQuery("FlushPermissions").then(callback);
848 await this.promiseTimeout(0);
852 * This function should be used when specialpowers is in content process but
853 * it want to get the notification from chrome space.
855 * This function will call Services.obs.addObserver in SpecialPowersObserver
856 * (that is in chrome process) and forward the data received to SpecialPowers
857 * via messageManager.
858 * You can use this._addMessageListener("specialpowers-YOUR_TOPIC") to fire
861 * To get the expected data, you should modify
862 * SpecialPowersObserver.prototype._registerObservers.observe. Or the message
863 * you received from messageManager will only contain 'aData' from Service.obs.
865 registerObservers(topic) {
868 observerTopic: topic,
870 return this.sendQuery("SPObserverService", msg);
873 setTestPluginEnabledState(newEnabledState, pluginName) {
874 return this.sendQuery("SPSetTestPluginEnabledState", {
880 async pushPrefEnv(inPrefs, callback = null) {
881 await this.sendQuery("PushPrefEnv", inPrefs).then(callback);
882 await this.promiseTimeout(0);
885 async popPrefEnv(callback = null) {
886 await this.sendQuery("PopPrefEnv").then(callback);
887 await this.promiseTimeout(0);
890 async flushPrefEnv(callback = null) {
891 await this.sendQuery("FlushPrefEnv").then(callback);
892 await this.promiseTimeout(0);
895 _addObserverProxy(notification) {
896 if (notification in this._proxiedObservers) {
897 this._addMessageListener(
899 this._proxiedObservers[notification]
903 _removeObserverProxy(notification) {
904 if (notification in this._proxiedObservers) {
905 this._removeMessageListener(
907 this._proxiedObservers[notification]
912 addObserver(obs, notification, weak) {
913 // Make sure the parent side exists, or we won't get any notifications.
914 this.sendAsyncMessage("Wakeup");
916 this._addObserverProxy(notification);
917 obs = Cu.waiveXrays(obs);
919 typeof obs == "object" &&
920 obs.observe.name != "SpecialPowersCallbackWrapper"
922 obs.observe = WrapPrivileged.wrapCallback(
923 Cu.unwaiveXrays(obs.observe),
927 Services.obs.addObserver(obs, notification, weak);
929 removeObserver(obs, notification) {
930 this._removeObserverProxy(notification);
931 Services.obs.removeObserver(Cu.waiveXrays(obs), notification);
933 notifyObservers(subject, topic, data) {
934 Services.obs.notifyObservers(subject, topic, data);
938 * An async observer is useful if you're listening for a
939 * notification that normally is only used by C++ code or chrome
940 * code (so it runs in the SystemGroup), but we need to know about
941 * it for a test (which runs as web content). If we used
942 * addObserver, we would assert when trying to enter web content
943 * from a runnabled labeled by the SystemGroup. An async observer
944 * avoids this problem.
946 addAsyncObserver(obs, notification, weak) {
947 obs = Cu.waiveXrays(obs);
949 typeof obs == "object" &&
950 obs.observe.name != "SpecialPowersCallbackWrapper"
952 obs.observe = WrapPrivileged.wrapCallback(
953 Cu.unwaiveXrays(obs.observe),
957 let asyncObs = (...args) => {
958 Services.tm.dispatchToMainThread(() => {
959 if (typeof obs == "function") {
962 obs.observe.call(undefined, ...args);
966 this._asyncObservers.set(obs, asyncObs);
967 Services.obs.addObserver(asyncObs, notification, weak);
969 removeAsyncObserver(obs, notification) {
970 let asyncObs = this._asyncObservers.get(Cu.waiveXrays(obs));
971 Services.obs.removeObserver(asyncObs, notification);
975 return obj.QueryInterface !== undefined;
977 do_QueryInterface(obj, iface) {
978 return obj.QueryInterface(Ci[iface]);
981 call_Instanceof(obj1, obj2) {
982 obj1 = WrapPrivileged.unwrap(obj1);
983 obj2 = WrapPrivileged.unwrap(obj2);
984 return obj1 instanceof obj2;
987 // Returns a privileged getter from an object. GetOwnPropertyDescriptor does
988 // not work here because xray wrappers don't properly implement it.
990 // This terribleness is used by dom/base/test/test_object.html because
991 // <object> and <embed> tags will spawn plugins if their prototype is touched,
992 // so we need to get and cache the getter of |hasRunningPlugin| if we want to
993 // call it without paradoxically spawning the plugin.
994 do_lookupGetter(obj, name) {
995 return Object.prototype.__lookupGetter__.call(obj, name);
998 // Mimic the get*Pref API
999 getBoolPref(...args) {
1000 return Services.prefs.getBoolPref(...args);
1002 getIntPref(...args) {
1003 return Services.prefs.getIntPref(...args);
1005 getCharPref(...args) {
1006 return Services.prefs.getCharPref(...args);
1008 getComplexValue(prefName, iid) {
1009 return Services.prefs.getComplexValue(prefName, iid);
1012 getParentBoolPref(prefName, defaultValue) {
1013 return this._getParentPref(prefName, "BOOL", { defaultValue });
1015 getParentIntPref(prefName, defaultValue) {
1016 return this._getParentPref(prefName, "INT", { defaultValue });
1018 getParentCharPref(prefName, defaultValue) {
1019 return this._getParentPref(prefName, "CHAR", { defaultValue });
1022 // Mimic the set*Pref API
1023 setBoolPref(prefName, value) {
1024 return this._setPref(prefName, "BOOL", value);
1026 setIntPref(prefName, value) {
1027 return this._setPref(prefName, "INT", value);
1029 setCharPref(prefName, value) {
1030 return this._setPref(prefName, "CHAR", value);
1032 setComplexValue(prefName, iid, value) {
1033 return this._setPref(prefName, "COMPLEX", value, iid);
1036 // Mimic the clearUserPref API
1037 clearUserPref(prefName) {
1043 return this.sendQuery("SPPrefService", msg);
1046 // Private pref functions to communicate to chrome
1047 async _getParentPref(prefName, prefType, { defaultValue, iid }) {
1052 iid, // Only used with complex prefs
1053 defaultValue, // Optional default value
1055 let val = await this.sendQuery("SPPrefService", msg);
1057 throw new Error(`Error getting pref '${prefName}'`);
1061 _getPref(prefName, prefType, { defaultValue }) {
1064 return Services.prefs.getBoolPref(prefName);
1066 return Services.prefs.getIntPref(prefName);
1068 return Services.prefs.getCharPref(prefName);
1072 _setPref(prefName, prefType, prefValue, iid) {
1077 iid, // Only used with complex prefs
1080 return this.sendQuery("SPPrefService", msg);
1084 return window.docShell.contentViewer;
1086 // XXX: these APIs really ought to be removed, they're not e10s-safe.
1087 // (also they're pretty Firefox-specific)
1088 _getTopChromeWindow(window) {
1089 return window.browsingContext.topChromeWindow;
1091 _getAutoCompletePopup(window) {
1092 return this._getTopChromeWindow(window).document.getElementById(
1096 addAutoCompletePopupEventListener(window, eventname, listener) {
1097 this._getAutoCompletePopup(window).addEventListener(eventname, listener);
1099 removeAutoCompletePopupEventListener(window, eventname, listener) {
1100 this._getAutoCompletePopup(window).removeEventListener(eventname, listener);
1104 ChromeUtils.import("resource://gre/modules/FormHistory.jsm", tmp);
1105 return tmp.FormHistory;
1107 getFormFillController(window) {
1108 return Cc["@mozilla.org/satchel/form-fill-controller;1"].getService(
1109 Ci.nsIFormFillController
1112 attachFormFillControllerTo(window) {
1113 this.getFormFillController().attachPopupElementToDocument(
1115 this._getAutoCompletePopup(window)
1118 detachFormFillControllerFrom(window) {
1119 this.getFormFillController().detachFromDocument(window.document);
1121 isBackButtonEnabled(window) {
1122 return !this._getTopChromeWindow(window)
1123 .document.getElementById("Browser:Back")
1124 .hasAttribute("disabled");
1126 // XXX end of problematic APIs
1128 addChromeEventListener(type, listener, capture, allowUntrusted) {
1129 this.docShell.chromeEventHandler.addEventListener(
1136 removeChromeEventListener(type, listener, capture) {
1137 this.docShell.chromeEventHandler.removeEventListener(
1144 async generateMediaControlKeyTestEvent(event) {
1145 await this.sendQuery("SPGenerateMediaControlKeyTestEvent", { event });
1148 // Note: each call to registerConsoleListener MUST be paired with a
1149 // call to postConsoleSentinel; when the callback receives the
1150 // sentinel it will unregister itself (_after_ calling the
1151 // callback). SimpleTest.expectConsoleMessages does this for you.
1152 // If you register more than one console listener, a call to
1153 // postConsoleSentinel will zap all of them.
1154 registerConsoleListener(callback) {
1155 let listener = new SPConsoleListener(callback, this.contentWindow);
1156 Services.console.registerListener(listener);
1158 // listen for dom/console events as well
1159 Services.obs.addObserver(listener, "console-api-log-event");
1161 postConsoleSentinel() {
1162 Services.console.logStringMessage("SENTINEL");
1165 Services.console.reset();
1168 getFullZoom(window) {
1169 return BrowsingContext.getFromWindow(window).fullZoom;
1172 getDeviceFullZoom(window) {
1173 return this._getMUDV(window).deviceFullZoomForTest;
1175 setFullZoom(window, zoom) {
1176 BrowsingContext.getFromWindow(window).fullZoom = zoom;
1178 getTextZoom(window) {
1179 return BrowsingContext.getFromWindow(window).textZoom;
1181 setTextZoom(window, zoom) {
1182 BrowsingContext.getFromWindow(window).textZoom = zoom;
1185 getOverrideDPPX(window) {
1186 return this._getMUDV(window).overrideDPPX;
1188 setOverrideDPPX(window, dppx) {
1189 this._getMUDV(window).overrideDPPX = dppx;
1192 emulateMedium(window, mediaType) {
1193 BrowsingContext.getFromWindow(window).top.mediumOverride = mediaType;
1196 stopEmulatingMedium(window) {
1197 BrowsingContext.getFromWindow(window).top.mediumOverride = "";
1200 // Takes a snapshot of the given window and returns a <canvas>
1201 // containing the image. When the window is same-process, the canvas
1202 // is returned synchronously. When it is out-of-process (or when a
1203 // BrowsingContext or FrameLoaderOwner is passed instead of a Window),
1204 // a promise which resolves to such a canvas is returned instead.
1205 snapshotWindowWithOptions(content, rect, bgcolor, options) {
1206 function getImageData(rect, bgcolor, options) {
1207 let el = content.document.createElementNS(
1208 "http://www.w3.org/1999/xhtml",
1211 if (rect === undefined) {
1213 top: content.scrollY,
1214 left: content.scrollX,
1215 width: content.innerWidth,
1216 height: content.innerHeight,
1219 if (bgcolor === undefined) {
1220 bgcolor = "rgb(255,255,255)";
1222 if (options === undefined) {
1226 el.width = rect.width;
1227 el.height = rect.height;
1228 let ctx = el.getContext("2d");
1231 for (let option in options) {
1232 flags |= options[option] && ctx[option];
1245 return ctx.getImageData(0, 0, el.width, el.height);
1248 let toCanvas = imageData => {
1249 let el = this.document.createElementNS(
1250 "http://www.w3.org/1999/xhtml",
1253 el.width = imageData.width;
1254 el.height = imageData.height;
1256 if (ImageData.isInstance(imageData)) {
1257 let ctx = el.getContext("2d");
1258 ctx.putImageData(imageData, 0, 0);
1264 if (Window.isInstance(content)) {
1265 // Hack around tests that try to snapshot 0 width or height
1267 if (rect && !(rect.width && rect.height)) {
1268 return toCanvas(rect);
1271 // This is an in-process window. Snapshot it synchronously.
1272 return toCanvas(getImageData(rect, bgcolor, options));
1275 // This is a remote window or frame. Snapshot it asynchronously and
1276 // return a promise for the result. Alas, consumers expect us to
1277 // return a <canvas> element rather than an ImageData object, so we
1278 // need to convert the result from the remote snapshot to a local
1280 let promise = this.spawn(
1282 [rect, bgcolor, options],
1285 if (Cu.isXrayWrapper(this.contentWindow)) {
1286 return new this.contentWindow.Promise((resolve, reject) => {
1287 promise.then(resolve, reject);
1293 snapshotWindow(win, withCaret, rect, bgcolor) {
1294 return this.snapshotWindowWithOptions(win, rect, bgcolor, {
1295 DRAWWINDOW_DRAW_CARET: withCaret,
1299 snapshotRect(win, rect, bgcolor) {
1300 return this.snapshotWindowWithOptions(win, rect, bgcolor);
1304 this.contentWindow.windowUtils.garbageCollect();
1311 forceShrinkingGC() {
1312 Cu.forceShrinkingGC();
1327 // Due to various dependencies between JS objects and C++ objects, an ordinary
1328 // forceGC doesn't necessarily clear all unused objects, thus the GC and CC
1329 // needs to run several times and when no other JS is running.
1330 // The current number of iterations has been determined according to massive
1331 // cross platform testing.
1335 function genGCCallback(cb) {
1339 Cu.schedulePreciseGC(genGCCallback(cb));
1346 Cu.schedulePreciseGC(genGCCallback(callback));
1349 nondeterministicGetWeakMapKeys(m) {
1350 let keys = ChromeUtils.nondeterministicGetWeakMapKeys(m);
1354 return this.contentWindow.Array.from(keys);
1357 getMemoryReports() {
1359 Cc["@mozilla.org/memory-reporter-manager;1"]
1360 .getService(Ci.nsIMemoryReporterManager)
1378 Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT
1385 if (this._xpcomabi != null) {
1386 return this._xpcomabi;
1389 var xulRuntime = Services.appinfo.QueryInterface(Ci.nsIXULRuntime);
1391 this._xpcomabi = xulRuntime.XPCOMABI;
1392 return this._xpcomabi;
1395 // The optional aWin parameter allows the caller to specify a given window in
1396 // whose scope the runnable should be dispatched. If aFun throws, the
1397 // exception will be reported to aWin.
1398 executeSoon(aFun, aWin) {
1399 // Create the runnable in the scope of aWin to avoid running into COWs.
1402 runnable = Cu.createObjectIn(aWin);
1404 runnable.run = aFun;
1405 Cu.dispatch(runnable, aWin);
1409 if (this._os != null) {
1413 this._os = Services.appinfo.OS;
1417 get useRemoteSubframes() {
1418 return this.docShell.nsILoadContext.useRemoteSubframes;
1421 addSystemEventListener(target, type, listener, useCapture) {
1422 Services.els.addSystemEventListener(target, type, listener, useCapture);
1424 removeSystemEventListener(target, type, listener, useCapture) {
1425 Services.els.removeSystemEventListener(target, type, listener, useCapture);
1428 // helper method to check if the event is consumed by either default group's
1429 // event listener or system group's event listener.
1430 defaultPreventedInAnyGroup(event) {
1431 // FYI: Event.defaultPrevented returns false in content context if the
1432 // event is consumed only by system group's event listeners.
1433 return event.defaultPrevented;
1436 getDOMRequestService() {
1437 var serv = Services.DOMRequest;
1445 "fireDetailedError",
1447 for (var i in props) {
1448 let prop = props[i];
1449 res[prop] = function() {
1450 return serv[prop].apply(serv, arguments);
1453 return Cu.cloneInto(res, this.contentWindow, { cloneFunctions: true });
1456 addCategoryEntry(category, entry, value, persists, replace) {
1457 Services.catMan.addCategoryEntry(category, entry, value, persists, replace);
1460 deleteCategoryEntry(category, entry, persists) {
1461 Services.catMan.deleteCategoryEntry(category, entry, persists);
1463 openDialog(win, args) {
1464 return win.openDialog.apply(win, args);
1466 // This is a blocking call which creates and spins a native event loop
1467 spinEventLoop(win) {
1468 // simply do a sync XHR back to our windows location.
1469 var syncXHR = new win.XMLHttpRequest();
1470 syncXHR.open("GET", win.location, false);
1474 // :jdm gets credit for this. ex: getPrivilegedProps(window, 'location.href');
1475 getPrivilegedProps(obj, props) {
1476 var parts = props.split(".");
1477 for (var i = 0; i < parts.length; i++) {
1479 if (obj[p] != undefined) {
1488 _browsingContextForTarget(target) {
1489 if (BrowsingContext.isInstance(target)) {
1492 if (Element.isInstance(target)) {
1493 return target.browsingContext;
1496 return BrowsingContext.getFromWindow(target);
1499 getBrowsingContextID(target) {
1500 return this._browsingContextForTarget(target).id;
1503 *getGroupTopLevelWindows(target) {
1504 let { group } = this._browsingContextForTarget(target);
1505 for (let bc of group.getToplevels()) {
1511 * Runs a task in the context of the given frame, and returns a
1512 * promise which resolves to the return value of that task.
1514 * The given frame may be in-process or out-of-process. Either way,
1515 * the task will run asynchronously, in a sandbox with access to the
1516 * frame's content window via its `content` global. Any arguments
1517 * passed will be copied via structured clone, as will its return
1520 * The sandbox also has access to an Assert object, as provided by
1521 * Assert.jsm. Any assertion methods called before the task resolves
1522 * will be relayed back to the test environment of the caller.
1524 * @param {BrowsingContext or FrameLoaderOwner or WindowProxy} target
1525 * The target in which to run the task. This may be any element
1526 * which implements the FrameLoaderOwner interface (including
1527 * HTML <iframe> elements and XUL <browser> elements) or a
1528 * WindowProxy (either in-process or remote).
1529 * @param {Array<any>} args
1530 * An array of arguments to pass to the task. All arguments
1531 * must be structured clone compatible, and will be cloned
1532 * before being passed to the task.
1533 * @param {function} task
1534 * The function to run in the context of the target. The
1535 * function will be stringified and re-evaluated in the context
1536 * of the target's content window. It may return any structured
1537 * clone compatible value, or a Promise which resolves to the
1538 * same, which will be returned to the caller.
1540 * @returns {Promise<any>}
1541 * A promise which resolves to the return value of the task, or
1542 * which rejects if the task raises an exception. As this is
1543 * being written, the rejection value will always be undefined
1544 * in the cases where the task throws an error, though that may
1545 * change in the future.
1547 spawn(target, args, task) {
1548 let browsingContext = this._browsingContextForTarget(target);
1550 return this.sendQuery("Spawn", {
1554 caller: Cu.getFunctionSourceLocation(task),
1555 hasHarness: typeof this.SimpleTest === "object",
1556 imports: this._spawnTaskImports,
1561 * Like `spawn`, but spawns a chrome task in the parent process,
1562 * instead. The task additionally has access to `windowGlobalParent`
1563 * and `browsingContext` globals corresponding to the window from
1564 * which the task was spawned.
1566 spawnChrome(args, task) {
1567 return this.sendQuery("SpawnChrome", {
1570 caller: Cu.getFunctionSourceLocation(task),
1571 imports: this._spawnTaskImports,
1575 snapshotContext(target, rect, background) {
1576 let browsingContext = this._browsingContextForTarget(target);
1578 return this.sendQuery("Snapshot", {
1582 }).then(imageData => {
1583 return this.contentWindow.createImageBitmap(imageData);
1587 getSecurityState(target) {
1588 let browsingContext = this._browsingContextForTarget(target);
1590 return this.sendQuery("SecurityState", {
1595 _spawnTask(task, args, caller, taskId, imports) {
1596 let sb = new SpecialPowersSandbox(
1599 this.sendAsyncMessage("ProxiedAssert", { taskId, data });
1604 sb.sandbox.SpecialPowers = this;
1605 sb.sandbox.ContentTaskUtils = ContentTaskUtils;
1606 for (let [global, prop] of Object.entries({
1607 content: "contentWindow",
1608 docShell: "docShell",
1610 Object.defineProperty(sb.sandbox, global, {
1618 return sb.execute(task, args, caller);
1622 * Automatically imports the given symbol from the given JSM for any
1623 * task spawned by this SpecialPowers instance.
1625 addTaskImport(symbol, url) {
1626 this._spawnTaskImports[symbol] = url;
1630 return this._SimpleTest || this.contentWindow.wrappedJSObject.SimpleTest;
1632 set SimpleTest(val) {
1633 this._SimpleTest = val;
1637 * Sets this actor as the default assertion result handler for tasks
1638 * which originate in a window without a test harness.
1640 setAsDefaultAssertHandler() {
1641 this.sendAsyncMessage("SetAsDefaultAssertHandler");
1644 getFocusedElementForWindow(targetWindow, aDeep) {
1646 Services.focus.getFocusedElementForWindow(targetWindow, aDeep, outParam);
1647 return outParam.value;
1650 get focusManager() {
1651 return Services.focus;
1655 return Services.focus.activeWindow;
1659 return Services.focus.focusedWindow;
1663 // This is called inside TestRunner._makeIframe without aWindow, because of assertions in oop mochitests
1664 // With aWindow, it is called in SimpleTest.waitForFocus to allow popup window opener focus switching
1671 ? aWindow.windowGlobalChild.getActor("SpecialPowers")
1673 actor.sendAsyncMessage("SpecialPowers.Focus", {});
1679 getClipboardData(flavor, whichClipboard) {
1680 if (whichClipboard === undefined) {
1681 whichClipboard = Services.clipboard.kGlobalClipboard;
1684 var xferable = Cc["@mozilla.org/widget/transferable;1"].createInstance(
1687 xferable.init(this.docShell);
1688 xferable.addDataFlavor(flavor);
1689 Services.clipboard.getData(xferable, whichClipboard);
1692 xferable.getTransferData(flavor, data);
1694 data = data.value || null;
1699 return data.QueryInterface(Ci.nsISupportsString).data;
1702 clipboardCopyString(str) {
1703 Cc["@mozilla.org/widget/clipboardhelper;1"]
1704 .getService(Ci.nsIClipboardHelper)
1708 supportsSelectionClipboard() {
1709 return Services.clipboard.supportsSelectionClipboard();
1712 swapFactoryRegistration(cid, contractID, newFactory) {
1713 newFactory = Cu.waiveXrays(newFactory);
1715 var componentRegistrar = Components.manager.QueryInterface(
1716 Ci.nsIComponentRegistrar
1719 var currentCID = componentRegistrar.contractIDToCID(contractID);
1720 var currentFactory = Components.manager.getClassObject(
1725 componentRegistrar.unregisterFactory(currentCID, currentFactory);
1727 let uuidGenerator = Cc["@mozilla.org/uuid-generator;1"].getService(
1730 cid = uuidGenerator.generateUUID();
1733 // Restore the original factory.
1734 componentRegistrar.registerFactory(cid, "", contractID, newFactory);
1735 return { originalCID: currentCID };
1738 _getElement(aWindow, id) {
1739 return typeof id == "string" ? aWindow.document.getElementById(id) : id;
1742 dispatchEvent(aWindow, target, event) {
1743 var el = this._getElement(aWindow, target);
1744 return el.dispatchEvent(event);
1747 get isDebugBuild() {
1748 return Cc["@mozilla.org/xpcom/debug;1"].getService(Ci.nsIDebug2)
1752 var debugsvc = Cc["@mozilla.org/xpcom/debug;1"].getService(Ci.nsIDebug2);
1753 return debugsvc.assertionCount;
1757 * @param arg one of the following:
1759 * - A document node.
1760 * - A dictionary including a URL (`url`) and origin attributes (`attr`).
1762 _getPrincipalFromArg(arg) {
1763 arg = WrapPrivileged.unwrap(Cu.unwaiveXrays(arg));
1765 if (arg.nodePrincipal) {
1767 return arg.nodePrincipal;
1770 let secMan = Services.scriptSecurityManager;
1771 if (typeof arg == "string") {
1773 let uri = Services.io.newURI(arg);
1774 return secMan.createContentPrincipal(uri, {});
1777 let uri = Services.io.newURI(arg.url);
1778 let attrs = arg.originAttributes || {};
1779 return secMan.createContentPrincipal(uri, attrs);
1782 async addPermission(type, allow, arg, expireType, expireTime) {
1783 let principal = this._getPrincipalFromArg(arg);
1784 if (principal.isSystemPrincipal) {
1785 return; // nothing to do
1788 let permission = allow;
1789 if (typeof permission === "boolean") {
1791 Ci.nsIPermissionManager[allow ? "ALLOW_ACTION" : "DENY_ACTION"];
1799 expireType: typeof expireType === "number" ? expireType : 0,
1800 expireTime: typeof expireTime === "number" ? expireTime : 0,
1803 await this.sendQuery("SPPermissionManager", msg);
1807 * @param type see nsIPermissionsManager::testPermissionFromPrincipal.
1808 * @param arg one of the following:
1810 * - A document node.
1811 * - A dictionary including a URL (`url`) and origin attributes (`attr`).
1813 async removePermission(type, arg) {
1814 let principal = this._getPrincipalFromArg(arg);
1815 if (principal.isSystemPrincipal) {
1816 return; // nothing to do
1825 await this.sendQuery("SPPermissionManager", msg);
1828 async hasPermission(type, arg) {
1829 let principal = this._getPrincipalFromArg(arg);
1830 if (principal.isSystemPrincipal) {
1831 return true; // system principals have all permissions
1840 return this.sendQuery("SPPermissionManager", msg);
1843 async testPermission(type, value, arg) {
1844 let principal = this._getPrincipalFromArg(arg);
1845 if (principal.isSystemPrincipal) {
1846 return true; // system principals have all permissions
1855 return this.sendQuery("SPPermissionManager", msg);
1858 isContentWindowPrivate(win) {
1859 return PrivateBrowsingUtils.isContentWindowPrivate(win);
1862 async notifyObserversInParentProcess(subject, topic, data) {
1864 throw new Error("Can't send subject to another process!");
1866 if (this.isMainProcess()) {
1867 this.notifyObservers(subject, topic, data);
1872 observerTopic: topic,
1875 await this.sendQuery("SPObserverService", msg);
1878 removeAllServiceWorkerData() {
1879 return this.sendQuery("SPRemoveAllServiceWorkers", {});
1882 removeServiceWorkerDataForExampleDomain() {
1883 return this.sendQuery("SPRemoveServiceWorkerDataForExampleDomain", {});
1886 cleanUpSTSData(origin, flags) {
1887 return this.sendQuery("SPCleanUpSTSData", { origin, flags: flags || 0 });
1890 async requestDumpCoverageCounters(cb) {
1891 // We want to avoid a roundtrip between child and parent.
1892 if (!PerTestCoverageUtils.enabled) {
1896 await this.sendQuery("SPRequestDumpCoverageCounters", {});
1899 async requestResetCoverageCounters(cb) {
1900 // We want to avoid a roundtrip between child and parent.
1901 if (!PerTestCoverageUtils.enabled) {
1904 await this.sendQuery("SPRequestResetCoverageCounters", {});
1907 loadExtension(ext, handler) {
1908 if (this._extensionListeners == null) {
1909 this._extensionListeners = new Set();
1911 this._addMessageListener("SPExtensionMessage", msg => {
1912 for (let listener of this._extensionListeners) {
1922 // Note, this is not the addon is as used by the AddonManager etc,
1923 // this is just an identifier used for specialpowers messaging
1924 // between this content process and the chrome process.
1925 let id = this._nextExtensionID++;
1927 handler = Cu.waiveXrays(handler);
1928 ext = Cu.waiveXrays(ext);
1931 let state = "uninitialized";
1939 return sp.sendQuery("SPStartupExtension", { id }).then(
1945 sp._extensionListeners.delete(listener);
1946 return Promise.reject("startup failed");
1952 state = "unloading";
1953 return sp.sendQuery("SPUnloadExtension", { id }).finally(() => {
1954 sp._extensionListeners.delete(listener);
1959 sendMessage(...args) {
1960 sp.sendAsyncMessage("SPExtensionMessage", { id, args });
1963 grantActiveTab(tabId) {
1964 sp.sendAsyncMessage("SPExtensionGrantActiveTab", { id, tabId });
1968 this.sendAsyncMessage("SPLoadExtension", { ext, id });
1970 let listener = msg => {
1971 if (msg.data.id == id) {
1972 if (msg.data.type == "extensionSetId") {
1973 extension.id = msg.data.args[0];
1974 extension.uuid = msg.data.args[1];
1975 } else if (msg.data.type in handler) {
1976 handler[msg.data.type](
1977 ...Cu.cloneInto(msg.data.args, this.contentWindow)
1980 dump(`Unexpected: ${msg.data.type}\n`);
1985 this._extensionListeners.add(listener);
1989 invalidateExtensionStorageCache() {
1990 this.notifyObserversInParentProcess(
1992 "extension-invalidate-storage-cache",
1997 allowMedia(window, enable) {
1998 window.docShell.allowMedia = enable;
2001 createChromeCache(name, url) {
2002 let principal = this._getPrincipalFromArg(url);
2003 return new this.contentWindow.CacheStorage(name, principal);
2006 loadChannelAndReturnStatus(url, loadUsingSystemPrincipal) {
2007 const BinaryInputStream = Components.Constructor(
2008 "@mozilla.org/binaryinputstream;1",
2009 "nsIBinaryInputStream",
2013 return new Promise(function(resolve) {
2017 onStartRequest(request) {
2018 request.QueryInterface(Ci.nsIHttpChannel);
2019 this.httpStatus = request.responseStatus;
2022 onDataAvailable(request, stream, offset, count) {
2023 new BinaryInputStream(stream).readByteArray(count);
2026 onStopRequest(request, status) {
2027 /* testing here that the redirect was not followed. If it was followed
2028 we would see a http status of 200 and status of NS_OK */
2030 let httpStatus = this.httpStatus;
2031 resolve({ status, httpStatus });
2034 let uri = NetUtil.newURI(url);
2035 let channel = NetUtil.newChannel({ uri, loadUsingSystemPrincipal });
2037 channel.loadFlags |= Ci.nsIChannel.LOAD_DOCUMENT_URI;
2038 channel.QueryInterface(Ci.nsIHttpChannelInternal);
2039 channel.documentURI = uri;
2040 channel.asyncOpen(listener);
2045 if (this._pu != null) {
2049 let pu = Cc["@mozilla.org/parserutils;1"].getService(Ci.nsIParserUtils);
2050 // We need to create and return our own wrapper.
2052 sanitize(src, flags) {
2053 return pu.sanitize(src, flags);
2055 convertToPlainText(src, flags, wrapCol) {
2056 return pu.convertToPlainText(src, flags, wrapCol);
2058 parseFragment(fragment, flags, isXML, baseURL, element) {
2059 let baseURI = baseURL ? NetUtil.newURI(baseURL) : null;
2060 return pu.parseFragment(
2061 WrapPrivileged.unwrap(fragment),
2065 WrapPrivileged.unwrap(element)
2072 createDOMWalker(node, showAnonymousContent) {
2073 node = WrapPrivileged.unwrap(node);
2074 let walker = Cc["@mozilla.org/inspector/deep-tree-walker;1"].createInstance(
2075 Ci.inIDeepTreeWalker
2077 walker.showAnonymousContent = showAnonymousContent;
2078 walker.init(node.ownerDocument, NodeFilter.SHOW_ALL);
2079 walker.currentNode = node;
2080 let contentWindow = this.contentWindow;
2083 return WrapPrivileged.wrap(walker.firstChild(), contentWindow);
2086 return WrapPrivileged.wrap(walker.lastChild(), contentWindow);
2091 observeMutationEvents(mo, node, nativeAnonymousChildList, subtree) {
2092 WrapPrivileged.unwrap(mo).observe(WrapPrivileged.unwrap(node), {
2093 nativeAnonymousChildList,
2098 doCommand(window, cmd, param) {
2101 case "cmd_backgroundColor":
2102 case "cmd_fontColor":
2103 case "cmd_fontFace":
2104 case "cmd_fontSize":
2105 case "cmd_highlight":
2106 case "cmd_insertImageNoUI":
2107 case "cmd_insertLinkNoUI":
2108 case "cmd_paragraphState":
2109 let params = Cu.createCommandParams();
2110 params.setStringValue("state_attribute", param);
2111 return window.docShell.doCommandWithParams(cmd, params);
2113 return window.docShell.doCommand(cmd);
2117 isCommandEnabled(window, cmd) {
2118 return window.docShell.isCommandEnabled(cmd);
2122 * See \ref nsIContentViewerEdit.setCommandNode(in Node).
2124 setCommandNode(window, node) {
2125 return window.docShell.contentViewer
2126 .QueryInterface(Ci.nsIContentViewerEdit)
2127 .setCommandNode(node);
2130 /* Bug 1339006 Runnables of nsIURIClassifier.classify may be labeled by
2131 * SystemGroup, but some test cases may run as web content. That would assert
2132 * when trying to enter web content from a runnable labeled by the
2133 * SystemGroup. To avoid that, we run classify from SpecialPowers which is
2134 * chrome-privileged and allowed to run inside SystemGroup
2137 doUrlClassify(principal, eventTarget, callback) {
2138 let classifierService = Cc[
2139 "@mozilla.org/url-classifier/dbservice;1"
2140 ].getService(Ci.nsIURIClassifier);
2142 let wrapCallback = (...args) => {
2143 Services.tm.dispatchToMainThread(() => {
2144 if (typeof callback == "function") {
2147 callback.onClassifyComplete.call(undefined, ...args);
2152 return classifierService.classify(
2153 WrapPrivileged.unwrap(principal),
2159 // TODO: Bug 1353701 - Supports custom event target for labelling.
2160 doUrlClassifyLocal(uri, tables, callback) {
2161 let classifierService = Cc[
2162 "@mozilla.org/url-classifier/dbservice;1"
2163 ].getService(Ci.nsIURIClassifier);
2165 let wrapCallback = results => {
2166 Services.tm.dispatchToMainThread(() => {
2167 if (typeof callback == "function") {
2168 callback(WrapPrivileged.wrap(results, this.contentWindow));
2170 callback.onClassifyComplete.call(
2172 WrapPrivileged.wrap(results, this.contentWindow)
2178 let feature = classifierService.createFeatureWithTables(
2183 return classifierService.asyncClassifyLocalWithFeatures(
2184 WrapPrivileged.unwrap(uri),
2186 Ci.nsIUrlClassifierFeature.blocklist,
2192 SpecialPowersChild.prototype._proxiedObservers = {
2193 "specialpowers-http-notify-request": function(aMessage) {
2194 let uri = aMessage.json.uri;
2195 Services.obs.notifyObservers(
2197 "specialpowers-http-notify-request",
2202 "specialpowers-service-worker-shutdown": function(aMessage) {
2203 Services.obs.notifyObservers(null, "specialpowers-service-worker-shutdown");
2206 "specialpowers-csp-on-violate-policy": function(aMessage) {
2210 subject = Services.io.newURI(aMessage.data.subject);
2212 // if it's not a valid URI it must be an nsISupportsCString
2213 subject = Cc["@mozilla.org/supports-cstring;1"].createInstance(
2214 Ci.nsISupportsCString
2216 subject.data = aMessage.data.subject;
2218 Services.obs.notifyObservers(
2220 "specialpowers-csp-on-violate-policy",
2225 "specialpowers-xfo-on-violate-policy": function(aMessage) {
2226 let subject = Services.io.newURI(aMessage.data.subject);
2227 Services.obs.notifyObservers(
2229 "specialpowers-xfo-on-violate-policy",
2235 SpecialPowersChild.prototype.EARLY_BETA_OR_EARLIER =
2236 AppConstants.EARLY_BETA_OR_EARLIER;