Bug 1405286: Part 3 - Test that filterResponseData from cached onHeadersReceived doesn't crash. r?mixedpuppy
It currently isn't possible to suspend a channel from onHeadersReceived for a
cached response. And since it's not possible to add a new stream filter after
a response has started, adding a stream filter at that point will crash if the
channel is still registered.
This test is a basic sanity check for that scenario.
MozReview-Commit-ID: ALYUtxX7mci
--- a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_responseBody.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_responseBody.html
@@ -402,16 +402,57 @@ add_task(async function() {
}
} else {
await Promise.all(TASKS.map(runTest));
}
await extension.unload();
});
+// Test that registering a listener for a cached response does not cause a crash.
+add_task(async function test_cachedResponse() {
+ let extension = ExtensionTestUtils.loadExtension({
+ background() {
+ browser.webRequest.onHeadersReceived.addListener(data => {
+ let filter = browser.webRequest.filterResponseData(data.requestId);
+
+ filter.onstop = event => {
+ filter.close();
+ };
+ filter.ondata = event => {
+ filter.write(event.data);
+ };
+
+ if (data.fromCache) {
+ browser.test.sendMessage("from-cache");
+ }
+ }, {
+ urls: ["http://mochi.test/*/file_sample.html"],
+ },
+ ["blocking"]);
+ },
+
+ manifest: {
+ permissions: [
+ "webRequest",
+ "webRequestBlocking",
+ "http://mochi.test/",
+ ],
+ },
+ });
+
+ await extension.startup();
+
+ await fetch("file_sample.html");
+ await fetch("file_sample.html");
+ await extension.awaitMessage("from-cache");
+
+ await extension.unload();
+});
+
add_task(async function test_permissions() {
let extension = ExtensionTestUtils.loadExtension({
background() {
browser.test.assertEq(
undefined, browser.webRequest.filterResponseData,
"filterResponseData is undefined without blocking permissions");
},
--- a/toolkit/components/extensions/webrequest/ChannelWrapper.cpp
+++ b/toolkit/components/extensions/webrequest/ChannelWrapper.cpp
@@ -625,16 +625,22 @@ ChannelWrapper::GetFrameAncestors(nsILoa
/*****************************************************************************
* Response filtering
*****************************************************************************/
void
ChannelWrapper::RegisterTraceableChannel(const WebExtensionPolicy& aAddon, nsITabParent* aTabParent)
{
+ // We can't register new listeners after the response has started, so don't
+ // bother registering anything.
+ if (mResponseStarted) {
+ return;
+ }
+
mAddonEntries.Put(aAddon.Id(), aTabParent);
if (!mChannelEntry) {
mChannelEntry = WebRequestService::GetSingleton().RegisterChannel(this);
CheckEventListeners();
}
}
already_AddRefed<nsITraceableChannel>
@@ -909,16 +915,17 @@ ChannelWrapper::RequestListener::Init()
}
NS_IMETHODIMP
ChannelWrapper::RequestListener::OnStartRequest(nsIRequest *request, nsISupports * aCtxt)
{
MOZ_ASSERT(mOrigStreamListener, "Should have mOrigStreamListener");
mChannelWrapper->mChannelEntry = nullptr;
+ mChannelWrapper->mResponseStarted = true;
mChannelWrapper->ErrorCheck();
mChannelWrapper->FireEvent(NS_LITERAL_STRING("start"));
return mOrigStreamListener->OnStartRequest(request, aCtxt);
}
NS_IMETHODIMP
ChannelWrapper::RequestListener::OnStopRequest(nsIRequest *request, nsISupports *aCtxt,
--- a/toolkit/components/extensions/webrequest/ChannelWrapper.h
+++ b/toolkit/components/extensions/webrequest/ChannelWrapper.h
@@ -283,16 +283,17 @@ private:
nsCString mContentTypeHdr = VoidCString();
const uint64_t mId = GetNextId();
nsCOMPtr<nsISupports> mParent;
bool mAddedStreamListener = false;
bool mFiredErrorEvent = false;
bool mSuspended = false;
+ bool mResponseStarted = false;
nsInterfaceHashtable<nsPtrHashKey<const nsAtom>, nsITabParent> mAddonEntries;
class RequestListener final : public nsIStreamListener
, public nsIThreadRetargetableStreamListener
{
--- a/toolkit/modules/addons/WebRequest.jsm
+++ b/toolkit/modules/addons/WebRequest.jsm
@@ -161,17 +161,17 @@ class ResponseHeaderChanger extends Head
}
readHeaders() {
return this.channel.getResponseHeaders();
}
}
const MAYBE_CACHED_EVENTS = new Set([
- "onResponseStarted", "onBeforeRedirect", "onCompleted", "onErrorOccurred",
+ "onResponseStarted", "onHeadersReceived", "onBeforeRedirect", "onCompleted", "onErrorOccurred",
]);
const OPTIONAL_PROPERTIES = [
"requestHeaders", "responseHeaders", "statusCode", "statusLine", "error", "redirectUrl",
"requestBody", "scheme", "realm", "isProxy", "challenger", "proxyInfo", "ip", "frameAncestors",
];
function serializeRequestData(eventName) {