Bug 1481021: Part 2 - Stop loading SpecialPowers into frame script scopes. r=bz,jmaher,aswan

Loading SpecialPowers into frame scripts has side-effects, detailed in part 1,
which are undesirable. The main side-effect that I'm trying to get rid of here
is the force-enabling of permissive COWs in frame script scopes, which is
blocking changes that I need to make elsewhere. But both that and the scope
pollution it causes are likely to allow code to work when running in
automation which fails in real world usage.

This patch changes our special powers frame scripts to load specialpowers.js
and specialpowersAPI.js as JSMs, which run in their own global, but define
most of the same properties on our frame script globals.

Most other callers still load those scripts via <script> tags or the subscript
loader, and should ideally migrated in a follow-up. But even so, this patch
still gives us a cleaner separation of the frame script and non-frame-script
loading code.

MozReview-Commit-ID: CR226gCDaGY

--HG--
extra : rebase_source : fa253abde2029ec09c724404106d83623f064875
This commit is contained in:
Kris Maglione 2018-08-07 14:03:21 -07:00
Родитель c1969dbca2
Коммит b9e9588050
8 изменённых файлов: 90 добавлений и 70 удалений

Просмотреть файл

@ -24,6 +24,8 @@ const whitelist = {
]),
modules: new Set([
"chrome://mochikit/content/ShutdownLeaksCollector.jsm",
"resource://specialpowers/specialpowers.js",
"resource://specialpowers/specialpowersAPI.js",
// General utilities
"resource://gre/modules/AppConstants.jsm",

Просмотреть файл

@ -9,8 +9,6 @@
title="bug 89419 test">
<script type="application/javascript" src= "chrome://mochikit/content/chrome-harness.js" />
<script type="text/javascript"
src="chrome://mochikit/content/tests/SimpleTest/specialpowersAPI.js"/>
<script type="text/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SpecialPowersObserverAPI.js"/>
<script type="text/javascript"

Просмотреть файл

@ -105,7 +105,7 @@ if (params.runUntilFailure) {
// closeWhenDone tells us to close the browser when complete
if (params.closeWhenDone) {
TestRunner.onComplete = SpecialPowers.quit;
TestRunner.onComplete = SpecialPowers.quit.bind(SpecialPowers);
}
if (params.failureFile) {

Просмотреть файл

@ -16,8 +16,7 @@ var EXPORTED_SYMBOLS = ["SpecialPowersObserver"];
ChromeUtils.import("resource://gre/modules/Services.jsm");
Cu.importGlobalProperties(["File"]);
const CHILD_SCRIPT = "resource://specialpowers/specialpowers.js";
const CHILD_SCRIPT_API = "resource://specialpowers/specialpowersAPI.js";
const CHILD_SCRIPT_API = "resource://specialpowers/specialpowersFrameScript.js";
const CHILD_LOGGER_SCRIPT = "resource://specialpowers/MozillaLogger.js";
@ -82,7 +81,6 @@ SpecialPowersObserver.prototype._loadFrameScript = function() {
this._messageManager.loadFrameScript(CHILD_LOGGER_SCRIPT, true);
this._messageManager.loadFrameScript(CHILD_SCRIPT_API, true);
this._messageManager.loadFrameScript(CHILD_SCRIPT, true);
this._isFrameScriptLoaded = true;
this._createdFiles = null;
}
@ -149,7 +147,6 @@ SpecialPowersObserver.prototype.uninit = function() {
this._messageManager.removeDelayedFrameScript(CHILD_LOGGER_SCRIPT);
this._messageManager.removeDelayedFrameScript(CHILD_SCRIPT_API);
this._messageManager.removeDelayedFrameScript(CHILD_SCRIPT);
this._isFrameScriptLoaded = false;
}
};

Просмотреть файл

@ -5,12 +5,21 @@
* order to be used as a replacement for UniversalXPConnect
*/
/* import-globals-from specialpowersAPI.js */
/* globals addMessageListener, removeMessageListener, sendSyncMessage, sendAsyncMessage */
/* globals bindDOMWindowUtils, SpecialPowersAPI */
ChromeUtils.import("resource://gre/modules/Services.jsm");
function SpecialPowers(window) {
Services.scriptloader.loadSubScript("resource://specialpowers/MozillaLogger.js", this);
var EXPORTED_SYMBOLS = ["SpecialPowers", "attachSpecialPowersToWindow"];
ChromeUtils.import("resource://specialpowers/specialpowersAPI.js", this);
Cu.forcePermissiveCOWs();
function SpecialPowers(window, mm) {
this.mm = mm;
this.window = Cu.getWeakReference(window);
this._windowID = window.windowUtils.currentInnerWindowID;
this._encounteredCrashDumpFiles = [];
@ -47,18 +56,18 @@ function SpecialPowers(window) {
"SPExtensionMessage",
"SPRequestDumpCoverageCounters",
"SPRequestResetCoverageCounters"];
addMessageListener("SPPingService", this._messageListener);
addMessageListener("SpecialPowers.FilesCreated", this._messageListener);
addMessageListener("SpecialPowers.FilesError", this._messageListener);
mm.addMessageListener("SPPingService", this._messageListener);
mm.addMessageListener("SpecialPowers.FilesCreated", this._messageListener);
mm.addMessageListener("SpecialPowers.FilesError", this._messageListener);
let self = this;
Services.obs.addObserver(function onInnerWindowDestroyed(subject, topic, data) {
var id = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
if (self._windowID === id) {
Services.obs.removeObserver(onInnerWindowDestroyed, "inner-window-destroyed");
try {
removeMessageListener("SPPingService", self._messageListener);
removeMessageListener("SpecialPowers.FilesCreated", self._messageListener);
removeMessageListener("SpecialPowers.FilesError", self._messageListener);
mm.removeMessageListener("SPPingService", self._messageListener);
mm.removeMessageListener("SpecialPowers.FilesCreated", self._messageListener);
mm.removeMessageListener("SpecialPowers.FilesError", self._messageListener);
} catch (e) {
// Ignore the exception which the message manager has been destroyed.
if (e.result != Cr.NS_ERROR_ILLEGAL_VALUE) {
@ -83,33 +92,34 @@ SpecialPowers.prototype._sendSyncMessage = function(msgname, msg) {
if (!this.SP_SYNC_MESSAGES.includes(msgname)) {
dump("TEST-INFO | specialpowers.js | Unexpected SP message: " + msgname + "\n");
}
return sendSyncMessage(msgname, msg);
let result = this.mm.sendSyncMessage(msgname, msg);
return Cu.cloneInto(result, this);
};
SpecialPowers.prototype._sendAsyncMessage = function(msgname, msg) {
if (!this.SP_ASYNC_MESSAGES.includes(msgname)) {
dump("TEST-INFO | specialpowers.js | Unexpected SP message: " + msgname + "\n");
}
sendAsyncMessage(msgname, msg);
this.mm.sendAsyncMessage(msgname, msg);
};
SpecialPowers.prototype._addMessageListener = function(msgname, listener) {
addMessageListener(msgname, listener);
sendAsyncMessage("SPPAddNestedMessageListener", { name: msgname });
this.mm.addMessageListener(msgname, listener);
this.mm.sendAsyncMessage("SPPAddNestedMessageListener", { name: msgname });
};
SpecialPowers.prototype._removeMessageListener = function(msgname, listener) {
removeMessageListener(msgname, listener);
this.mm.removeMessageListener(msgname, listener);
};
SpecialPowers.prototype.registerProcessCrashObservers = function() {
addMessageListener("SPProcessCrashService", this._messageListener);
sendSyncMessage("SPProcessCrashService", { op: "register-observer" });
this.mm.addMessageListener("SPProcessCrashService", this._messageListener);
this.mm.sendSyncMessage("SPProcessCrashService", { op: "register-observer" });
};
SpecialPowers.prototype.unregisterProcessCrashObservers = function() {
removeMessageListener("SPProcessCrashService", this._messageListener);
sendSyncMessage("SPProcessCrashService", { op: "unregister-observer" });
this.mm.removeMessageListener("SPProcessCrashService", this._messageListener);
this.mm.sendSyncMessage("SPProcessCrashService", { op: "unregister-observer" });
};
SpecialPowers.prototype._messageReceived = function(aMessage) {
@ -139,7 +149,7 @@ SpecialPowers.prototype._messageReceived = function(aMessage) {
this._createFilesOnSuccess = null;
this._createFilesOnError = null;
if (createdHandler) {
createdHandler(aMessage.data);
createdHandler(Cu.cloneInto(aMessage.data, this.mm.content));
}
break;
@ -157,7 +167,7 @@ SpecialPowers.prototype._messageReceived = function(aMessage) {
};
SpecialPowers.prototype.quit = function() {
sendAsyncMessage("SpecialPowers.Quit", {});
this.mm.sendAsyncMessage("SpecialPowers.Quit", {});
};
// fileRequests is an array of file requests. Each file request is an object.
@ -172,18 +182,18 @@ SpecialPowers.prototype.createFiles = function(fileRequests, onCreation, onError
this._createFilesOnSuccess = onCreation;
this._createFilesOnError = onError;
sendAsyncMessage("SpecialPowers.CreateFiles", fileRequests);
this.mm.sendAsyncMessage("SpecialPowers.CreateFiles", fileRequests);
};
// Remove the files that were created using |SpecialPowers.createFiles()|.
// This will be automatically called by |SimpleTest.finish()|.
SpecialPowers.prototype.removeFiles = function() {
sendAsyncMessage("SpecialPowers.RemoveFiles", {});
this.mm.sendAsyncMessage("SpecialPowers.RemoveFiles", {});
};
SpecialPowers.prototype.executeAfterFlushingMessageQueue = function(aCallback) {
this._pongHandlers.push(aCallback);
sendAsyncMessage("SPPingService", { op: "ping" });
this.mm.sendAsyncMessage("SPPingService", { op: "ping" });
};
SpecialPowers.prototype.nestedFrameSetup = function() {
@ -215,9 +225,7 @@ SpecialPowers.prototype.nestedFrameSetup = function() {
});
});
mm.loadFrameScript("resource://specialpowers/MozillaLogger.js", false);
mm.loadFrameScript("resource://specialpowers/specialpowersAPI.js", false);
mm.loadFrameScript("resource://specialpowers/specialpowers.js", false);
mm.loadFrameScript("resource://specialpowers/specialpowersFrameScript.js", false);
let frameScript = "SpecialPowers.prototype.IsInNestedFrame=true;";
mm.loadFrameScript("data:," + frameScript, false);
@ -232,13 +240,13 @@ SpecialPowers.prototype.isServiceWorkerRegistered = function() {
};
// Attach our API to the window.
function attachSpecialPowersToWindow(aWindow) {
function attachSpecialPowersToWindow(aWindow, mm) {
try {
if ((aWindow !== null) &&
(aWindow !== undefined) &&
(aWindow.wrappedJSObject) &&
!(aWindow.wrappedJSObject.SpecialPowers)) {
let sp = new SpecialPowers(aWindow);
let sp = new SpecialPowers(aWindow, mm);
aWindow.wrappedJSObject.SpecialPowers = sp;
if (sp.IsInNestedFrame) {
sp.addPermission("allowXULXBL", true, aWindow.document);
@ -249,25 +257,6 @@ function attachSpecialPowersToWindow(aWindow) {
}
}
// This is a frame script, so it may be running in a content process.
// In any event, it is targeted at a specific "tab", so we listen for
// the DOMWindowCreated event to be notified about content windows
// being created in this context.
function SpecialPowersManager() {
addEventListener("DOMWindowCreated", this, false);
}
SpecialPowersManager.prototype = {
handleEvent: function handleEvent(aEvent) {
var window = aEvent.target.defaultView;
attachSpecialPowersToWindow(window);
}
};
var specialpowersmanager = new SpecialPowersManager();
this.SpecialPowers = SpecialPowers;
this.attachSpecialPowersToWindow = attachSpecialPowersToWindow;
@ -276,5 +265,5 @@ this.attachSpecialPowersToWindow = attachSpecialPowersToWindow;
if (typeof window != "undefined") {
window.addMessageListener = function() {};
window.removeMessageListener = function() {};
window.wrappedJSObject.SpecialPowers = new SpecialPowers(window);
window.wrappedJSObject.SpecialPowers = new SpecialPowers(window, window);
}

Просмотреть файл

@ -8,13 +8,17 @@
"use strict";
/* import-globals-from MozillaLogger.js */
/* globals XPCNativeWrapper, content */
/* globals XPCNativeWrapper */
var global = this;
var EXPORTED_SYMBOLS = ["SpecialPowersAPI", "bindDOMWindowUtils"];
ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
Services.scriptloader.loadSubScript("resource://specialpowers/MozillaLogger.js", this);
ChromeUtils.defineModuleGetter(this, "setTimeout",
"resource://gre/modules/Timer.jsm");
ChromeUtils.defineModuleGetter(this, "MockFilePicker",
"resource://specialpowers/MockFilePicker.jsm");
ChromeUtils.defineModuleGetter(this, "MockColorPicker",
@ -582,6 +586,9 @@ SpecialPowersAPI.prototype = {
let messageId = aMessage.json.id;
let name = aMessage.json.name;
let message = aMessage.json.message;
if (this.mm) {
message = new StructuredCloneHolder(message).deserialize(this.mm.content);
}
// Ignore message from other chrome script
if (messageId != id)
return;
@ -754,12 +761,12 @@ SpecialPowersAPI.prototype = {
setTimeout(callback, 0);
// for mochitest-plain
else
content.window.setTimeout(callback, 0);
this.mm.content.setTimeout(callback, 0);
},
_delayCallbackTwice(callback) {
function delayedCallback() {
function delayAgain(aCallback) {
let delayedCallback = () => {
let delayAgain = (aCallback) => {
// Using this._setTimeout doesn't work here
// It causes failures in mochtests that use
// multiple pushPrefEnv calls
@ -768,10 +775,10 @@ SpecialPowersAPI.prototype = {
setTimeout(aCallback, 0);
// For mochitest-plain
else
content.window.setTimeout(aCallback, 0);
}
delayAgain(delayAgain(callback));
}
this.mm.content.setTimeout(aCallback, 0);
};
delayAgain(delayAgain.bind(this, callback));
};
return delayedCallback;
},
@ -1461,10 +1468,10 @@ SpecialPowersAPI.prototype = {
// XXX end of problematic APIs
addChromeEventListener(type, listener, capture, allowUntrusted) {
addEventListener(type, listener, capture, allowUntrusted);
this.mm.addEventListener(type, listener, capture, allowUntrusted);
},
removeChromeEventListener(type, listener, capture) {
removeEventListener(type, listener, capture);
this.mm.removeEventListener(type, listener, capture);
},
// Note: each call to registerConsoleListener MUST be paired with a
@ -1752,7 +1759,7 @@ SpecialPowersAPI.prototype = {
// With aWindow, it is called in SimpleTest.waitForFocus to allow popup window opener focus switching
if (aWindow)
aWindow.focus();
var mm = global;
var mm = this.mm;
if (aWindow) {
let windowMM = aWindow.docShell.messageManager;
if (windowMM) {
@ -1775,7 +1782,7 @@ SpecialPowersAPI.prototype = {
// in e10s b-c tests |content.window| is a CPOW whereas |window| works fine.
// for some non-e10s mochi tests, |window| is null whereas |content.window|
// works fine. So we take whatever is non-null!
xferable.init(this._getDocShell(typeof(window) == "undefined" ? content.window : window)
xferable.init(this._getDocShell(typeof(window) == "undefined" ? this.mm.content.window : window)
.QueryInterface(Ci.nsILoadContext));
xferable.addDataFlavor(flavor);
Services.clipboard.getData(xferable, whichClipboard);
@ -2095,7 +2102,7 @@ SpecialPowersAPI.prototype = {
state = "unloaded";
resolveUnload();
} else if (msg.data.type in handler) {
handler[msg.data.type](...msg.data.args);
handler[msg.data.type](...Cu.cloneInto(msg.data.args, this.window));
} else {
dump(`Unexpected: ${msg.data.type}\n`);
}
@ -2116,7 +2123,7 @@ SpecialPowersAPI.prototype = {
createChromeCache(name, url) {
let principal = this._getPrincipalFromArg(url);
return wrapIfUnwrapped(new content.window.CacheStorage(name, principal));
return wrapIfUnwrapped(new this.mm.content.CacheStorage(name, principal));
},
loadChannelAndReturnStatus(url, loadUsingSystemPrincipal) {

Просмотреть файл

@ -0,0 +1,26 @@
"use strict";
/* globals attachSpecialPowersToWindow */
let mm = this;
ChromeUtils.import("resource://specialpowers/specialpowersAPI.js", this);
ChromeUtils.import("resource://specialpowers/specialpowers.js", this);
// This is a frame script, so it may be running in a content process.
// In any event, it is targeted at a specific "tab", so we listen for
// the DOMWindowCreated event to be notified about content windows
// being created in this context.
function SpecialPowersManager() {
addEventListener("DOMWindowCreated", this, false);
}
SpecialPowersManager.prototype = {
handleEvent: function handleEvent(aEvent) {
var window = aEvent.target.defaultView;
attachSpecialPowersToWindow(window, mm);
}
};
var specialpowersmanager = new SpecialPowersManager();

Просмотреть файл

@ -23,6 +23,7 @@ FINAL_TARGET_FILES.content += [
'content/MozillaLogger.js',
'content/specialpowers.js',
'content/specialpowersAPI.js',
'content/specialpowersFrameScript.js',
'content/SpecialPowersObserver.jsm',
'content/SpecialPowersObserverAPI.js',
]