browser_destroy_callbacks.js (6277B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 "use strict"; 4 5 declTest("destroy actor by iframe remove", { 6 allFrames: true, 7 8 async test(browser) { 9 await SpecialPowers.spawn(browser, [], async function () { 10 // Create and append an iframe into the window's document. 11 let frame = content.document.createElement("iframe"); 12 frame.id = "frame"; 13 content.document.body.appendChild(frame); 14 is(content.window.frames.length, 1, "There should be an iframe."); 15 let child = frame.contentWindow.windowGlobalChild; 16 let actorChild = child.getActor("TestWindow"); 17 ok(actorChild, "JSWindowActorChild should have value."); 18 19 { 20 let error = actorChild.uninitializedGetterError; 21 const prop = "contentWindow"; 22 Assert.ok( 23 error, 24 `Should get error accessing '${prop}' before actor initialization` 25 ); 26 if (error) { 27 Assert.equal( 28 error.name, 29 "InvalidStateError", 30 "Error should be an InvalidStateError" 31 ); 32 Assert.equal( 33 error.message, 34 `JSWindowActorChild.${prop} getter: Cannot access property '${prop}' before actor is initialized`, 35 "Error should have informative message" 36 ); 37 } 38 } 39 40 let didDestroyPromise = new Promise(resolve => { 41 const TOPIC = "test-js-window-actor-diddestroy"; 42 Services.obs.addObserver(function obs(subject, topic, data) { 43 ok(data, "didDestroyCallback data should be true."); 44 is(subject, actorChild, "Should have this value"); 45 46 Services.obs.removeObserver(obs, TOPIC); 47 // Make a trip through the event loop to ensure that the 48 // actor's manager has been cleared before running remaining 49 // checks. 50 Services.tm.dispatchToMainThread(resolve); 51 }, TOPIC); 52 }); 53 54 info("Remove frame"); 55 content.document.getElementById("frame").remove(); 56 await didDestroyPromise; 57 58 Assert.throws( 59 () => child.getActor("TestWindow"), 60 /InvalidStateError/, 61 "Should throw if frame destroy." 62 ); 63 64 for (let prop of [ 65 "document", 66 "browsingContext", 67 "docShell", 68 "contentWindow", 69 ]) { 70 let error; 71 try { 72 void actorChild[prop]; 73 } catch (e) { 74 error = e; 75 } 76 Assert.ok( 77 error, 78 `Should get error accessing '${prop}' after actor destruction` 79 ); 80 if (error) { 81 Assert.equal( 82 error.name, 83 "InvalidStateError", 84 "Error should be an InvalidStateError" 85 ); 86 Assert.equal( 87 error.message, 88 `JSWindowActorChild.${prop} getter: Cannot access property '${prop}' after actor 'TestWindow' has been destroyed`, 89 "Error should have informative message" 90 ); 91 } 92 } 93 }); 94 }, 95 }); 96 97 declTest("destroy actor by page navigates", { 98 allFrames: true, 99 100 async test(browser) { 101 info("creating an in-process frame"); 102 await SpecialPowers.spawn(browser, [URL], async function (url) { 103 let frame = content.document.createElement("iframe"); 104 frame.src = url; 105 content.document.body.appendChild(frame); 106 }); 107 108 info("navigating page"); 109 await SpecialPowers.spawn(browser, [TEST_URL], async function (url) { 110 let frame = content.document.querySelector("iframe"); 111 frame.contentWindow.location = url; 112 let child = frame.contentWindow.windowGlobalChild; 113 let actorChild = child.getActor("TestWindow"); 114 ok(actorChild, "JSWindowActorChild should have value."); 115 116 let didDestroyPromise = new Promise(resolve => { 117 const TOPIC = "test-js-window-actor-diddestroy"; 118 Services.obs.addObserver(function obs(subject, topic, data) { 119 ok(data, "didDestroyCallback data should be true."); 120 is(subject, actorChild, "Should have this value"); 121 122 Services.obs.removeObserver(obs, TOPIC); 123 resolve(); 124 }, TOPIC); 125 }); 126 127 await Promise.all([ 128 didDestroyPromise, 129 ContentTaskUtils.waitForEvent(frame, "load"), 130 ]); 131 132 Assert.throws( 133 () => child.getActor("TestWindow"), 134 /InvalidStateError/, 135 "Should throw if frame destroy." 136 ); 137 }); 138 }, 139 }); 140 141 declTest("destroy actor by tab being closed", { 142 allFrames: true, 143 144 async test() { 145 info("creating a new tab"); 146 let newTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, URL); 147 let newTabBrowser = newTab.linkedBrowser; 148 149 let parent = 150 newTabBrowser.browsingContext.currentWindowGlobal.getActor("TestWindow"); 151 ok(parent, "JSWindowActorParent should have value."); 152 153 // We can't depend on `SpecialPowers.spawn` to resolve our promise, as the 154 // frame message manager will be being shut down at the same time. Instead 155 // send messages over the per-process message manager which should still be 156 // active. 157 let didDestroyPromise = new Promise(resolve => { 158 Services.ppmm.addMessageListener( 159 "test-jswindowactor-diddestroy", 160 function onmessage() { 161 Services.ppmm.removeMessageListener( 162 "test-jswindowactor-diddestroy", 163 onmessage 164 ); 165 resolve(); 166 } 167 ); 168 }); 169 170 info("setting up destroy listeners"); 171 await SpecialPowers.spawn(newTabBrowser, [], () => { 172 let child = content.windowGlobalChild; 173 let actorChild = child.getActor("TestWindow"); 174 ok(actorChild, "JSWindowActorChild should have value."); 175 176 Services.obs.addObserver(function obs(subject, topic, data) { 177 if (subject != actorChild) { 178 return; 179 } 180 dump("DidDestroy called\n"); 181 Services.obs.removeObserver(obs, "test-js-window-actor-diddestroy"); 182 Services.cpmm.sendAsyncMessage("test-jswindowactor-diddestroy", data); 183 }, "test-js-window-actor-diddestroy"); 184 }); 185 186 info("removing new tab"); 187 await BrowserTestUtils.removeTab(newTab); 188 info("waiting for destroy callbacks to fire"); 189 await didDestroyPromise; 190 info("got didDestroy callback"); 191 }, 192 });