browser_fullscreen_api_fission.js (7373B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 /* This test checks that `document.fullscreenElement` is set correctly and 5 * proper fullscreenchange events fire when an element inside of a 6 * multi-origin tree of iframes calls `requestFullscreen()`. It is designed 7 * to make sure the fullscreen API is working properly in fission when the 8 * frame tree spans multiple processes. 9 * 10 * A similarly purposed Web Platform Test exists, but at the time of writing 11 * is manual, so it cannot be run in CI: 12 * `element-request-fullscreen-cross-origin-manual.sub.html` 13 */ 14 15 "use strict"; 16 17 const actorModuleURI = getRootDirectory(gTestPath) + "FullscreenFrame.sys.mjs"; 18 const actorName = "FullscreenFrame"; 19 20 const fullscreenPath = 21 getRootDirectory(gTestPath).replace("chrome://mochitests/content", "") + 22 "fullscreen.html"; 23 24 const fullscreenTarget = "D"; 25 // TOP 26 // | \ 27 // A B 28 // | 29 // C 30 // | 31 // D 32 // | 33 // E 34 const frameTree = { 35 name: "TOP", 36 // eslint-disable-next-line @microsoft/sdl/no-insecure-url 37 url: `http://example.com${fullscreenPath}`, 38 allow_fullscreen: true, 39 children: [ 40 { 41 name: "A", 42 // eslint-disable-next-line @microsoft/sdl/no-insecure-url 43 url: `http://example.org${fullscreenPath}`, 44 allow_fullscreen: true, 45 children: [ 46 { 47 name: "C", 48 // eslint-disable-next-line @microsoft/sdl/no-insecure-url 49 url: `http://example.com${fullscreenPath}`, 50 allow_fullscreen: true, 51 children: [ 52 { 53 name: "D", 54 // eslint-disable-next-line @microsoft/sdl/no-insecure-url 55 url: `http://example.com${fullscreenPath}?different-uri=1`, 56 allow_fullscreen: true, 57 children: [ 58 { 59 name: "E", 60 // eslint-disable-next-line @microsoft/sdl/no-insecure-url 61 url: `http://example.org${fullscreenPath}`, 62 allow_fullscreen: true, 63 children: [], 64 }, 65 ], 66 }, 67 ], 68 }, 69 ], 70 }, 71 { 72 name: "B", 73 // eslint-disable-next-line @microsoft/sdl/no-insecure-url 74 url: `http://example.net${fullscreenPath}`, 75 allow_fullscreen: true, 76 children: [], 77 }, 78 ], 79 }; 80 81 add_task(async function test_fullscreen_api_cross_origin_tree() { 82 await new Promise(r => { 83 SpecialPowers.pushPrefEnv( 84 { 85 set: [ 86 ["full-screen-api.enabled", true], 87 ["full-screen-api.allow-trusted-requests-only", false], 88 ["full-screen-api.transition-duration.enter", "0 0"], 89 ["full-screen-api.transition-duration.leave", "0 0"], 90 ["dom.security.featurePolicy.header.enabled", true], 91 ["dom.security.featurePolicy.webidl.enabled", true], 92 ], 93 }, 94 r 95 ); 96 }); 97 98 // Register a custom window actor to handle tracking events 99 // and constructing subframes 100 ChromeUtils.registerWindowActor(actorName, { 101 child: { 102 esModuleURI: actorModuleURI, 103 events: { 104 fullscreenchange: { mozSystemGroup: true, capture: true }, 105 fullscreenerror: { mozSystemGroup: true, capture: true }, 106 }, 107 }, 108 allFrames: true, 109 }); 110 111 let tab = await BrowserTestUtils.openNewForegroundTab({ 112 gBrowser, 113 url: frameTree.url, 114 }); 115 116 let frames = new Map(); 117 async function construct_frame_children(browsingContext, tree) { 118 let actor = browsingContext.currentWindowGlobal.getActor(actorName); 119 frames.set(tree.name, { 120 browsingContext, 121 actor, 122 }); 123 124 for (let child of tree.children) { 125 // Create the child IFrame and wait for it to load. 126 let childBC = await actor.sendQuery("CreateChild", child); 127 await construct_frame_children(childBC, child); 128 } 129 } 130 131 await construct_frame_children(tab.linkedBrowser.browsingContext, frameTree); 132 133 async function check_events(expected_events) { 134 for (let [name, expected] of expected_events) { 135 let actor = frames.get(name).actor; 136 137 // Each content process fires the fullscreenchange 138 // event independently and in parallel making it 139 // possible for the promises returned by 140 // `requestFullscreen` or `exitFullscreen` to 141 // resolve before all events have fired. We wait 142 // for the number of events to match before 143 // continuing to ensure we don't miss an expected 144 // event that hasn't fired yet. 145 let events; 146 await TestUtils.waitForCondition(async () => { 147 events = await actor.sendQuery("GetEvents"); 148 return events.length == expected.length; 149 }, `Waiting for number of events to match`); 150 151 Assert.equal(events.length, expected.length, "Number of events equal"); 152 events.forEach((value, i) => { 153 Assert.equal(value, expected[i], "Event type matches"); 154 }); 155 } 156 } 157 158 async function check_fullscreenElement(expected_elements) { 159 for (let [name, expected] of expected_elements) { 160 let element = await frames 161 .get(name) 162 .actor.sendQuery("GetFullscreenElement"); 163 Assert.equal(element, expected, "The fullScreenElement matches"); 164 } 165 } 166 167 // Trigger fullscreen from the target frame. 168 let target = frames.get(fullscreenTarget); 169 await target.actor.sendQuery("RequestFullscreen"); 170 // true is fullscreenchange and false is fullscreenerror. 171 await check_events( 172 new Map([ 173 ["TOP", [true]], 174 ["A", [true]], 175 ["B", []], 176 ["C", [true]], 177 ["D", [true]], 178 ["E", []], 179 ]) 180 ); 181 await check_fullscreenElement( 182 new Map([ 183 ["TOP", "child_iframe"], 184 ["A", "child_iframe"], 185 ["B", "null"], 186 ["C", "child_iframe"], 187 ["D", "body"], 188 ["E", "null"], 189 ]) 190 ); 191 192 await target.actor.sendQuery("ExitFullscreen"); 193 // fullscreenchange should have fired on exit as well. 194 // true is fullscreenchange and false is fullscreenerror. 195 await check_events( 196 new Map([ 197 ["TOP", [true, true]], 198 ["A", [true, true]], 199 ["B", []], 200 ["C", [true, true]], 201 ["D", [true, true]], 202 ["E", []], 203 ]) 204 ); 205 await check_fullscreenElement( 206 new Map([ 207 ["TOP", "null"], 208 ["A", "null"], 209 ["B", "null"], 210 ["C", "null"], 211 ["D", "null"], 212 ["E", "null"], 213 ]) 214 ); 215 216 // Clear previous events before testing exiting fullscreen with ESC. 217 for (const frame of frames.values()) { 218 frame.actor.sendQuery("ClearEvents"); 219 } 220 await target.actor.sendQuery("RequestFullscreen"); 221 222 // Escape should cause the proper events to fire and 223 // document.fullscreenElement should be cleared. 224 let finished_exiting = target.actor.sendQuery("WaitForChange"); 225 EventUtils.sendKey("ESCAPE"); 226 await finished_exiting; 227 // true is fullscreenchange and false is fullscreenerror. 228 await check_events( 229 new Map([ 230 ["TOP", [true, true]], 231 ["A", [true, true]], 232 ["B", []], 233 ["C", [true, true]], 234 ["D", [true, true]], 235 ["E", []], 236 ]) 237 ); 238 await check_fullscreenElement( 239 new Map([ 240 ["TOP", "null"], 241 ["A", "null"], 242 ["B", "null"], 243 ["C", "null"], 244 ["D", "null"], 245 ["E", "null"], 246 ]) 247 ); 248 249 // Remove the tests custom window actor. 250 ChromeUtils.unregisterWindowActor("FullscreenFrame"); 251 BrowserTestUtils.removeTab(tab); 252 });