/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ /* vim: set sts=2 sw=2 et tw=80: */ "use strict"; async function runTests(options) { async function background(getTests) { let manifest = browser.runtime.getManifest(); let { manifest_version } = manifest; const action = manifest_version < 3 ? "browserAction" : "action"; async function checkExtAPIDetails(expecting, details) { let title = await browser[action].getTitle(details); browser.test.assertEq( expecting.title, title, "expected value from getTitle" ); let popup = await browser[action].getPopup(details); browser.test.assertEq( expecting.popup, popup, "expected value from getPopup" ); let badge = await browser[action].getBadgeText(details); browser.test.assertEq( expecting.badge, badge, "expected value from getBadge" ); let badgeBackgroundColor = await browser[action].getBadgeBackgroundColor( details ); browser.test.assertEq( String(expecting.badgeBackgroundColor), String(badgeBackgroundColor), "expected value from getBadgeBackgroundColor" ); let badgeTextColor = await browser[action].getBadgeTextColor(details); browser.test.assertEq( String(expecting.badgeTextColor), String(badgeTextColor), "expected value from getBadgeTextColor" ); let enabled = await browser[action].isEnabled(details); browser.test.assertEq( expecting.enabled, enabled, "expected value from isEnabled" ); } let tabs = []; let windows = []; let tests = getTests(tabs, windows); { let tabId = 0xdeadbeef; let calls = [ () => browser[action].enable(tabId), () => browser[action].disable(tabId), () => browser[action].setTitle({ tabId, title: "foo" }), () => browser[action].setIcon({ tabId, path: "foo.png" }), () => browser[action].setPopup({ tabId, popup: "foo.html" }), () => browser[action].setBadgeText({ tabId, text: "foo" }), () => browser[action].setBadgeBackgroundColor({ tabId, color: [0xff, 0, 0, 0xff], }), () => browser[action].setBadgeTextColor({ tabId, color: [0, 0xff, 0xff, 0xff], }), ]; for (let call of calls) { await browser.test.assertRejects( new Promise(resolve => resolve(call())), RegExp(`Invalid tab ID: ${tabId}`), "Expected invalid tab ID error" ); } } // Runs the next test in the `tests` array, checks the results, // and passes control back to the outer test scope. function nextTest() { let test = tests.shift(); test(async (expectTab, expectWindow, expectGlobal, expectDefault) => { expectGlobal = { ...expectDefault, ...expectGlobal }; expectWindow = { ...expectGlobal, ...expectWindow }; expectTab = { ...expectWindow, ...expectTab }; // Check that the API returns the expected values, and then // run the next test. let [{ windowId, id: tabId }] = await browser.tabs.query({ active: true, currentWindow: true, }); await checkExtAPIDetails(expectTab, { tabId }); await checkExtAPIDetails(expectWindow, { windowId }); await checkExtAPIDetails(expectGlobal, {}); // Check that the actual icon has the expected values, then // run the next test. browser.test.sendMessage("nextTest", expectTab, windowId, tests.length); }); } browser.test.onMessage.addListener(msg => { if (msg != "runNextTest") { browser.test.fail("Expecting 'runNextTest' message"); } nextTest(); }); let [{ id, windowId }] = await browser.tabs.query({ active: true, currentWindow: true, }); tabs.push(id); windows.push(windowId); nextTest(); } let extension = ExtensionTestUtils.loadExtension({ manifest: options.manifest, files: options.files || {}, background: `(${background})(${options.getTests})`, }); function serializeColor([r, g, b, a]) { if (a === 255) { return `rgb(${r}, ${g}, ${b})`; } return `rgba(${r}, ${g}, ${b}, ${a / 255})`; } let browserActionId; async function checkWidgetDetails(details, windowId) { let { document } = Services.wm.getOuterWindowWithId(windowId); if (!browserActionId) { browserActionId = `${makeWidgetId(extension.id)}-browser-action`; } let node = document.getElementById(browserActionId); let button = node.firstElementChild; ok(button, "button exists"); let title = details.title || options.manifest.name; // NOTE: resorting to waitForCondition to prevent frequent // intermittent failures due to multiple action API calls // being queued. if (getListStyleImage(button) !== details.icon) { info(`wait for action icon url to be set to ${details.icon}`); await TestUtils.waitForCondition( () => getListStyleImage(button) === details.icon, "Wait for the expected icon URL to be set" ); } // NOTE: resorting to waitForCondition to prevent frequent // intermittent failures due to multiple action API calls // being queued. if (button.getAttribute("tooltiptext") !== title) { info(`wait for action tooltiptext to be set to ${title}`); await TestUtils.waitForCondition( () => button.getAttribute("tooltiptext") === title, "Wait for expected title to be set" ); } is(getListStyleImage(button), details.icon, "icon URL is correct"); is(button.getAttribute("tooltiptext"), title, "image title is correct"); is(button.getAttribute("label"), title, "image label is correct"); is(button.getAttribute("badge"), details.badge, "badge text is correct"); is( button.getAttribute("disabled") == "true", !details.enabled, "disabled state is correct" ); if (details.badge) { let badge = button.badgeLabel; let style = window.getComputedStyle(badge); let expected = { backgroundColor: serializeColor(details.badgeBackgroundColor), color: serializeColor(details.badgeTextColor), }; for (let [prop, value] of Object.entries(expected)) { // NOTE: resorting to waitForCondition to prevent frequent // intermittent failures due to multiple action API calls // being queued. if (style[prop] !== value) { info(`wait for badge ${prop} to be set to ${value}`); await TestUtils.waitForCondition( () => window.getComputedStyle(badge)[prop] === value, `Wait for expected badge ${prop} to be set` ); } } } // TODO: Popup URL. } let awaitFinish = new Promise(resolve => { extension.onMessage( "nextTest", async (expecting, windowId, testsRemaining) => { await promiseAnimationFrame(); await checkWidgetDetails(expecting, windowId); if (testsRemaining) { extension.sendMessage("runNextTest"); } else { resolve(); } } ); }); await extension.startup(); await awaitFinish; await extension.unload(); } let tabSwitchTestData = { files: { "_locales/en/messages.json": { popup: { message: "default.html", description: "Popup", }, title: { message: "Title", description: "Title", }, }, "default.png": imageBuffer, "global.png": imageBuffer, "1.png": imageBuffer, "2.png": imageBuffer, }, getTests: function (tabs, windows) { let manifest = browser.runtime.getManifest(); let { manifest_version } = manifest; const action = manifest_version < 3 ? "browserAction" : "action"; let details = [ { icon: browser.runtime.getURL("default.png"), popup: browser.runtime.getURL("default.html"), title: "Default Title", badge: "", badgeBackgroundColor: [0xd9, 0, 0, 255], badgeTextColor: [0xff, 0xff, 0xff, 0xff], enabled: true, }, { icon: browser.runtime.getURL("1.png") }, { icon: browser.runtime.getURL("2.png"), popup: browser.runtime.getURL("2.html"), title: "Title 2", badge: "2", badgeBackgroundColor: [0xff, 0, 0, 0xff], badgeTextColor: [0, 0xff, 0xff, 0xff], enabled: false, }, { icon: browser.runtime.getURL("global.png"), popup: browser.runtime.getURL("global.html"), title: "Global Title", badge: "g", badgeBackgroundColor: [0, 0xff, 0, 0xff], badgeTextColor: [0xff, 0, 0xff, 0xff], enabled: false, }, { icon: browser.runtime.getURL("global.png"), popup: browser.runtime.getURL("global.html"), title: "Global Title", badge: "g", badgeBackgroundColor: [0, 0xff, 0, 0xff], badgeTextColor: [0xff, 0, 0xff, 0xff], }, ]; let promiseTabLoad = details => { return new Promise(resolve => { browser.tabs.onUpdated.addListener(function listener(tabId, changed) { if (tabId == details.id && changed.url == details.url) { browser.tabs.onUpdated.removeListener(listener); resolve(); } }); }); }; return [ async expect => { browser.test.log("Initial state, expect default properties."); expect(null, null, null, details[0]); }, async expect => { browser.test.log( "Change the icon in the current tab. Expect default properties excluding the icon." ); browser[action].setIcon({ tabId: tabs[0], path: "1.png" }); expect(details[1], null, null, details[0]); }, async expect => { browser.test.log("Create a new tab. Expect default properties."); let tab = await browser.tabs.create({ active: true, url: "about:blank?0", }); tabs.push(tab.id); browser.test.log("Await tab load."); let promise = promiseTabLoad({ id: tabs[1], url: "about:blank?0" }); let { url } = await browser.tabs.get(tabs[1]); if (url === "about:blank") { await promise; } expect(null, null, null, details[0]); }, async expect => { browser.test.log("Change properties. Expect new properties."); let tabId = tabs[1]; browser[action].setIcon({ tabId, path: "2.png" }); browser[action].setPopup({ tabId, popup: "2.html" }); browser[action].setTitle({ tabId, title: "Title 2" }); browser[action].setBadgeText({ tabId, text: "2" }); browser[action].setBadgeBackgroundColor({ tabId, color: "#ff0000", }); browser[action].setBadgeTextColor({ tabId, color: "#00ffff" }); browser[action].disable(tabId); expect(details[2], null, null, details[0]); }, async expect => { browser.test.log( "Switch back to the first tab. Expect previously set properties." ); await browser.tabs.update(tabs[0], { active: true }); expect(details[1], null, null, details[0]); }, async expect => { browser.test.log( "Change global values, expect those changes reflected." ); browser[action].setIcon({ path: "global.png" }); browser[action].setPopup({ popup: "global.html" }); browser[action].setTitle({ title: "Global Title" }); browser[action].setBadgeText({ text: "g" }); browser[action].setBadgeBackgroundColor({ color: [0, 0xff, 0, 0xff], }); browser[action].setBadgeTextColor({ color: [0xff, 0, 0xff, 0xff], }); browser[action].disable(); expect(details[1], null, details[3], details[0]); }, async expect => { browser.test.log("Re-enable globally. Expect enabled."); browser[action].enable(); expect(details[1], null, details[4], details[0]); }, async expect => { browser.test.log( "Switch back to tab 2. Expect former tab values, and new global values from previous steps." ); await browser.tabs.update(tabs[1], { active: true }); expect(details[2], null, details[4], details[0]); }, async expect => { browser.test.log( "Navigate to a new page. Expect tab-specific values to be cleared." ); let promise = promiseTabLoad({ id: tabs[1], url: "about:blank?1" }); browser.tabs.update(tabs[1], { url: "about:blank?1" }); await promise; expect(null, null, details[4], details[0]); }, async expect => { browser.test.log( "Delete tab, switch back to tab 1. Expect previous results again." ); await browser.tabs.remove(tabs[1]); expect(details[1], null, details[4], details[0]); }, async expect => { browser.test.log("Create a new tab. Expect new global properties."); let tab = await browser.tabs.create({ active: true, url: "about:blank?2", }); tabs.push(tab.id); expect(null, null, details[4], details[0]); }, async expect => { browser.test.log("Delete tab."); await browser.tabs.remove(tabs[2]); expect(details[1], null, details[4], details[0]); }, ]; }, }; add_task(async function testTabSwitchContext() { await runTests({ manifest: { browser_action: { default_icon: "default.png", default_popup: "__MSG_popup__", default_title: "Default __MSG_title__", default_area: "navbar", }, default_locale: "en", permissions: ["tabs"], }, ...tabSwitchTestData, }); }); add_task(async function testTabSwitchActionContext() { await SpecialPowers.pushPrefEnv({ set: [["extensions.manifestV3.enabled", true]], }); await runTests({ manifest: { manifest_version: 3, action: { default_icon: "default.png", default_popup: "__MSG_popup__", default_title: "Default __MSG_title__", default_area: "navbar", }, default_locale: "en", permissions: ["tabs"], }, ...tabSwitchTestData, }); }); add_task(async function testDefaultTitle() { await runTests({ manifest: { name: "Foo Extension", browser_action: { default_icon: "icon.png", default_area: "navbar", }, permissions: ["tabs"], }, files: { "icon.png": imageBuffer, }, getTests: function (tabs, windows) { let details = [ { title: "Foo Extension", popup: "", badge: "", badgeBackgroundColor: [0xd9, 0, 0, 255], badgeTextColor: [0xff, 0xff, 0xff, 0xff], icon: browser.runtime.getURL("icon.png"), enabled: true, }, { title: "Foo Title" }, { title: "Bar Title" }, ]; return [ async expect => { browser.test.log("Initial state. Expect default properties."); expect(null, null, null, details[0]); }, async expect => { browser.test.log("Change the tab title. Expect new title."); browser.browserAction.setTitle({ tabId: tabs[0], title: "Foo Title", }); expect(details[1], null, null, details[0]); }, async expect => { browser.test.log("Change the global title. Expect same properties."); browser.browserAction.setTitle({ title: "Bar Title" }); expect(details[1], null, details[2], details[0]); }, async expect => { browser.test.log("Clear the tab title. Expect new global title."); browser.browserAction.setTitle({ tabId: tabs[0], title: null }); expect(null, null, details[2], details[0]); }, async expect => { browser.test.log("Clear the global title. Expect default title."); browser.browserAction.setTitle({ title: null }); expect(null, null, null, details[0]); }, async expect => { browser.test.assertRejects( browser.browserAction.setPopup({ popup: "about:addons" }), /Access denied for URL about:addons/, "unable to set popup to about:addons" ); expect(null, null, null, details[0]); }, ]; }, }); }); add_task(async function testBadgeColorPersistence() { const extension = ExtensionTestUtils.loadExtension({ background() { browser.test.onMessage.addListener((msg, arg) => { browser.browserAction[msg](arg); }); }, manifest: { browser_action: { default_area: "navbar", }, }, }); await extension.startup(); function getBadgeForWindow(win) { const widget = getBrowserActionWidget(extension).forWindow(win).node; return widget.firstElementChild.badgeLabel; } let badge = getBadgeForWindow(window); const badgeChanged = new Promise(resolve => { const observer = new MutationObserver(() => resolve()); observer.observe(badge, { attributes: true, attributeFilter: ["style"] }); }); extension.sendMessage("setBadgeText", { text: "hi" }); extension.sendMessage("setBadgeBackgroundColor", { color: [0, 255, 0, 255] }); await badgeChanged; is(badge.textContent, "hi", "badge text is set in first window"); is( badge.style.backgroundColor, "rgb(0, 255, 0)", "badge color is set in first window" ); let windowOpenedPromise = BrowserTestUtils.waitForNewWindow(); let win = OpenBrowserWindow(); await windowOpenedPromise; badge = getBadgeForWindow(win); is(badge.textContent, "hi", "badge text is set in new window"); is( badge.style.backgroundColor, "rgb(0, 255, 0)", "badge color is set in new window" ); await BrowserTestUtils.closeWindow(win); await extension.unload(); }); add_task(async function testPropertyRemoval() { await runTests({ manifest: { name: "Generated extension", browser_action: { default_icon: "default.png", default_popup: "default.html", default_title: "Default Title", default_area: "navbar", }, }, files: { "default.png": imageBuffer, "global.png": imageBuffer, "global2.png": imageBuffer, "window.png": imageBuffer, "tab.png": imageBuffer, }, getTests: function (tabs, windows) { let defaultIcon = "chrome://mozapps/skin/extensions/extensionGeneric.svg"; let details = [ { icon: browser.runtime.getURL("default.png"), popup: browser.runtime.getURL("default.html"), title: "Default Title", badge: "", badgeBackgroundColor: [0xd9, 0x00, 0x00, 0xff], badgeTextColor: [0xff, 0xff, 0xff, 0xff], enabled: true, }, { icon: browser.runtime.getURL("global.png"), popup: browser.runtime.getURL("global.html"), title: "global", badge: "global", badgeBackgroundColor: [0x11, 0x11, 0x11, 0xff], badgeTextColor: [0x99, 0x99, 0x99, 0xff], }, { icon: browser.runtime.getURL("window.png"), popup: browser.runtime.getURL("window.html"), title: "window", badge: "window", badgeBackgroundColor: [0x22, 0x22, 0x22, 0xff], badgeTextColor: [0x88, 0x88, 0x88, 0xff], }, { icon: browser.runtime.getURL("tab.png"), popup: browser.runtime.getURL("tab.html"), title: "tab", badge: "tab", badgeBackgroundColor: [0x33, 0x33, 0x33, 0xff], badgeTextColor: [0x77, 0x77, 0x77, 0xff], }, { icon: defaultIcon, popup: "", title: "", badge: "", badgeBackgroundColor: [0x33, 0x33, 0x33, 0xff], badgeTextColor: [0x77, 0x77, 0x77, 0xff], }, { icon: browser.runtime.getURL("global2.png"), popup: browser.runtime.getURL("global2.html"), title: "global2", badge: "global2", badgeBackgroundColor: [0x44, 0x44, 0x44, 0xff], badgeTextColor: [0x66, 0x66, 0x66, 0xff], }, ]; return [ async expect => { browser.test.log("Initial state, expect default properties."); expect(null, null, null, details[0]); }, async expect => { browser.test.log("Set global values, expect the new values."); browser.browserAction.setIcon({ path: "global.png" }); browser.browserAction.setPopup({ popup: "global.html" }); browser.browserAction.setTitle({ title: "global" }); browser.browserAction.setBadgeText({ text: "global" }); browser.browserAction.setBadgeBackgroundColor({ color: "#111" }); browser.browserAction.setBadgeTextColor({ color: "#999" }); expect(null, null, details[1], details[0]); }, async expect => { browser.test.log("Set window values, expect the new values."); let windowId = windows[0]; browser.browserAction.setIcon({ windowId, path: "window.png" }); browser.browserAction.setPopup({ windowId, popup: "window.html" }); browser.browserAction.setTitle({ windowId, title: "window" }); browser.browserAction.setBadgeText({ windowId, text: "window" }); browser.browserAction.setBadgeBackgroundColor({ windowId, color: "#222", }); browser.browserAction.setBadgeTextColor({ windowId, color: "#888" }); expect(null, details[2], details[1], details[0]); }, async expect => { browser.test.log("Set tab values, expect the new values."); let tabId = tabs[0]; browser.browserAction.setIcon({ tabId, path: "tab.png" }); browser.browserAction.setPopup({ tabId, popup: "tab.html" }); browser.browserAction.setTitle({ tabId, title: "tab" }); browser.browserAction.setBadgeText({ tabId, text: "tab" }); browser.browserAction.setBadgeBackgroundColor({ tabId, color: "#333", }); browser.browserAction.setBadgeTextColor({ tabId, color: "#777" }); expect(details[3], details[2], details[1], details[0]); }, async expect => { browser.test.log( "Set empty tab values, expect empty values except for colors." ); let tabId = tabs[0]; browser.browserAction.setIcon({ tabId, path: "" }); browser.browserAction.setPopup({ tabId, popup: "" }); browser.browserAction.setTitle({ tabId, title: "" }); browser.browserAction.setBadgeText({ tabId, text: "" }); await browser.test.assertRejects( browser.browserAction.setBadgeBackgroundColor({ tabId, color: "" }), /^Invalid badge background color: ""$/, "Expected invalid badge background color error" ); await browser.test.assertRejects( browser.browserAction.setBadgeTextColor({ tabId, color: "" }), /^Invalid badge text color: ""$/, "Expected invalid badge text color error" ); expect(details[4], details[2], details[1], details[0]); }, async expect => { browser.test.log("Remove tab values, expect window values."); let tabId = tabs[0]; browser.browserAction.setIcon({ tabId, path: null }); browser.browserAction.setPopup({ tabId, popup: null }); browser.browserAction.setTitle({ tabId, title: null }); browser.browserAction.setBadgeText({ tabId, text: null }); browser.browserAction.setBadgeBackgroundColor({ tabId, color: null }); browser.browserAction.setBadgeTextColor({ tabId, color: null }); expect(null, details[2], details[1], details[0]); }, async expect => { browser.test.log("Remove window values, expect global values."); let windowId = windows[0]; browser.browserAction.setIcon({ windowId, path: null }); browser.browserAction.setPopup({ windowId, popup: null }); browser.browserAction.setTitle({ windowId, title: null }); browser.browserAction.setBadgeText({ windowId, text: null }); browser.browserAction.setBadgeBackgroundColor({ windowId, color: null, }); browser.browserAction.setBadgeTextColor({ windowId, color: null }); expect(null, null, details[1], details[0]); }, async expect => { browser.test.log("Change global values, expect the new values."); browser.browserAction.setIcon({ path: "global2.png" }); browser.browserAction.setPopup({ popup: "global2.html" }); browser.browserAction.setTitle({ title: "global2" }); browser.browserAction.setBadgeText({ text: "global2" }); browser.browserAction.setBadgeBackgroundColor({ color: "#444" }); browser.browserAction.setBadgeTextColor({ color: "#666" }); expect(null, null, details[5], details[0]); }, async expect => { browser.test.log("Remove global values, expect defaults."); browser.browserAction.setIcon({ path: null }); browser.browserAction.setPopup({ popup: null }); browser.browserAction.setBadgeText({ text: null }); browser.browserAction.setTitle({ title: null }); browser.browserAction.setBadgeBackgroundColor({ color: null }); browser.browserAction.setBadgeTextColor({ color: null }); expect(null, null, null, details[0]); }, ]; }, }); }); add_task(async function testMultipleWindows() { await runTests({ manifest: { browser_action: { default_icon: "default.png", default_popup: "default.html", default_title: "Default Title", default_area: "navbar", }, }, files: { "default.png": imageBuffer, "window1.png": imageBuffer, "window2.png": imageBuffer, }, getTests: function (tabs, windows) { let details = [ { icon: browser.runtime.getURL("default.png"), popup: browser.runtime.getURL("default.html"), title: "Default Title", badge: "", badgeBackgroundColor: [0xd9, 0x00, 0x00, 0xff], badgeTextColor: [0xff, 0xff, 0xff, 0xff], enabled: true, }, { icon: browser.runtime.getURL("window1.png"), popup: browser.runtime.getURL("window1.html"), title: "window1", badge: "w1", badgeBackgroundColor: [0x11, 0x11, 0x11, 0xff], badgeTextColor: [0x99, 0x99, 0x99, 0xff], }, { icon: browser.runtime.getURL("window2.png"), popup: browser.runtime.getURL("window2.html"), title: "window2", badge: "w2", badgeBackgroundColor: [0x22, 0x22, 0x22, 0xff], badgeTextColor: [0x88, 0x88, 0x88, 0xff], }, { title: "tab" }, ]; return [ async expect => { browser.test.log("Initial state, expect default properties."); expect(null, null, null, details[0]); }, async expect => { browser.test.log("Set window values, expect the new values."); let windowId = windows[0]; browser.browserAction.setIcon({ windowId, path: "window1.png" }); browser.browserAction.setPopup({ windowId, popup: "window1.html" }); browser.browserAction.setTitle({ windowId, title: "window1" }); browser.browserAction.setBadgeText({ windowId, text: "w1" }); browser.browserAction.setBadgeBackgroundColor({ windowId, color: "#111", }); browser.browserAction.setBadgeTextColor({ windowId, color: "#999" }); expect(null, details[1], null, details[0]); }, async expect => { browser.test.log("Create a new tab, expect window values."); let tab = await browser.tabs.create({ active: true }); tabs.push(tab.id); expect(null, details[1], null, details[0]); }, async expect => { browser.test.log("Set a tab title, expect it."); await browser.browserAction.setTitle({ tabId: tabs[1], title: "tab", }); expect(details[3], details[1], null, details[0]); }, async expect => { browser.test.log("Open a new window, expect default values."); let { id } = await browser.windows.create(); windows.push(id); expect(null, null, null, details[0]); }, async expect => { browser.test.log("Set window values, expect the new values."); let windowId = windows[1]; browser.browserAction.setIcon({ windowId, path: "window2.png" }); browser.browserAction.setPopup({ windowId, popup: "window2.html" }); browser.browserAction.setTitle({ windowId, title: "window2" }); browser.browserAction.setBadgeText({ windowId, text: "w2" }); browser.browserAction.setBadgeBackgroundColor({ windowId, color: "#222", }); browser.browserAction.setBadgeTextColor({ windowId, color: "#888" }); expect(null, details[2], null, details[0]); }, async expect => { browser.test.log( "Move tab from old window to the new one. Tab-specific data" + " is preserved but inheritance is from the new window" ); await browser.tabs.move(tabs[1], { windowId: windows[1], index: -1 }); await browser.tabs.update(tabs[1], { active: true }); expect(details[3], details[2], null, details[0]); }, async expect => { browser.test.log("Close the initial tab of the new window."); let [{ id }] = await browser.tabs.query({ windowId: windows[1], index: 0, }); await browser.tabs.remove(id); expect(details[3], details[2], null, details[0]); }, async expect => { browser.test.log( "Move the previous tab to a 3rd window, the 2nd one will close." ); await browser.windows.create({ tabId: tabs[1] }); expect(details[3], null, null, details[0]); }, async expect => { browser.test.log("Close the tab, go back to the 1st window."); await browser.tabs.remove(tabs[1]); expect(null, details[1], null, details[0]); }, async expect => { browser.test.log( "Assert failures for bad parameters. Expect no change" ); let calls = { setIcon: { path: "default.png" }, setPopup: { popup: "default.html" }, setTitle: { title: "Default Title" }, setBadgeText: { text: "" }, setBadgeBackgroundColor: { color: [0xd9, 0x00, 0x00, 0xff] }, setBadgeTextColor: { color: [0xff, 0xff, 0xff, 0xff] }, getPopup: {}, getTitle: {}, getBadgeText: {}, getBadgeBackgroundColor: {}, }; for (let [method, arg] of Object.entries(calls)) { browser.test.assertThrows( () => browser.browserAction[method]({ ...arg, windowId: -3 }), /-3 is too small \(must be at least -2\)/, method + " with invalid windowId" ); await browser.test.assertRejects( browser.browserAction[method]({ ...arg, tabId: tabs[0], windowId: windows[0], }), /Only one of tabId and windowId can be specified/, method + " with both tabId and windowId" ); } expect(null, details[1], null, details[0]); }, ]; }, }); }); add_task(async function testDefaultBadgeTextColor() { await runTests({ manifest: { browser_action: { default_icon: "default.png", default_popup: "default.html", default_title: "Default Title", default_area: "navbar", }, }, files: { "default.png": imageBuffer, "window1.png": imageBuffer, "window2.png": imageBuffer, }, getTests: function (tabs, windows) { let details = [ { icon: browser.runtime.getURL("default.png"), popup: browser.runtime.getURL("default.html"), title: "Default Title", badge: "", badgeBackgroundColor: [0xd9, 0x00, 0x00, 0xff], badgeTextColor: [0xff, 0xff, 0xff, 0xff], enabled: true, }, { badgeBackgroundColor: [0xff, 0xff, 0x00, 0xff], badgeTextColor: [0x00, 0x00, 0x00, 0xff], }, { badgeBackgroundColor: [0x00, 0x00, 0xff, 0xff], badgeTextColor: [0xff, 0xff, 0xff, 0xff], }, { badgeBackgroundColor: [0xff, 0xff, 0xff, 0x00], badgeTextColor: [0x00, 0x00, 0x00, 0xff], }, { badgeBackgroundColor: [0x00, 0x00, 0xff, 0xff], badgeTextColor: [0xff, 0x00, 0xff, 0xff], }, { badgeBackgroundColor: [0xff, 0xff, 0xff, 0x00] }, { badgeBackgroundColor: [0x00, 0x00, 0x00, 0x00], badgeTextColor: [0xff, 0xff, 0xff, 0xff], }, ]; return [ async expect => { browser.test.log("Initial state, expect default properties."); expect(null, null, null, details[0]); }, async expect => { browser.test.log("Set a global light bgcolor, expect black text."); browser.browserAction.setBadgeBackgroundColor({ color: "#ff0" }); expect(null, null, details[1], details[0]); }, async expect => { browser.test.log( "Set a window-specific dark bgcolor, expect white text." ); let windowId = windows[0]; browser.browserAction.setBadgeBackgroundColor({ windowId, color: "#00f", }); expect(null, details[2], details[1], details[0]); }, async expect => { browser.test.log( "Set a tab-specific transparent-white bgcolor, expect black text." ); let tabId = tabs[0]; browser.browserAction.setBadgeBackgroundColor({ tabId, color: "#fff0", }); expect(details[3], details[2], details[1], details[0]); }, async expect => { browser.test.log( "Set a window-specific text color, expect it in the tab." ); let windowId = windows[0]; browser.browserAction.setBadgeTextColor({ windowId, color: "#f0f" }); expect(details[5], details[4], details[1], details[0]); }, async expect => { browser.test.log( "Remove the window-specific text color, expect black again." ); let windowId = windows[0]; browser.browserAction.setBadgeTextColor({ windowId, color: null }); expect(details[3], details[2], details[1], details[0]); }, async expect => { browser.test.log( "Set a tab-specific transparent-black bgcolor, expect white text." ); let tabId = tabs[0]; browser.browserAction.setBadgeBackgroundColor({ tabId, color: "#0000", }); expect(details[6], details[2], details[1], details[0]); }, ]; }, }); }); add_task(async function testNavigationClearsData() { let url = "http://example.com/"; let default_title = "Default title"; let tab_title = "Tab title"; const { Management: { global: { tabTracker }, }, } = ChromeUtils.importESModule("resource://gre/modules/Extension.sys.mjs"); let extension, tabs = []; async function addTab(...args) { let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, ...args); tabs.push(tab); return tab; } async function sendMessage(method, param, expect, msg) { extension.sendMessage({ method, param, expect, msg }); await extension.awaitMessage("done"); } async function expectTabSpecificData(tab, msg) { let tabId = tabTracker.getId(tab); await sendMessage("getBadgeText", { tabId }, "foo", msg); await sendMessage("getTitle", { tabId }, tab_title, msg); } async function expectDefaultData(tab, msg) { let tabId = tabTracker.getId(tab); await sendMessage("getBadgeText", { tabId }, "", msg); await sendMessage("getTitle", { tabId }, default_title, msg); } async function setTabSpecificData(tab) { let tabId = tabTracker.getId(tab); await expectDefaultData( tab, "Expect default data before setting tab-specific data." ); await sendMessage("setBadgeText", { tabId, text: "foo" }); await sendMessage("setTitle", { tabId, title: tab_title }); await expectTabSpecificData( tab, "Expect tab-specific data after setting it." ); } info("Load a tab before installing the extension"); let tab1 = await addTab(url, true, true); extension = ExtensionTestUtils.loadExtension({ manifest: { browser_action: { default_title }, }, background: function () { browser.test.onMessage.addListener( async ({ method, param, expect, msg }) => { let result = await browser.browserAction[method](param); if (expect !== undefined) { browser.test.assertEq(expect, result, msg); } browser.test.sendMessage("done"); } ); }, }); await extension.startup(); info("Set tab-specific data to the existing tab."); await setTabSpecificData(tab1); info("Add a hash. Does not cause navigation."); await navigateTab(tab1, url + "#hash"); await expectTabSpecificData( tab1, "Adding a hash does not clear tab-specific data" ); info("Remove the hash. Causes navigation."); await navigateTab(tab1, url); await expectDefaultData(tab1, "Removing hash clears tab-specific data"); info("Open a new tab, set tab-specific data to it."); let tab2 = await addTab("about:newtab", false, false); await setTabSpecificData(tab2); info("Load a page in that tab."); await navigateTab(tab2, url); await expectDefaultData(tab2, "Loading a page clears tab-specific data."); info("Set tab-specific data."); await setTabSpecificData(tab2); info("Push history state. Does not cause navigation."); await historyPushState(tab2, url + "/path"); await expectTabSpecificData( tab2, "history.pushState() does not clear tab-specific data" ); info("Navigate when the tab is not selected"); gBrowser.selectedTab = tab1; await navigateTab(tab2, url); await expectDefaultData( tab2, "Navigating clears tab-specific data, even when not selected." ); for (let tab of tabs) { await BrowserTestUtils.removeTab(tab); } await extension.unload(); });