browser_ext_getViews.js (12272B)
1 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ 2 /* vim: set sts=2 sw=2 et tw=80: */ 3 "use strict"; 4 5 function genericChecker() { 6 let kind = window.location.search.slice(1) || "background"; 7 window.kind = kind; 8 9 let bcGroupId = SpecialPowers.wrap(window).browsingContext.group.id; 10 11 browser.test.onMessage.addListener((msg, ...args) => { 12 if (msg == kind + "-check-views") { 13 let counts = { 14 background: 0, 15 tab: 0, 16 popup: 0, 17 kind: 0, 18 sidebar: 0, 19 }; 20 if (kind !== "background") { 21 counts.kind = browser.extension.getViews({ type: kind }).length; 22 } 23 let views = browser.extension.getViews(); 24 let background; 25 for (let i = 0; i < views.length; i++) { 26 let view = views[i]; 27 browser.test.assertTrue(view.kind in counts, "view type is valid"); 28 counts[view.kind]++; 29 if (view.kind == "background") { 30 browser.test.assertTrue( 31 view === browser.extension.getBackgroundPage(), 32 "background page is correct" 33 ); 34 background = view; 35 } 36 37 browser.test.assertEq( 38 bcGroupId, 39 SpecialPowers.wrap(view).browsingContext.group.id, 40 "browsing context group is correct" 41 ); 42 } 43 if (background) { 44 browser.runtime.getBackgroundPage().then(view => { 45 browser.test.assertEq( 46 background, 47 view, 48 "runtime.getBackgroundPage() is correct" 49 ); 50 browser.test.sendMessage("counts", counts); 51 }); 52 } else { 53 browser.test.sendMessage("counts", counts); 54 } 55 } else if (msg == kind + "-getViews-with-filter") { 56 let filter = args[0]; 57 let count = browser.extension.getViews(filter).length; 58 browser.test.sendMessage("getViews-count", count); 59 } else if (msg == kind + "-open-tab") { 60 let url = browser.runtime.getURL("page.html?tab"); 61 browser.tabs 62 .create({ windowId: args[0], url }) 63 .then(tab => browser.test.sendMessage("opened-tab", tab.id)); 64 } else if (msg == kind + "-close-tab") { 65 browser.tabs.query( 66 { 67 windowId: args[0], 68 }, 69 tabs => { 70 let tab = tabs.find(tab => tab.url.includes("page.html?tab")); 71 browser.tabs.remove(tab.id, () => { 72 browser.test.sendMessage("closed"); 73 }); 74 } 75 ); 76 } 77 }); 78 browser.test.sendMessage(kind + "-ready"); 79 } 80 81 add_task(async function () { 82 let win1 = await BrowserTestUtils.openNewBrowserWindow(); 83 let win2 = await BrowserTestUtils.openNewBrowserWindow(); 84 85 let extension = ExtensionTestUtils.loadExtension({ 86 useAddonManager: "temporary", // To automatically show sidebar on load. 87 manifest: { 88 permissions: ["tabs"], 89 90 browser_action: { 91 default_popup: "page.html?popup", 92 default_area: "navbar", 93 }, 94 95 sidebar_action: { 96 default_panel: "page.html?sidebar", 97 }, 98 }, 99 100 files: { 101 "page.html": ` 102 <!DOCTYPE html> 103 <html> 104 <head><meta charset="utf-8"></head> 105 <body> 106 <script src="page.js"></script> 107 </body></html> 108 `, 109 110 "page.js": genericChecker, 111 }, 112 113 background: genericChecker, 114 }); 115 116 await Promise.all([ 117 extension.startup(), 118 extension.awaitMessage("background-ready"), 119 ]); 120 121 await extension.awaitMessage("sidebar-ready"); 122 await extension.awaitMessage("sidebar-ready"); 123 await extension.awaitMessage("sidebar-ready"); 124 125 info("started"); 126 127 const { 128 Management: { 129 global: { windowTracker }, 130 }, 131 } = ChromeUtils.importESModule("resource://gre/modules/Extension.sys.mjs"); 132 133 let winId1 = windowTracker.getId(win1); 134 let winId2 = windowTracker.getId(win2); 135 136 async function openTab(winId) { 137 extension.sendMessage("background-open-tab", winId); 138 await extension.awaitMessage("tab-ready"); 139 return extension.awaitMessage("opened-tab"); 140 } 141 142 async function checkViews(kind, tabCount, popupCount, kindCount) { 143 extension.sendMessage(kind + "-check-views"); 144 let counts = await extension.awaitMessage("counts"); 145 if (kind === "sidebar") { 146 // We have 3 sidebars thaat will answer. 147 await extension.awaitMessage("counts"); 148 await extension.awaitMessage("counts"); 149 } 150 is(counts.background, 1, "background count correct"); 151 is(counts.tab, tabCount, "tab count correct"); 152 is(counts.popup, popupCount, "popup count correct"); 153 is(counts.kind, kindCount, "count for type correct"); 154 is(counts.sidebar, 3, "sidebar count is constant"); 155 } 156 157 async function checkViewsWithFilter(filter, expectedCount) { 158 extension.sendMessage("background-getViews-with-filter", filter); 159 let count = await extension.awaitMessage("getViews-count"); 160 is(count, expectedCount, `count for ${JSON.stringify(filter)} correct`); 161 } 162 163 await checkViews("background", 0, 0, 0); 164 await checkViews("sidebar", 0, 0, 3); 165 await checkViewsWithFilter({ windowId: -1 }, 1); 166 await checkViewsWithFilter({ windowId: 0 }, 0); 167 await checkViewsWithFilter({ tabId: -1 }, 4); 168 await checkViewsWithFilter({ tabId: 0 }, 0); 169 170 let tabId1 = await openTab(winId1); 171 172 await checkViews("background", 1, 0, 0); 173 await checkViews("sidebar", 1, 0, 3); 174 await checkViews("tab", 1, 0, 1); 175 await checkViewsWithFilter({ windowId: winId1 }, 2); 176 await checkViewsWithFilter({ tabId: tabId1 }, 1); 177 178 let tabId2 = await openTab(winId2); 179 180 await checkViews("background", 2, 0, 0); 181 await checkViews("sidebar", 2, 0, 3); 182 await checkViewsWithFilter({ windowId: winId2 }, 2); 183 await checkViewsWithFilter({ tabId: tabId2 }, 1); 184 185 async function triggerPopup(win, callback) { 186 // Window needs focus to open popups. 187 await focusWindow(win); 188 await clickBrowserAction(extension, win); 189 let browser = await awaitExtensionPanel(extension, win); 190 191 await extension.awaitMessage("popup-ready"); 192 193 await callback(); 194 195 let { unloadPromise } = await promiseBrowserContentUnloaded(browser); 196 closeBrowserAction(extension, win); 197 await unloadPromise; 198 } 199 200 await triggerPopup(win1, async function () { 201 await checkViews("background", 2, 1, 0); 202 await checkViews("sidebar", 2, 1, 3); 203 await checkViews("popup", 2, 1, 1); 204 await checkViewsWithFilter({ windowId: winId1 }, 3); 205 await checkViewsWithFilter({ type: "popup", tabId: -1 }, 1); 206 }); 207 208 await triggerPopup(win2, async function () { 209 await checkViews("background", 2, 1, 0); 210 await checkViews("sidebar", 2, 1, 3); 211 await checkViews("popup", 2, 1, 1); 212 await checkViewsWithFilter({ windowId: winId2 }, 3); 213 await checkViewsWithFilter({ type: "popup", tabId: -1 }, 1); 214 }); 215 216 info("checking counts after popups"); 217 218 await checkViews("background", 2, 0, 0); 219 await checkViews("sidebar", 2, 0, 3); 220 await checkViewsWithFilter({ windowId: winId1 }, 2); 221 await checkViewsWithFilter({ tabId: -1 }, 4); 222 223 info("closing one tab"); 224 225 let { unloadPromise } = await promiseBrowserContentUnloaded( 226 win1.gBrowser.selectedBrowser 227 ); 228 extension.sendMessage("background-close-tab", winId1); 229 await extension.awaitMessage("closed"); 230 await unloadPromise; 231 232 info("one tab closed, one remains"); 233 234 await checkViews("background", 1, 0, 0); 235 await checkViews("sidebar", 1, 0, 3); 236 237 info("opening win1 popup"); 238 239 await triggerPopup(win1, async function () { 240 await checkViews("background", 1, 1, 0); 241 await checkViews("sidebar", 1, 1, 3); 242 await checkViews("tab", 1, 1, 1); 243 await checkViews("popup", 1, 1, 1); 244 }); 245 246 info("opening win2 popup"); 247 248 await triggerPopup(win2, async function () { 249 await checkViews("background", 1, 1, 0); 250 await checkViews("sidebar", 1, 1, 3); 251 await checkViews("tab", 1, 1, 1); 252 await checkViews("popup", 1, 1, 1); 253 }); 254 255 await checkViews("sidebar", 1, 0, 3); 256 257 await extension.unload(); 258 259 await BrowserTestUtils.closeWindow(win1); 260 await BrowserTestUtils.closeWindow(win2); 261 }); 262 263 add_task(async function test_getViews_excludes_blocked_parsing_documents() { 264 const extension = ExtensionTestUtils.loadExtension({ 265 manifest: { 266 browser_action: { 267 default_popup: "popup.html", 268 default_area: "navbar", 269 }, 270 }, 271 files: { 272 "popup.html": `<!DOCTYPE html> 273 <script src="popup.js"> 274 </script> 275 <h1>ExtensionPopup</h1> 276 `, 277 "popup.js": function () { 278 browser.test.sendMessage( 279 "browserActionPopup:loaded", 280 window.location.href 281 ); 282 }, 283 }, 284 background() { 285 browser.test.onMessage.addListener(msg => { 286 browser.test.assertEq("getViews", msg, "Got the expected test message"); 287 const views = browser.extension 288 .getViews() 289 .map(win => win.location?.href); 290 291 browser.test.sendMessage("getViews:done", views); 292 }); 293 browser.test.sendMessage("bgpage:loaded", window.location.href); 294 }, 295 }); 296 297 await extension.startup(); 298 const bgpageURL = await extension.awaitMessage("bgpage:loaded"); 299 extension.sendMessage("getViews"); 300 Assert.deepEqual( 301 await extension.awaitMessage("getViews:done"), 302 [bgpageURL], 303 "Expect only the background page to be initially listed in getViews" 304 ); 305 306 const { 307 Management: { 308 global: { browserActionFor }, 309 }, 310 } = ChromeUtils.importESModule("resource://gre/modules/Extension.sys.mjs"); 311 312 let ext = WebExtensionPolicy.getByID(extension.id)?.extension; 313 let browserAction = browserActionFor(ext); 314 315 // Ensure the mouse is not initially hovering the browserAction widget. 316 EventUtils.synthesizeMouseAtCenter( 317 window.gURLBar, 318 { type: "mouseover" }, 319 window 320 ); 321 322 let widget = await TestUtils.waitForCondition( 323 () => getBrowserActionWidget(extension).forWindow(window), 324 "Wait for browserAction widget" 325 ); 326 327 await TestUtils.waitForCondition( 328 () => !browserAction.pendingPopup, 329 "Wait for no pending preloaded popup" 330 ); 331 332 await TestUtils.waitForCondition(async () => { 333 // Trigger preload browserAction popup (by directly dispatching a MouseEvent 334 // to prevent intermittent failures that where often triggered in macos 335 // PGO builds when this was using EventUtils.synthesizeMouseAtCenter). 336 let mouseOverEvent = new MouseEvent("mouseover"); 337 widget.node 338 .querySelector(".unified-extensions-item-action-button") 339 .dispatchEvent(mouseOverEvent); 340 341 await TestUtils.waitForCondition( 342 () => browserAction.pendingPopup?.browser, 343 "Wait for pending preloaded popup browser" 344 ); 345 346 return SpecialPowers.spawn( 347 browserAction.pendingPopup.browser, 348 [], 349 async () => { 350 const policy = this.content.WebExtensionPolicy.getByHostname( 351 this.content.location.hostname 352 ); 353 return policy?.weakExtension 354 ?.get() 355 ?.blockedParsingDocuments.has(this.content.document); 356 } 357 ).catch(err => { 358 // Tolerate errors triggered by SpecialPowers.spawn 359 // being aborted before we got a result back. 360 if (err.name === "AbortError") { 361 return false; 362 } 363 throw err; 364 }); 365 }, "Wait for preload browserAction document to be blocked on parsing"); 366 367 extension.sendMessage("getViews"); 368 Assert.deepEqual( 369 await extension.awaitMessage("getViews:done"), 370 [bgpageURL], 371 "Expect preloaded browserAction popup to not be listed in getViews" 372 ); 373 374 // Test browserAction popup is listed in getViews once document parser is unblocked. 375 EventUtils.synthesizeMouseAtCenter( 376 widget.node, 377 { type: "mousedown", button: 0 }, 378 window 379 ); 380 EventUtils.synthesizeMouseAtCenter( 381 widget.node, 382 { type: "mouseup", button: 0 }, 383 window 384 ); 385 386 const popupURL = await extension.awaitMessage("browserActionPopup:loaded"); 387 388 extension.sendMessage("getViews"); 389 Assert.deepEqual( 390 (await extension.awaitMessage("getViews:done")).sort(), 391 [bgpageURL, popupURL].sort(), 392 "Expect loaded browserAction popup to be listed in getViews" 393 ); 394 395 // Ensure the mouse is not hovering the browserAction widget anymore when exiting the test case. 396 EventUtils.synthesizeMouseAtCenter( 397 window.gURLBar, 398 { type: "mouseover", button: 0 }, 399 window 400 ); 401 402 await extension.unload(); 403 });