test_subframe_stop_after_parent_error.js (4280B)
1 "use strict"; 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. 5 6 const { PromiseTestUtils } = ChromeUtils.importESModule( 7 "resource://testing-common/PromiseTestUtils.sys.mjs" 8 ); 9 PromiseTestUtils.allowMatchingRejectionsGlobally(/undefined/); 10 11 const { XPCShellContentUtils } = ChromeUtils.importESModule( 12 "resource://testing-common/XPCShellContentUtils.sys.mjs" 13 ); 14 15 XPCShellContentUtils.init(this); 16 17 const server = XPCShellContentUtils.createHttpServer({ 18 hosts: ["example.com"], 19 }); 20 21 // Registers a URL with the HTTP server which will not return a response 22 // until we're ready to. 23 function registerSlowPage(path) { 24 let result = { 25 url: `http://example.com/${path}`, 26 }; 27 28 let finishedPromise = new Promise(resolve => { 29 result.finish = resolve; 30 }); 31 32 server.registerPathHandler(`/${path}`, async (request, response) => { 33 response.processAsync(); 34 35 response.setHeader("Content-Type", "text/html"); 36 response.write("<html><body>Hello.</body></html>"); 37 38 await finishedPromise; 39 40 response.finish(); 41 }); 42 43 return result; 44 } 45 46 let topFrameRequest = registerSlowPage("top.html"); 47 let subFrameRequest = registerSlowPage("frame.html"); 48 49 let thunks = new Set(); 50 function promiseStateStop(webProgress) { 51 return new Promise(resolve => { 52 let listener = { 53 onStateChange(aWebProgress, request, stateFlags) { 54 if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) { 55 webProgress.removeProgressListener(listener); 56 57 thunks.delete(listener); 58 resolve(); 59 } 60 }, 61 62 QueryInterface: ChromeUtils.generateQI([ 63 "nsIWebProgressListener", 64 "nsISupportsWeakReference", 65 ]), 66 }; 67 68 // Keep the listener alive, since it's stored as a weak reference. 69 thunks.add(listener); 70 webProgress.addProgressListener( 71 listener, 72 Ci.nsIWebProgress.NOTIFY_STATE_NETWORK 73 ); 74 }); 75 } 76 77 async function runTest(waitForErrorPage) { 78 let page = await XPCShellContentUtils.loadContentPage("about:blank"); 79 80 // Watch for the HTTP request for the top frame so that we can cancel 81 // it with an error. 82 let requestPromise = TestUtils.topicObserved( 83 "http-on-modify-request", 84 subject => subject.QueryInterface(Ci.nsIRequest).name == topFrameRequest.url 85 ); 86 87 await page.spawn( 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); 96 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 99 // document request. 100 let frameDoc = frame.contentDocument; 101 let subframe = frameDoc.createElement("iframe"); 102 subframe.src = subFrameRequestUrl; 103 frameDoc.body.appendChild(subframe); 104 } 105 ); 106 107 let [req] = await requestPromise; 108 109 info("Cancel request for parent frame"); 110 req.cancel(Cr.NS_ERROR_PROXY_CONNECTION_REFUSED); 111 112 // Request cancelation is not synchronous, so wait for the STATE_STOP 113 // event to fire. 114 await promiseStateStop(page.browsingContext.webProgress); 115 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); 119 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( 125 "about:neterror?" 126 ) 127 ); 128 } 129 130 await page.close(); 131 } 132 133 add_task(async function testRemoveFrameImmediately() { 134 await runTest(false); 135 }); 136 137 add_task(async function testRemoveFrameAfterErrorPage() { 138 await runTest(true); 139 }); 140 141 add_task(async function () { 142 // Allow the document requests for the frames to complete. 143 topFrameRequest.finish(); 144 subFrameRequest.finish(); 145 });