Bug 1541557: Part 6 - Read scripts for loadChromeScript in child process rather than parent. r=nika

`loadChromeScript` is often called with http: URLs pointing to mochitest
resources, which need to be read synchronously before they can be executed.
Unfortunately, while the API for this pretends to be synchronous, it really
spins the event loop underneath.

When we attempt to use it in the parent, that means that we spin the event
loop and process messages from the child before the script has been executed.
And since those messages often contain messages intended for the chrome
script, that causes problems.

When the chrome script APIs use sync messaging, this doesn't matter much,
since the `loadChromeScript` call blocks the caller until the message handler
in the parent returns. When it uses async messaging, though, we have no such
luck, and the messages intended for the script get sent to the parent
immediately.


Loading the script contents in the child solves this problem, since it
reliably blocks the child callers until the script contents are ready, and
doesn't give them a chance to try to send messages to the script while it's
still being read.

Differential Revision: https://phabricator.services.mozilla.com/D35056

--HG--
extra : rebase_source : 137a244f2c071977ee633631de05f7fd776e9b88
extra : source : c2697f04d38cf0b01b1f3e227910ab5890926a33
This commit is contained in:
Kris Maglione 2019-06-13 17:01:10 -07:00
parent 3e44c16cf9
commit 0f5c62c855
2 changed files with 49 additions and 44 deletions

View File

@ -235,6 +235,46 @@ class SpecialPowersAPI {
return mc.port2;
}
_readUrlAsString(aUrl) {
// Fetch script content as we can't use scriptloader's loadSubScript
// to evaluate http:// urls...
var scriptableStream = Cc["@mozilla.org/scriptableinputstream;1"]
.getService(Ci.nsIScriptableInputStream);
var channel = NetUtil.newChannel({
uri: aUrl,
loadUsingSystemPrincipal: true,
});
var input = channel.open();
scriptableStream.init(input);
var str;
var buffer = [];
while ((str = scriptableStream.read(4096))) {
buffer.push(str);
}
var output = buffer.join("");
scriptableStream.close();
input.close();
var status;
if (channel instanceof Ci.nsIHttpChannel) {
status = channel.responseStatus;
}
if (status == 404) {
throw new Error(
`Error while executing chrome script '${aUrl}':\n` +
"The script doesn't exist. Ensure you have registered it in " +
"'support-files' in your mochitest.ini.");
}
return output;
}
loadChromeScript(urlOrFunction, sandboxOptions) {
// Create a unique id for this chrome script
let uuidGenerator = Cc["@mozilla.org/uuid-generator;1"]
@ -249,6 +289,14 @@ class SpecialPowersAPI {
name: urlOrFunction.name,
};
} else {
// Note: We need to do this in the child since, even though
// `_readUrlAsString` pretends to be synchronous, its channel
// winds up spinning the event loop when loading HTTP URLs. That
// leads to unexpected out-of-order operations if the child sends
// a message immediately after loading the script.
scriptArgs.function = {
body: this._readUrlAsString(urlOrFunction),
};
scriptArgs.url = urlOrFunction;
}
this._sendSyncMessage("SPLoadChromeScript",

View File

@ -7,7 +7,6 @@
var EXPORTED_SYMBOLS = ["SpecialPowersObserverAPI", "SpecialPowersError"];
var {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
var {NetUtil} = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetters(this, {
@ -206,46 +205,6 @@ class SpecialPowersObserverAPI {
return Services.io.newURI(url);
}
_readUrlAsString(aUrl) {
// Fetch script content as we can't use scriptloader's loadSubScript
// to evaluate http:// urls...
var scriptableStream = Cc["@mozilla.org/scriptableinputstream;1"]
.getService(Ci.nsIScriptableInputStream);
var channel = NetUtil.newChannel({
uri: aUrl,
loadUsingSystemPrincipal: true,
});
var input = channel.open();
scriptableStream.init(input);
var str;
var buffer = [];
while ((str = scriptableStream.read(4096))) {
buffer.push(str);
}
var output = buffer.join("");
scriptableStream.close();
input.close();
var status;
if (channel instanceof Ci.nsIHttpChannel) {
status = channel.responseStatus;
}
if (status == 404) {
throw new SpecialPowersError(
"Error while executing chrome script '" + aUrl + "':\n" +
"The script doesn't exists. Ensure you have registered it in " +
"'support-files' in your mochitest.ini.");
}
return output;
}
_sendReply(aMessage, aReplyName, aReplyMsg) {
let mm = aMessage.target.frameLoader
.messageManager;
@ -446,14 +405,12 @@ class SpecialPowersObserverAPI {
case "SPLoadChromeScript": {
let id = aMessage.json.id;
let jsScript;
let scriptName;
let jsScript = aMessage.json.function.body;
if (aMessage.json.url) {
jsScript = this._readUrlAsString(aMessage.json.url);
scriptName = aMessage.json.url;
} else if (aMessage.json.function) {
jsScript = aMessage.json.function.body;
scriptName = aMessage.json.function.name
|| "<loadChromeScript anonymous function>";
} else {