Bug 1696473: End subframe loads for initial about:blank document on request error. r=nika

Differential Revision: https://phabricator.services.mozilla.com/D112289
This commit is contained in:
Kris Maglione 2021-05-04 21:19:51 +00:00
parent 314594a5ea
commit c40721b2ea
5 changed files with 158 additions and 1 deletions

View File

@ -6572,7 +6572,9 @@ nsresult nsDocShell::EndPageLoad(nsIWebProgress* aProgress,
isInitialDocument,
&skippedUnknownProtocolNavigation);
if (NS_FAILED(aStatus)) {
DisplayLoadError(aStatus, url, nullptr, aChannel);
if (!mIsBeingDestroyed) {
DisplayLoadError(aStatus, url, nullptr, aChannel);
}
} else if (skippedUnknownProtocolNavigation) {
nsTArray<nsString> params;
if (NS_FAILED(

View File

@ -12,6 +12,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
SearchUtils: "resource://gre/modules/SearchUtils.jsm",
SearchTestUtils: "resource://testing-common/SearchTestUtils.jsm",
Services: "resource://gre/modules/Services.jsm",
TestUtils: "resource://testing-common/TestUtils.jsm",
});
var dirSvc = Services.dirSvc;

View File

@ -0,0 +1,140 @@
"use strict";
// Tests that pending subframe requests for an initial about:blank
// document do not delay showing load errors (and possibly result in a
// crash at docShell destruction) for failed document loads.
const { ExtensionTestUtils } = ChromeUtils.import(
"resource://testing-common/ExtensionXPCShellUtils.jsm"
);
AddonTestUtils.init(this);
ExtensionTestUtils.init(this);
const server = AddonTestUtils.createHttpServer({ hosts: ["example.com"] });
// Registers a URL with the HTTP server which will not return a response
// until we're ready to.
function registerSlowPage(path) {
let result = {
url: `http://example.com/${path}`,
};
let finishedPromise = new Promise(resolve => {
result.finish = resolve;
});
server.registerPathHandler(`/${path}`, async (request, response) => {
response.processAsync();
response.setHeader("Content-Type", "text/html");
response.write("<html><body>Hello.</body></html>");
await finishedPromise;
response.finish();
});
return result;
}
let topFrameRequest = registerSlowPage("top.html");
let subFrameRequest = registerSlowPage("frame.html");
let thunks = new Set();
function promiseStateStop(webProgress) {
return new Promise(resolve => {
let listener = {
onStateChange(aWebProgress, request, stateFlags, status) {
if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
webProgress.removeProgressListener(listener);
thunks.delete(listener);
resolve();
}
},
QueryInterface: ChromeUtils.generateQI([
"nsIWebProgressListener",
"nsISupportsWeakReference",
]),
};
// Keep the listener alive, since it's stored as a weak reference.
thunks.add(listener);
webProgress.addProgressListener(
listener,
Ci.nsIWebProgress.NOTIFY_STATE_REQUEST
);
});
}
async function runTest(waitForErrorPage) {
let page = await ExtensionTestUtils.loadContentPage("about:blank", {
remote: false,
});
let { browser } = page;
// Watch for the HTTP request for the top frame so that we can cancel
// it with an error.
let requestPromise = TestUtils.topicObserved(
"http-on-modify-request",
subject => subject.QueryInterface(Ci.nsIRequest).name == topFrameRequest.url
);
// Create a frame with a source URL which will not return a response
// before we cancel it with an error.
let doc = browser.contentDocument;
let frame = doc.createElement("iframe");
frame.src = topFrameRequest.url;
doc.body.appendChild(frame);
// Create a subframe in the initial about:blank document for the above
// frame which will not return a response before we cancel the
// document request.
let frameDoc = frame.contentDocument;
let subframe = frameDoc.createElement("iframe");
subframe.src = subFrameRequest.url;
frameDoc.body.appendChild(subframe);
let [req] = await requestPromise;
info("Cancel request for parent frame");
req.cancel(Cr.NS_ERROR_PROXY_CONNECTION_REFUSED);
// Request cancelation is not synchronous, so wait for the STATE_STOP
// event to fire.
await promiseStateStop(
browser.docShell.nsIInterfaceRequestor.getInterface(Ci.nsIWebProgress)
);
// And make a trip through the event loop to give the DocLoader a
// chance to update its state.
await new Promise(executeSoon);
if (waitForErrorPage) {
// Make sure that canceling the request with an error code actually
// shows an error page without waiting for a subframe response.
await TestUtils.waitForCondition(() =>
frame.contentDocument.documentURI.startsWith("about:neterror?")
);
}
info("Remove frame");
frame.remove();
await page.close();
}
add_task(async function testRemoveFrameImmediately() {
await runTest(false);
});
add_task(async function testRemoveFrameAfterErrorPage() {
await runTest(true);
});
add_task(async function() {
// Allow the document requests for the frames to complete.
topFrameRequest.finish();
subFrameRequest.finish();
});

View File

@ -14,3 +14,4 @@ skip-if = os == 'android'
# Bug 751575: unrelated JS changes cause timeouts on random platforms
skip-if = true
[test_privacy_transition.js]
[test_subframe_stop_after_parent_error.js]

View File

@ -641,6 +641,19 @@ nsDocLoader::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) {
// when address of the request is reused.
RemoveRequestInfo(aRequest);
// For the special case where the current document is an initial about:blank
// document, we may still have subframes loading, and keeping the DocLoader
// busy. In that case, if we have an error, we won't show it until those
// frames finish loading, which is nonsensical. So stop any subframe loads
// now.
if (NS_FAILED(aStatus) && aStatus != NS_BINDING_ABORTED) {
if (RefPtr<Document> doc = do_GetInterface(GetAsSupports(this))) {
if (doc->IsInitialDocument()) {
NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mChildList, Stop, ());
}
}
}
//
// Only fire the DocLoaderIsEmpty(...) if we may need to fire onload.
//