diff options
Diffstat (limited to '')
-rw-r--r-- | docshell/test/unit/test_subframe_stop_after_parent_error.js | 145 |
1 files changed, 145 insertions, 0 deletions
diff --git a/docshell/test/unit/test_subframe_stop_after_parent_error.js b/docshell/test/unit/test_subframe_stop_after_parent_error.js new file mode 100644 index 0000000000..ee1876b4b9 --- /dev/null +++ b/docshell/test/unit/test_subframe_stop_after_parent_error.js @@ -0,0 +1,145 @@ +"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 { PromiseTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PromiseTestUtils.sys.mjs" +); +PromiseTestUtils.allowMatchingRejectionsGlobally(/undefined/); + +const { XPCShellContentUtils } = ChromeUtils.importESModule( + "resource://testing-common/XPCShellContentUtils.sys.mjs" +); + +XPCShellContentUtils.init(this); + +const server = XPCShellContentUtils.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_NETWORK + ); + }); +} + +async function runTest(waitForErrorPage) { + let page = await XPCShellContentUtils.loadContentPage("about:blank"); + + // 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 + ); + + await page.spawn( + [topFrameRequest.url, subFrameRequest.url], + function (topFrameUrl, subFrameRequestUrl) { + // Create a frame with a source URL which will not return a response + // before we cancel it with an error. + let doc = this.content.document; + let frame = doc.createElement("iframe"); + frame.src = topFrameUrl; + 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 = subFrameRequestUrl; + 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(page.browsingContext.webProgress); + + // 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(() => + page.browsingContext.children[0]?.currentWindowGlobal?.documentURI?.spec.startsWith( + "about:neterror?" + ) + ); + } + + 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(); +}); |