Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
function genericChecker() {
let kind = window.location.search.slice(1) || "background";
window.kind = kind;
let bcGroupId = SpecialPowers.wrap(window).browsingContext.group.id;
browser.test.onMessage.addListener((msg, ...args) => {
if (msg == kind + "-check-views") {
let counts = {
background: 0,
tab: 0,
popup: 0,
kind: 0,
sidebar: 0,
};
if (kind !== "background") {
counts.kind = browser.extension.getViews({ type: kind }).length;
}
let views = browser.extension.getViews();
let background;
for (let i = 0; i < views.length; i++) {
let view = views[i];
browser.test.assertTrue(view.kind in counts, "view type is valid");
counts[view.kind]++;
if (view.kind == "background") {
browser.test.assertTrue(
view === browser.extension.getBackgroundPage(),
"background page is correct"
);
background = view;
}
browser.test.assertEq(
bcGroupId,
SpecialPowers.wrap(view).browsingContext.group.id,
"browsing context group is correct"
);
}
if (background) {
browser.runtime.getBackgroundPage().then(view => {
browser.test.assertEq(
background,
view,
"runtime.getBackgroundPage() is correct"
);
browser.test.sendMessage("counts", counts);
});
} else {
browser.test.sendMessage("counts", counts);
}
} else if (msg == kind + "-getViews-with-filter") {
let filter = args[0];
let count = browser.extension.getViews(filter).length;
browser.test.sendMessage("getViews-count", count);
} else if (msg == kind + "-open-tab") {
let url = browser.runtime.getURL("page.html?tab");
browser.tabs
.create({ windowId: args[0], url })
.then(tab => browser.test.sendMessage("opened-tab", tab.id));
} else if (msg == kind + "-close-tab") {
browser.tabs.query(
{
windowId: args[0],
},
tabs => {
let tab = tabs.find(tab => tab.url.includes("page.html?tab"));
browser.tabs.remove(tab.id, () => {
browser.test.sendMessage("closed");
});
}
);
}
});
browser.test.sendMessage(kind + "-ready");
}
async function promiseBrowserContentUnloaded(browser) {
// Wait until the content has unloaded before resuming the test, to avoid
// calling extension.getViews too early (and having intermittent failures).
const MSG_WINDOW_DESTROYED = "Test:BrowserContentDestroyed";
let unloadPromise = new Promise(resolve => {
Services.ppmm.addMessageListener(MSG_WINDOW_DESTROYED, function listener() {
Services.ppmm.removeMessageListener(MSG_WINDOW_DESTROYED, listener);
resolve();
});
});
await ContentTask.spawn(
browser,
MSG_WINDOW_DESTROYED,
MSG_WINDOW_DESTROYED => {
let innerWindowId = this.content.windowGlobalChild.innerWindowId;
let observer = subject => {
if (
innerWindowId === subject.QueryInterface(Ci.nsISupportsPRUint64).data
) {
Services.obs.removeObserver(observer, "inner-window-destroyed");
// Use process message manager to ensure that the message is delivered
// even after the <browser>'s message manager is disconnected.
Services.cpmm.sendAsyncMessage(MSG_WINDOW_DESTROYED);
}
};
// Observe inner-window-destroyed, like ExtensionPageChild, to ensure that
// the ExtensionPageContextChild instance has been unloaded when we resolve
// the unloadPromise.
Services.obs.addObserver(observer, "inner-window-destroyed");
}
);
// Return an object so that callers can use "await".
return { unloadPromise };
}
add_task(async function () {
let win1 = await BrowserTestUtils.openNewBrowserWindow();
let win2 = await BrowserTestUtils.openNewBrowserWindow();
let extension = ExtensionTestUtils.loadExtension({
useAddonManager: "temporary", // To automatically show sidebar on load.
manifest: {
permissions: ["tabs"],
browser_action: {
default_popup: "page.html?popup",
default_area: "navbar",
},
sidebar_action: {
default_panel: "page.html?sidebar",
},
},
files: {
"page.html": `
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"></head>
<body>
<script src="page.js"></script>
</body></html>
`,
"page.js": genericChecker,
},
background: genericChecker,
});
await Promise.all([
extension.startup(),
extension.awaitMessage("background-ready"),
]);
await extension.awaitMessage("sidebar-ready");
await extension.awaitMessage("sidebar-ready");
await extension.awaitMessage("sidebar-ready");
info("started");
const {
Management: {
global: { windowTracker },
},
} = ChromeUtils.importESModule("resource://gre/modules/Extension.sys.mjs");
let winId1 = windowTracker.getId(win1);
let winId2 = windowTracker.getId(win2);
async function openTab(winId) {
extension.sendMessage("background-open-tab", winId);
await extension.awaitMessage("tab-ready");
return extension.awaitMessage("opened-tab");
}
async function checkViews(kind, tabCount, popupCount, kindCount) {
extension.sendMessage(kind + "-check-views");
let counts = await extension.awaitMessage("counts");
if (kind === "sidebar") {
// We have 3 sidebars thaat will answer.
await extension.awaitMessage("counts");
await extension.awaitMessage("counts");
}
is(counts.background, 1, "background count correct");
is(counts.tab, tabCount, "tab count correct");
is(counts.popup, popupCount, "popup count correct");
is(counts.kind, kindCount, "count for type correct");
is(counts.sidebar, 3, "sidebar count is constant");
}
async function checkViewsWithFilter(filter, expectedCount) {
extension.sendMessage("background-getViews-with-filter", filter);
let count = await extension.awaitMessage("getViews-count");
is(count, expectedCount, `count for ${JSON.stringify(filter)} correct`);
}
await checkViews("background", 0, 0, 0);
await checkViews("sidebar", 0, 0, 3);
await checkViewsWithFilter({ windowId: -1 }, 1);
await checkViewsWithFilter({ windowId: 0 }, 0);
await checkViewsWithFilter({ tabId: -1 }, 4);
await checkViewsWithFilter({ tabId: 0 }, 0);
let tabId1 = await openTab(winId1);
await checkViews("background", 1, 0, 0);
await checkViews("sidebar", 1, 0, 3);
await checkViews("tab", 1, 0, 1);
await checkViewsWithFilter({ windowId: winId1 }, 2);
await checkViewsWithFilter({ tabId: tabId1 }, 1);
let tabId2 = await openTab(winId2);
await checkViews("background", 2, 0, 0);
await checkViews("sidebar", 2, 0, 3);
await checkViewsWithFilter({ windowId: winId2 }, 2);
await checkViewsWithFilter({ tabId: tabId2 }, 1);
async function triggerPopup(win, callback) {
// Window needs focus to open popups.
await focusWindow(win);
await clickBrowserAction(extension, win);
let browser = await awaitExtensionPanel(extension, win);
await extension.awaitMessage("popup-ready");
await callback();
let { unloadPromise } = await promiseBrowserContentUnloaded(browser);
closeBrowserAction(extension, win);
await unloadPromise;
}
await triggerPopup(win1, async function () {
await checkViews("background", 2, 1, 0);
await checkViews("sidebar", 2, 1, 3);
await checkViews("popup", 2, 1, 1);
await checkViewsWithFilter({ windowId: winId1 }, 3);
await checkViewsWithFilter({ type: "popup", tabId: -1 }, 1);
});
await triggerPopup(win2, async function () {
await checkViews("background", 2, 1, 0);
await checkViews("sidebar", 2, 1, 3);
await checkViews("popup", 2, 1, 1);
await checkViewsWithFilter({ windowId: winId2 }, 3);
await checkViewsWithFilter({ type: "popup", tabId: -1 }, 1);
});
info("checking counts after popups");
await checkViews("background", 2, 0, 0);
await checkViews("sidebar", 2, 0, 3);
await checkViewsWithFilter({ windowId: winId1 }, 2);
await checkViewsWithFilter({ tabId: -1 }, 4);
info("closing one tab");
let { unloadPromise } = await promiseBrowserContentUnloaded(
win1.gBrowser.selectedBrowser
);
extension.sendMessage("background-close-tab", winId1);
await extension.awaitMessage("closed");
await unloadPromise;
info("one tab closed, one remains");
await checkViews("background", 1, 0, 0);
await checkViews("sidebar", 1, 0, 3);
info("opening win1 popup");
await triggerPopup(win1, async function () {
await checkViews("background", 1, 1, 0);
await checkViews("sidebar", 1, 1, 3);
await checkViews("tab", 1, 1, 1);
await checkViews("popup", 1, 1, 1);
});
info("opening win2 popup");
await triggerPopup(win2, async function () {
await checkViews("background", 1, 1, 0);
await checkViews("sidebar", 1, 1, 3);
await checkViews("tab", 1, 1, 1);
await checkViews("popup", 1, 1, 1);
});
await checkViews("sidebar", 1, 0, 3);
await extension.unload();
await BrowserTestUtils.closeWindow(win1);
await BrowserTestUtils.closeWindow(win2);
});
add_task(async function test_getViews_excludes_blocked_parsing_documents() {
const extension = ExtensionTestUtils.loadExtension({
manifest: {
browser_action: {
default_popup: "popup.html",
default_area: "navbar",
},
},
files: {
"popup.html": `<!DOCTYPE html>
<script src="popup.js">
</script>
<h1>ExtensionPopup</h1>
`,
"popup.js": function () {
browser.test.sendMessage(
"browserActionPopup:loaded",
window.location.href
);
},
},
background() {
browser.test.onMessage.addListener(msg => {
browser.test.assertEq("getViews", msg, "Got the expected test message");
const views = browser.extension
.getViews()
.map(win => win.location?.href);
browser.test.sendMessage("getViews:done", views);
});
browser.test.sendMessage("bgpage:loaded", window.location.href);
},
});
await extension.startup();
const bgpageURL = await extension.awaitMessage("bgpage:loaded");
extension.sendMessage("getViews");
Assert.deepEqual(
await extension.awaitMessage("getViews:done"),
[bgpageURL],
"Expect only the background page to be initially listed in getViews"
);
const {
Management: {
global: { browserActionFor },
},
} = ChromeUtils.importESModule("resource://gre/modules/Extension.sys.mjs");
let ext = WebExtensionPolicy.getByID(extension.id)?.extension;
let browserAction = browserActionFor(ext);
// Ensure the mouse is not initially hovering the browserAction widget.
EventUtils.synthesizeMouseAtCenter(
window.gURLBar.textbox,
{ type: "mouseover" },
window
);
let widget = await TestUtils.waitForCondition(
() => getBrowserActionWidget(extension).forWindow(window),
"Wait for browserAction widget"
);
await TestUtils.waitForCondition(
() => !browserAction.pendingPopup,
"Wait for no pending preloaded popup"
);
await TestUtils.waitForCondition(async () => {
// Trigger preload browserAction popup (by directly dispatching a MouseEvent
// to prevent intermittent failures that where often triggered in macos
// PGO builds when this was using EventUtils.synthesizeMouseAtCenter).
let mouseOverEvent = new MouseEvent("mouseover");
widget.node.firstElementChild.dispatchEvent(mouseOverEvent);
await TestUtils.waitForCondition(
() => browserAction.pendingPopup?.browser,
"Wait for pending preloaded popup browser"
);
return SpecialPowers.spawn(
browserAction.pendingPopup.browser,
[],
async () => {
const policy = this.content.WebExtensionPolicy.getByHostname(
this.content.location.hostname
);
return policy?.weakExtension
?.get()
?.blockedParsingDocuments.has(this.content.document);
}
).catch(err => {
// Tolerate errors triggered by SpecialPowers.spawn
// being aborted before we got a result back.
if (err.name === "AbortError") {
return false;
}
throw err;
});
}, "Wait for preload browserAction document to be blocked on parsing");
extension.sendMessage("getViews");
Assert.deepEqual(
await extension.awaitMessage("getViews:done"),
[bgpageURL],
"Expect preloaded browserAction popup to not be listed in getViews"
);
// Test browserAction popup is listed in getViews once document parser is unblocked.
EventUtils.synthesizeMouseAtCenter(
widget.node,
{ type: "mousedown", button: 0 },
window
);
EventUtils.synthesizeMouseAtCenter(
widget.node,
{ type: "mouseup", button: 0 },
window
);
const popupURL = await extension.awaitMessage("browserActionPopup:loaded");
extension.sendMessage("getViews");
Assert.deepEqual(
(await extension.awaitMessage("getViews:done")).sort(),
[bgpageURL, popupURL].sort(),
"Expect loaded browserAction popup to be listed in getViews"
);
// Ensure the mouse is not hovering the browserAction widget anymore when exiting the test case.
EventUtils.synthesizeMouseAtCenter(
window.gURLBar.textbox,
{ type: "mouseover", button: 0 },
window
);
await extension.unload();
});