mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-24 02:35:41 +00:00
Bug 1447551 Part 2: Convert webRequest to persistent events r=mixedpuppy,kmag
MozReview-Commit-ID: ANprpK8Kw5Q --HG-- extra : rebase_source : fb3283c36cd392dbb1713e856204226df840930d
This commit is contained in:
parent
7af2a23072
commit
4ad9f49ebe
@ -7,96 +7,120 @@
|
||||
ChromeUtils.defineModuleGetter(this, "WebRequest",
|
||||
"resource://gre/modules/WebRequest.jsm");
|
||||
|
||||
// EventManager-like class specifically for WebRequest. Inherits from
|
||||
// EventManager. Takes care of converting |details| parameter
|
||||
// when invoking listeners.
|
||||
function WebRequestEventManager(context, eventName) {
|
||||
let name = `webRequest.${eventName}`;
|
||||
let register = (fire, filter, info) => {
|
||||
let listener = data => {
|
||||
let browserData = {tabId: -1, windowId: -1};
|
||||
if (data.browser) {
|
||||
browserData = tabTracker.getBrowserData(data.browser);
|
||||
}
|
||||
if (filter.tabId != null && browserData.tabId != filter.tabId) {
|
||||
return;
|
||||
}
|
||||
if (filter.windowId != null && browserData.windowId != filter.windowId) {
|
||||
return;
|
||||
}
|
||||
|
||||
let event = data.serialize(eventName);
|
||||
event.tabId = browserData.tabId;
|
||||
|
||||
return fire.sync(event);
|
||||
};
|
||||
|
||||
let filter2 = {};
|
||||
if (filter.urls) {
|
||||
let perms = new MatchPatternSet([...context.extension.whiteListedHosts.patterns,
|
||||
...context.extension.optionalOrigins.patterns]);
|
||||
|
||||
filter2.urls = new MatchPatternSet(filter.urls);
|
||||
|
||||
if (!perms.overlapsAll(filter2.urls)) {
|
||||
Cu.reportError("The webRequest.addListener filter doesn't overlap with host permissions.");
|
||||
}
|
||||
// The guts of a WebRequest event handler. Takes care of converting
|
||||
// |details| parameter when invoking listeners.
|
||||
function registerEvent(extension, eventName, fire, filter, info, tabParent = null) {
|
||||
let listener = async data => {
|
||||
let browserData = {tabId: -1, windowId: -1};
|
||||
if (data.browser) {
|
||||
browserData = tabTracker.getBrowserData(data.browser);
|
||||
}
|
||||
if (filter.types) {
|
||||
filter2.types = filter.types;
|
||||
if (filter.tabId != null && browserData.tabId != filter.tabId) {
|
||||
return;
|
||||
}
|
||||
if (filter.tabId) {
|
||||
filter2.tabId = filter.tabId;
|
||||
}
|
||||
if (filter.windowId) {
|
||||
filter2.windowId = filter.windowId;
|
||||
if (filter.windowId != null && browserData.windowId != filter.windowId) {
|
||||
return;
|
||||
}
|
||||
|
||||
let blockingAllowed = context.extension.hasPermission("webRequestBlocking");
|
||||
let event = data.serialize(eventName);
|
||||
event.tabId = browserData.tabId;
|
||||
|
||||
let info2 = [];
|
||||
if (info) {
|
||||
for (let desc of info) {
|
||||
if (desc == "blocking" && !blockingAllowed) {
|
||||
Cu.reportError("Using webRequest.addListener with the blocking option " +
|
||||
"requires the 'webRequestBlocking' permission.");
|
||||
} else {
|
||||
info2.push(desc);
|
||||
}
|
||||
if (data.registerTraceableChannel) {
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
data.registerTraceableChannel(extension.policy, tabParent);
|
||||
}
|
||||
|
||||
let listenerDetails = {
|
||||
addonId: context.extension.id,
|
||||
extension: context.extension.policy,
|
||||
blockingAllowed,
|
||||
tabParent: context.xulBrowser.frameLoader.tabParent,
|
||||
};
|
||||
|
||||
WebRequest[eventName].addListener(
|
||||
listener, filter2, info2,
|
||||
listenerDetails);
|
||||
return () => {
|
||||
WebRequest[eventName].removeListener(listener);
|
||||
};
|
||||
return fire.sync(event);
|
||||
};
|
||||
|
||||
return new EventManager({context, name, register}).api();
|
||||
let filter2 = {};
|
||||
if (filter.urls) {
|
||||
let perms = new MatchPatternSet([...extension.whiteListedHosts.patterns,
|
||||
...extension.optionalOrigins.patterns]);
|
||||
|
||||
filter2.urls = new MatchPatternSet(filter.urls);
|
||||
|
||||
if (!perms.overlapsAll(filter2.urls)) {
|
||||
Cu.reportError("The webRequest.addListener filter doesn't overlap with host permissions.");
|
||||
}
|
||||
}
|
||||
if (filter.types) {
|
||||
filter2.types = filter.types;
|
||||
}
|
||||
if (filter.tabId) {
|
||||
filter2.tabId = filter.tabId;
|
||||
}
|
||||
if (filter.windowId) {
|
||||
filter2.windowId = filter.windowId;
|
||||
}
|
||||
|
||||
let blockingAllowed = extension.hasPermission("webRequestBlocking");
|
||||
|
||||
let info2 = [];
|
||||
if (info) {
|
||||
for (let desc of info) {
|
||||
if (desc == "blocking" && !blockingAllowed) {
|
||||
Cu.reportError("Using webRequest.addListener with the blocking option " +
|
||||
"requires the 'webRequestBlocking' permission.");
|
||||
} else {
|
||||
info2.push(desc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let listenerDetails = {
|
||||
addonId: extension.id,
|
||||
extension: extension.policy,
|
||||
blockingAllowed,
|
||||
};
|
||||
|
||||
WebRequest[eventName].addListener(
|
||||
listener, filter2, info2,
|
||||
listenerDetails);
|
||||
|
||||
return {
|
||||
unregister: () => { WebRequest[eventName].removeListener(listener); },
|
||||
convert(_fire, context) {
|
||||
fire = _fire;
|
||||
tabParent = context.xulBrowser.frameLoader.tabParent;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function makeWebRequestEvent(context, name) {
|
||||
return new EventManager({
|
||||
context,
|
||||
name: `webRequest.${name}`,
|
||||
persistent: {
|
||||
module: "webRequest",
|
||||
event: name,
|
||||
},
|
||||
register: (fire, filter, info) => {
|
||||
return registerEvent(context.extension, name, fire, filter, info,
|
||||
context.xulBrowser.frameLoader.tabParent).unregister;
|
||||
},
|
||||
}).api();
|
||||
}
|
||||
|
||||
this.webRequest = class extends ExtensionAPI {
|
||||
primeListener(extension, event, fire, params) {
|
||||
return registerEvent(extension, event, fire, ...params);
|
||||
}
|
||||
|
||||
getAPI(context) {
|
||||
return {
|
||||
webRequest: {
|
||||
onBeforeRequest: WebRequestEventManager(context, "onBeforeRequest"),
|
||||
onBeforeSendHeaders: WebRequestEventManager(context, "onBeforeSendHeaders"),
|
||||
onSendHeaders: WebRequestEventManager(context, "onSendHeaders"),
|
||||
onHeadersReceived: WebRequestEventManager(context, "onHeadersReceived"),
|
||||
onAuthRequired: WebRequestEventManager(context, "onAuthRequired"),
|
||||
onBeforeRedirect: WebRequestEventManager(context, "onBeforeRedirect"),
|
||||
onResponseStarted: WebRequestEventManager(context, "onResponseStarted"),
|
||||
onErrorOccurred: WebRequestEventManager(context, "onErrorOccurred"),
|
||||
onCompleted: WebRequestEventManager(context, "onCompleted"),
|
||||
onBeforeRequest: makeWebRequestEvent(context, "onBeforeRequest"),
|
||||
onBeforeSendHeaders: makeWebRequestEvent(context, "onBeforeSendHeaders"),
|
||||
onSendHeaders: makeWebRequestEvent(context, "onSendHeaders"),
|
||||
onHeadersReceived: makeWebRequestEvent(context, "onHeadersReceived"),
|
||||
onAuthRequired: makeWebRequestEvent(context, "onAuthRequired"),
|
||||
onBeforeRedirect: makeWebRequestEvent(context, "onBeforeRedirect"),
|
||||
onResponseStarted: makeWebRequestEvent(context, "onResponseStarted"),
|
||||
onErrorOccurred: makeWebRequestEvent(context, "onErrorOccurred"),
|
||||
onCompleted: makeWebRequestEvent(context, "onCompleted"),
|
||||
handlerBehaviorChanged: function() {
|
||||
// TODO: Flush all caches.
|
||||
},
|
||||
|
@ -0,0 +1,172 @@
|
||||
"use strict";
|
||||
|
||||
PromiseTestUtils.whitelistRejectionsGlobally(/Message manager disconnected/);
|
||||
|
||||
AddonTestUtils.init(this);
|
||||
AddonTestUtils.overrideCertDB();
|
||||
AddonTestUtils.createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "43");
|
||||
|
||||
let {
|
||||
promiseRestartManager,
|
||||
promiseShutdownManager,
|
||||
promiseStartupManager,
|
||||
} = AddonTestUtils;
|
||||
|
||||
const server = createHttpServer({hosts: ["example.com"]});
|
||||
server.registerDirectory("/data/", do_get_file("data"));
|
||||
|
||||
Services.prefs.setBoolPref("extensions.webextensions.background-delayed-startup", true);
|
||||
|
||||
function trackEvents(wrapper) {
|
||||
let events = new Map();
|
||||
for (let event of ["background-page-event", "start-background-page"]) {
|
||||
events.set(event, false);
|
||||
wrapper.extension.once(event, () => events.set(event, true));
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
// Test that a non-blocking listener during startup does not immediately
|
||||
// start the background page, but the event is queued until the background
|
||||
// page is started.
|
||||
add_task(async function() {
|
||||
await promiseStartupManager();
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
useAddonManager: "permanent",
|
||||
manifest: {
|
||||
permissions: ["webRequest", "http://example.com/"],
|
||||
},
|
||||
|
||||
background() {
|
||||
browser.webRequest.onBeforeRequest.addListener(details => {
|
||||
browser.test.sendMessage("saw-request");
|
||||
}, {urls: ["http://example.com/data/file_sample.html"]});
|
||||
},
|
||||
});
|
||||
|
||||
await extension.startup();
|
||||
|
||||
await promiseRestartManager(false);
|
||||
|
||||
let events = trackEvents(extension);
|
||||
|
||||
await ExtensionTestUtils.fetch("http://example.com/",
|
||||
"http://example.com/data/file_sample.html");
|
||||
|
||||
equal(events.get("background-page-event"), true,
|
||||
"Should have gotten a background page event");
|
||||
equal(events.get("start-background-page"), false,
|
||||
"Background page should not be started");
|
||||
|
||||
Services.obs.notifyObservers(null, "browser-delayed-startup-finished");
|
||||
await new Promise(executeSoon);
|
||||
|
||||
equal(events.get("start-background-page"), true,
|
||||
"Should have gotten start-background-page event");
|
||||
|
||||
await extension.awaitMessage("saw-request");
|
||||
ok(true, "Background page loaded and received webRequest event");
|
||||
|
||||
await extension.unload();
|
||||
|
||||
await promiseShutdownManager();
|
||||
});
|
||||
|
||||
// Tests that filters are handled properly: if we have a blocking listener
|
||||
// with a filter, a request that does not match the filter does not get
|
||||
// suspended and does not start the background page.
|
||||
add_task(async function() {
|
||||
await promiseStartupManager();
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
useAddonManager: "permanent",
|
||||
manifest: {
|
||||
permissions: ["webRequest", "webRequestBlocking",
|
||||
"http://test1.example.com/"],
|
||||
},
|
||||
|
||||
background() {
|
||||
browser.webRequest.onBeforeRequest.addListener(details => {
|
||||
browser.test.fail("Listener should not have been called");
|
||||
}, {urls: ["http://test1.example.com/*"]}, ["blocking"]);
|
||||
|
||||
browser.test.sendMessage("ready");
|
||||
},
|
||||
});
|
||||
|
||||
await extension.startup();
|
||||
await extension.awaitMessage("ready");
|
||||
|
||||
await promiseRestartManager(false);
|
||||
|
||||
let events = trackEvents(extension);
|
||||
|
||||
await ExtensionTestUtils.fetch("http://example.com/",
|
||||
"http://example.com/data/file_sample.html");
|
||||
|
||||
equal(events.get("background-page-event"), false,
|
||||
"Should not have gotten a background page event");
|
||||
|
||||
Services.obs.notifyObservers(null, "browser-delayed-startup-finished");
|
||||
await new Promise(executeSoon);
|
||||
|
||||
equal(events.get("start-background-page"), false,
|
||||
"Should not have tried to start background page yet");
|
||||
|
||||
Services.obs.notifyObservers(null, "sessionstore-windows-restored");
|
||||
await extension.awaitMessage("ready");
|
||||
|
||||
await extension.unload();
|
||||
await promiseShutdownManager();
|
||||
});
|
||||
|
||||
// Test that a block listener that uses filterResponseData() works
|
||||
// properly (i.e., that the delayed call to registerTraceableChannel
|
||||
// works properly).
|
||||
add_task(async function() {
|
||||
const DATA = `<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<h1>This is a modified page</h1>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
function background(data) {
|
||||
browser.webRequest.onBeforeRequest.addListener(details => {
|
||||
let filter = browser.webRequest.filterResponseData(details.requestId);
|
||||
filter.onstop = () => {
|
||||
let encoded = new TextEncoder("utf-8").encode(data);
|
||||
filter.write(encoded);
|
||||
filter.close();
|
||||
};
|
||||
}, {urls: ["http://example.com/data/file_sample.html"]}, ["blocking"]);
|
||||
}
|
||||
|
||||
await promiseStartupManager();
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
useAddonManager: "permanent",
|
||||
manifest: {
|
||||
permissions: ["webRequest", "webRequestBlocking", "http://example.com/"],
|
||||
},
|
||||
|
||||
background: `(${background})(${uneval(DATA)})`,
|
||||
});
|
||||
|
||||
await extension.startup();
|
||||
|
||||
await promiseRestartManager(false);
|
||||
|
||||
let dataPromise = ExtensionTestUtils.fetch("http://example.com/",
|
||||
"http://example.com/data/file_sample.html");
|
||||
|
||||
Services.obs.notifyObservers(null, "browser-delayed-startup-finished");
|
||||
let data = await dataPromise;
|
||||
|
||||
equal(data, DATA, "Stream filter was properly installed for a load during startup");
|
||||
|
||||
await extension.unload();
|
||||
await promiseShutdownManager();
|
||||
});
|
||||
|
@ -115,6 +115,7 @@ skip-if = true # Too frequent intermittent failures
|
||||
[test_ext_webRequest_permission.js]
|
||||
[test_ext_webRequest_responseBody.js]
|
||||
[test_ext_webRequest_set_cookie.js]
|
||||
[test_ext_webRequest_startup.js]
|
||||
[test_ext_webRequest_suspend.js]
|
||||
[test_ext_webRequest_webSocket.js]
|
||||
[test_ext_xhr_capabilities.js]
|
||||
|
@ -756,7 +756,9 @@ HttpObserverManager = {
|
||||
let data = Object.create(commonData);
|
||||
|
||||
if (registerFilter && opts.blocking && opts.extension) {
|
||||
channel.registerTraceableChannel(opts.extension, opts.tabParent);
|
||||
data.registerTraceableChannel = (extension, tabParent) => {
|
||||
channel.registerTraceableChannel(extension, tabParent);
|
||||
};
|
||||
}
|
||||
|
||||
if (opts.requestHeaders) {
|
||||
|
Loading…
Reference in New Issue
Block a user