2 // Tests that pending subframe requests for an initial about:blank
3 // document do not delay showing load errors (and possibly result in a
4 // crash at docShell destruction) for failed document loads.
6 const { PromiseTestUtils } = ChromeUtils.importESModule(
7 "resource://testing-common/PromiseTestUtils.sys.mjs"
9 PromiseTestUtils.allowMatchingRejectionsGlobally(/undefined/);
11 const { XPCShellContentUtils } = ChromeUtils.importESModule(
12 "resource://testing-common/XPCShellContentUtils.sys.mjs"
15 XPCShellContentUtils.init(this);
17 const server = XPCShellContentUtils.createHttpServer({
18 hosts: ["example.com"],
21 // Registers a URL with the HTTP server which will not return a response
22 // until we're ready to.
23 function registerSlowPage(path) {
25 url: `http://example.com/${path}`,
28 let finishedPromise = new Promise(resolve => {
29 result.finish = resolve;
32 server.registerPathHandler(`/${path}`, async (request, response) => {
33 response.processAsync();
35 response.setHeader("Content-Type", "text/html");
36 response.write("<html><body>Hello.</body></html>");
38 await finishedPromise;
46 let topFrameRequest = registerSlowPage("top.html");
47 let subFrameRequest = registerSlowPage("frame.html");
49 let thunks = new Set();
50 function promiseStateStop(webProgress) {
51 return new Promise(resolve => {
53 onStateChange(aWebProgress, request, stateFlags) {
54 if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
55 webProgress.removeProgressListener(listener);
57 thunks.delete(listener);
62 QueryInterface: ChromeUtils.generateQI([
63 "nsIWebProgressListener",
64 "nsISupportsWeakReference",
68 // Keep the listener alive, since it's stored as a weak reference.
70 webProgress.addProgressListener(
72 Ci.nsIWebProgress.NOTIFY_STATE_NETWORK
77 async function runTest(waitForErrorPage) {
78 let page = await XPCShellContentUtils.loadContentPage("about:blank");
80 // Watch for the HTTP request for the top frame so that we can cancel
82 let requestPromise = TestUtils.topicObserved(
83 "http-on-modify-request",
84 subject => subject.QueryInterface(Ci.nsIRequest).name == topFrameRequest.url
88 [topFrameRequest.url, subFrameRequest.url],
89 function (topFrameUrl, subFrameRequestUrl) {
90 // Create a frame with a source URL which will not return a response
91 // before we cancel it with an error.
92 let doc = this.content.document;
93 let frame = doc.createElement("iframe");
94 frame.src = topFrameUrl;
95 doc.body.appendChild(frame);
97 // Create a subframe in the initial about:blank document for the above
98 // frame which will not return a response before we cancel the
100 let frameDoc = frame.contentDocument;
101 let subframe = frameDoc.createElement("iframe");
102 subframe.src = subFrameRequestUrl;
103 frameDoc.body.appendChild(subframe);
107 let [req] = await requestPromise;
109 info("Cancel request for parent frame");
110 req.cancel(Cr.NS_ERROR_PROXY_CONNECTION_REFUSED);
112 // Request cancelation is not synchronous, so wait for the STATE_STOP
114 await promiseStateStop(page.browsingContext.webProgress);
116 // And make a trip through the event loop to give the DocLoader a
117 // chance to update its state.
118 await new Promise(executeSoon);
120 if (waitForErrorPage) {
121 // Make sure that canceling the request with an error code actually
122 // shows an error page without waiting for a subframe response.
123 await TestUtils.waitForCondition(() =>
124 page.browsingContext.children[0]?.currentWindowGlobal?.documentURI?.spec.startsWith(
133 add_task(async function testRemoveFrameImmediately() {
134 await runTest(false);
137 add_task(async function testRemoveFrameAfterErrorPage() {
141 add_task(async function () {
142 // Allow the document requests for the frames to complete.
143 topFrameRequest.finish();
144 subFrameRequest.finish();