test_SpecialPowersForProcessSpawn.js (5666B)
1 "use strict"; 2 3 // Tests SpecialPowersForProcess spawn() in xpcshell tests. 4 // A browser chrome mochitest version of this test exists at 5 // testing/mochitest/tests/browser/browser_SpecialPowersForProcessSpawn.js 6 7 const { XPCShellContentUtils } = ChromeUtils.importESModule( 8 "resource://testing-common/XPCShellContentUtils.sys.mjs" 9 ); 10 11 const { SpecialPowersForProcess } = ChromeUtils.importESModule( 12 "resource://testing-common/SpecialPowersProcessActor.sys.mjs" 13 ); 14 15 XPCShellContentUtils.init(this); 16 17 const server = XPCShellContentUtils.createHttpServer({ 18 hosts: ["example.com", "example.org"], 19 }); 20 server.registerFile( 21 "/file_xorigin_frames.html", 22 do_get_file("file_xorigin_frames.html") 23 ); 24 25 const scope = this; 26 const interceptedMessages = []; 27 add_setup(() => { 28 const orig_do_report_result = scope.do_report_result; 29 scope.do_report_result = (passed, msg, stack) => { 30 if (msg?.startsWith?.("CHECK_THIS:")) { 31 interceptedMessages.push(msg); 32 } 33 return orig_do_report_result(passed, msg, stack); 34 }; 35 const orig_info = scope.info; 36 scope.info = msg => { 37 if (msg?.startsWith?.("CHECK_THIS:")) { 38 interceptedMessages.push(msg); 39 } 40 return orig_info(msg); 41 }; 42 43 registerCleanupFunction(() => { 44 scope.do_report_result = orig_do_report_result; 45 scope.info = orig_info; 46 }); 47 }); 48 49 // Tests that SpecialPowersForProcess can spawn() in processes that the test 50 // grabbed off a contentPage, even after the original page navigated/closed. 51 add_task(async function test_SpecialPowersForProcess_spawn() { 52 interceptedMessages.length = 0; 53 54 const page = await XPCShellContentUtils.loadContentPage( 55 // eslint-disable-next-line @microsoft/sdl/no-insecure-url 56 "http://example.com/file_xorigin_frames.html", 57 { remote: true, remoteSubframes: true } 58 ); 59 await page.spawn([], async () => { 60 Assert.equal( 61 await this.content.wrappedJSObject.loadedPromise, 62 "frames_all_loaded", 63 "All (cross-origin) frames have finished loading" 64 ); 65 }); 66 const proc1 = page.browsingContext.children[0].currentWindowGlobal.domProcess; 67 const proc2 = page.browsingContext.children[1].currentWindowGlobal.domProcess; 68 Assert.equal(proc1, proc2, "The two child frames share the same process"); 69 70 const processBoundSpecialPowers = new SpecialPowersForProcess(scope, proc1); 71 72 await page.spawn([], async () => { 73 info("ContentPage.spawn: Change frame1 process"); 74 const frame1 = this.content.document.getElementById("frame1"); 75 Assert.throws( 76 () => frame1.contentDocument.location.search, 77 /TypeError: (can't access property "location", )?frame1.contentDocument is null/, 78 "ContentPage.spawn: Assert, cannot read cross-origin content" 79 ); 80 await new Promise(resolve => { 81 frame1.onload = resolve; 82 frame1.src = "/dummy?3"; 83 }); 84 // Verify that it is same-origin now. 85 Assert.equal( 86 frame1.contentDocument.location.search, 87 "?3", 88 "CHECK_THIS: ContentPage.spawn: Assert, frame1 is now same-origin" 89 ); 90 info("CHECK_THIS: ContentPage.spawn: remove frame1"); 91 frame1.remove(); 92 93 // spawn() implementation has special logic to route Assert messages; 94 // Prepare to check that Assert can be called after spawn() returns. 95 this.content.assertAfterSpawnReturns = () => { 96 Assert.ok(true, "CHECK_THIS: ContentPage.spawn: asssert after return"); 97 }; 98 }); 99 100 await page.spawn([], () => { 101 this.content.assertAfterSpawnReturns(); 102 }); 103 104 Assert.equal(page.browsingContext.children.length, 1, "frame1 was removed"); 105 106 // Now frame1 has navigated (and switched processes) and removed, so if the 107 // SpecialPowers implementation were to rely on JSWindowActor, then that 108 // would break if we try to interact with it at this point. Check that we 109 // can connect just fine (because JSProcessActor should be used instead). 110 await processBoundSpecialPowers.spawn([], () => { 111 info("CHECK_THIS: process-bound spawn: still works"); 112 Assert.equal( 113 typeof content, 114 "undefined", 115 "CHECK_THIS: process-bound spawn: no content global" 116 ); 117 // Need a shared object that outlives this SpecialPowersSandbox instance: 118 const sharedGlobalObj = Cu.getGlobalForObject(Services); 119 // spawn() implementation has special logic to route Assert messages; 120 // Prepare to check that Assert can be called after spawn() returns. 121 sharedGlobalObj.assertAfterProcessBoundSpawnReturns = () => { 122 Assert.ok(true, "CHECK_THIS: process-bound spawn: asssert after return"); 123 }; 124 }); 125 await processBoundSpecialPowers.spawn([], () => { 126 // Shared object that outlived the previous SpecialPowersSandbox instance: 127 const sharedGlobalObj = Cu.getGlobalForObject(Services); 128 sharedGlobalObj.assertAfterProcessBoundSpawnReturns(); 129 delete sharedGlobalObj.assertAfterProcessBoundSpawnReturns; 130 }); 131 await page.close(); 132 await processBoundSpecialPowers.destroy(); 133 134 Assert.throws( 135 () => processBoundSpecialPowers.spawn([], () => {}), 136 /this.actor is null/, 137 "Cannot spawn after destroy()" 138 ); 139 140 const observedMessages = interceptedMessages.splice(0); 141 Assert.deepEqual( 142 observedMessages, 143 [ 144 `CHECK_THIS: ContentPage.spawn: Assert, frame1 is now same-origin - "?3" == "?3"`, 145 "CHECK_THIS: ContentPage.spawn: remove frame1", 146 "CHECK_THIS: ContentPage.spawn: asssert after return - true == true", 147 "CHECK_THIS: process-bound spawn: still works", 148 `CHECK_THIS: process-bound spawn: no content global - "undefined" == "undefined"`, 149 "CHECK_THIS: process-bound spawn: asssert after return - true == true", 150 ], 151 "Observed calls through spawn" 152 ); 153 });