From 2b62ae8f00273dc5e3debd9582a373c1558e9743 Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Sun, 28 Jan 2018 20:46:24 +0100 Subject: [PATCH] Bug 1390464 - Add windowId parameter in sidebarAction methods r=mixedpuppy MozReview-Commit-ID: eSJnVzpNvO --HG-- extra : rebase_source : 5837207b8375649c5ebc24e27a6dcc6894d6d40b --- .../extensions/parent/ext-sidebarAction.js | 126 ++++--- .../extensions/schemas/sidebar_action.json | 34 +- .../browser_ext_sidebarAction_context.js | 356 ++++++++++++------ 3 files changed, 338 insertions(+), 178 deletions(-) diff --git a/browser/components/extensions/parent/ext-sidebarAction.js b/browser/components/extensions/parent/ext-sidebarAction.js index c0a7f2cfeb9f..87f612bd4b0b 100644 --- a/browser/components/extensions/parent/ext-sidebarAction.js +++ b/browser/components/extensions/parent/ext-sidebarAction.js @@ -4,6 +4,10 @@ ChromeUtils.import("resource://gre/modules/ExtensionParent.jsm"); +var { + ExtensionError, +} = ExtensionUtils; + var { IconDetails, } = ExtensionParent; @@ -50,8 +54,13 @@ this.sidebarAction = class extends ExtensionAPI { }; this.globals = Object.create(this.defaults); - this.tabContext = new TabContext(tab => Object.create(this.globals), - extension); + this.tabContext = new TabContext(target => { + let window = target.ownerGlobal; + if (target === window) { + return Object.create(this.globals); + } + return Object.create(this.tabContext.get(window)); + }, extension); // We need to ensure our elements are available before session restore. this.windowOpenListener = (window) => { @@ -248,16 +257,18 @@ this.sidebarAction = class extends ExtensionAPI { /** * Update the broadcaster and menuitem when the extension changes the icon, - * title, url, etc. If it only changes a parameter for a single - * tab, `tab` will be that tab. Otherwise it will be null. + * title, url, etc. If it only changes a parameter for a single tab, `target` + * will be that tab. If it only changes a parameter for a single window, + * `target` will be that window. Otherwise `target` will be null. * - * @param {XULElement|null} nativeTab - * Browser tab, may be null. + * @param {XULElement|ChromeWindow|null} target + * Browser tab or browser chrome window, may be null. */ - updateOnChange(nativeTab) { - if (nativeTab) { - if (nativeTab.selected) { - this.updateWindow(nativeTab.ownerGlobal); + updateOnChange(target) { + if (target) { + let window = target.ownerGlobal; + if (target === window || target.selected) { + this.updateWindow(window); } } else { for (let window of windowTracker.browserWindows()) { @@ -267,46 +278,71 @@ this.sidebarAction = class extends ExtensionAPI { } /** - * Set a default or tab specific property. + * Gets the target object and its associated values corresponding to + * the `details` parameter of the various get* and set* API methods. * - * @param {XULElement|null} nativeTab - * Webextension tab object, may be null. + * @param {Object} details + * An object with optional `tabId` or `windowId` properties. + * @throws if both `tabId` and `windowId` are specified, or if they are invalid. + * @returns {Object} + * An object with two properties: `target` and `values`. + * - If a `tabId` was specified, `target` will be the corresponding + * XULElement tab. If a `windowId` was specified, `target` will be + * the corresponding ChromeWindow. Otherwise it will be `null`. + * - `values` will contain the icon, title and panel associated with + * the target. + */ + getContextData({tabId, windowId}) { + if (tabId != null && windowId != null) { + throw new ExtensionError("Only one of tabId and windowId can be specified."); + } + let target, values; + if (tabId != null) { + target = tabTracker.getTab(tabId); + values = this.tabContext.get(target); + } else if (windowId != null) { + target = windowTracker.getWindow(windowId); + values = this.tabContext.get(target); + } else { + target = null; + values = this.globals; + } + return {target, values}; + } + + /** + * Set a global, window specific or tab specific property. + * + * @param {Object} details + * An object with optional `tabId` or `windowId` properties. * @param {string} prop - * String property to retrieve ["icon", "title", or "panel"]. + * String property to set ["icon", "title", or "panel"]. * @param {string} value * Value for property. */ - setProperty(nativeTab, prop, value) { - let values; - if (nativeTab === null) { - values = this.globals; - } else { - values = this.tabContext.get(nativeTab); - } + setProperty(details, prop, value) { + let {target, values} = this.getContextData(details); if (value === null) { delete values[prop]; } else { values[prop] = value; } - this.updateOnChange(nativeTab); + this.updateOnChange(target); } /** - * Retrieve a property from the tab or globals if tab is null. + * Retrieve the value of a global, window specific or tab specific property. * - * @param {XULElement|null} nativeTab - * Browser tab object, may be null. + * @param {Object} details + * An object with optional `tabId` or `windowId` properties. * @param {string} prop * String property to retrieve ["icon", "title", or "panel"] * @returns {string} value - * Value for prop. + * Value of prop. */ - getProperty(nativeTab, prop) { - if (nativeTab === null) { - return this.globals[prop]; - } - return this.tabContext.get(nativeTab)[prop]; + getProperty(details, prop) { + return this.getContextData(details).values[prop]; } /** @@ -360,40 +396,25 @@ this.sidebarAction = class extends ExtensionAPI { let {extension} = context; const sidebarAction = this; - function getTab(tabId) { - if (tabId !== null) { - return tabTracker.getTab(tabId); - } - return null; - } - return { sidebarAction: { async setTitle(details) { - let nativeTab = getTab(details.tabId); - sidebarAction.setProperty(nativeTab, "title", details.title); + sidebarAction.setProperty(details, "title", details.title); }, getTitle(details) { - let nativeTab = getTab(details.tabId); - - let title = sidebarAction.getProperty(nativeTab, "title"); - return Promise.resolve(title); + return sidebarAction.getProperty(details, "title"); }, async setIcon(details) { - let nativeTab = getTab(details.tabId); - let icon = IconDetails.normalize(details, extension, context); if (!Object.keys(icon).length) { icon = null; } - sidebarAction.setProperty(nativeTab, "icon", icon); + sidebarAction.setProperty(details, "icon", icon); }, async setPanel(details) { - let nativeTab = getTab(details.tabId); - let url; // Clear the url when given null or empty string. if (!details.panel) { @@ -405,14 +426,11 @@ this.sidebarAction = class extends ExtensionAPI { } } - sidebarAction.setProperty(nativeTab, "panel", url); + sidebarAction.setProperty(details, "panel", url); }, getPanel(details) { - let nativeTab = getTab(details.tabId); - - let panel = sidebarAction.getProperty(nativeTab, "panel"); - return Promise.resolve(panel); + return sidebarAction.getProperty(details, "panel"); }, open() { diff --git a/browser/components/extensions/schemas/sidebar_action.json b/browser/components/extensions/schemas/sidebar_action.json index 4e10bb4a2323..ae13737bda38 100644 --- a/browser/components/extensions/schemas/sidebar_action.json +++ b/browser/components/extensions/schemas/sidebar_action.json @@ -74,6 +74,12 @@ "type": "integer", "optional": true, "description": "Sets the sidebar title for the tab specified by tabId. Automatically resets when the tab is closed." + }, + "windowId": { + "type": "integer", + "optional": true, + "minimum": -2, + "description": "Sets the sidebar title for the window specified by windowId." } } } @@ -92,7 +98,13 @@ "tabId": { "type": "integer", "optional": true, - "description": "Specify the tab to get the title from. If no tab is specified, the non-tab-specific title is returned." + "description": "Specify the tab to get the title from. If no tab nor window is specified, the global title is returned." + }, + "windowId": { + "type": "integer", + "optional": true, + "minimum": -2, + "description": "Specify the window to get the title from. If no tab nor window is specified, the global title is returned." } } } @@ -137,6 +149,12 @@ "type": "integer", "optional": true, "description": "Sets the sidebar icon for the tab specified by tabId. Automatically resets when the tab is closed." + }, + "windowId": { + "type": "integer", + "optional": true, + "minimum": -2, + "description": "Sets the sidebar icon for the window specified by windowId." } } } @@ -158,6 +176,12 @@ "minimum": 0, "description": "Sets the sidebar url for the tab specified by tabId. Automatically resets when the tab is closed." }, + "windowId": { + "type": "integer", + "optional": true, + "minimum": -2, + "description": "Sets the sidebar url for the window specified by windowId." + }, "panel": { "choices": [ {"type": "string"}, @@ -182,7 +206,13 @@ "tabId": { "type": "integer", "optional": true, - "description": "Specify the tab to get the sidebar from. If no tab is specified, the non-tab-specific sidebar is returned." + "description": "Specify the tab to get the panel from. If no tab nor window is specified, the global panel is returned." + }, + "windowId": { + "type": "integer", + "optional": true, + "minimum": -2, + "description": "Specify the window to get the panel from. If no tab nor window is specified, the global panel is returned." } } } diff --git a/browser/components/extensions/test/browser/browser_ext_sidebarAction_context.js b/browser/components/extensions/test/browser/browser_ext_sidebarAction_context.js index a6294777fc24..8f10706eb580 100644 --- a/browser/components/extensions/test/browser/browser_ext_sidebarAction_context.js +++ b/browser/components/extensions/test/browser/browser_ext_sidebarAction_context.js @@ -11,22 +11,19 @@ SpecialPowers.pushPrefEnv({ async function runTests(options) { async function background(getTests) { - async function checkDetails(expecting, tabId) { - let title = await browser.sidebarAction.getTitle({tabId}); + async function checkDetails(expecting, details) { + let title = await browser.sidebarAction.getTitle(details); browser.test.assertEq(expecting.title, title, - "expected value from getTitle"); + "expected value from getTitle in " + JSON.stringify(details)); - let panel = await browser.sidebarAction.getPanel({tabId}); + let panel = await browser.sidebarAction.getPanel(details); browser.test.assertEq(expecting.panel, panel, - "expected value from getPanel"); + "expected value from getPanel in " + JSON.stringify(details)); } - let expectDefaults = expecting => { - return checkDetails(expecting); - }; - let tabs = []; - let tests = getTests(tabs, expectDefaults); + let windows = []; + let tests = getTests(tabs, windows); { let tabId = 0xdeadbeef; @@ -49,15 +46,21 @@ async function runTests(options) { function nextTest() { let test = tests.shift(); - test(async expecting => { + 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 tabs = await browser.tabs.query({active: true, currentWindow: true}); - await checkDetails(expecting, tabs[0].id); + let [{windowId, id: tabId}] = await browser.tabs.query({active: true, currentWindow: true}); + await checkDetails(expectTab, {tabId}); + await checkDetails(expectWindow, {windowId}); + await checkDetails(expectGlobal, {}); // Check that the actual icon has the expected values, then // run the next test. - browser.test.sendMessage("nextTest", expecting, tests.length); + browser.test.sendMessage("nextTest", expectTab, windowId, tests.length); }); } @@ -69,9 +72,9 @@ async function runTests(options) { nextTest(); }); - browser.tabs.query({active: true, currentWindow: true}, resultTabs => { - tabs[0] = resultTabs[0].id; - }); + let [{id, windowId}] = await browser.tabs.query({active: true, currentWindow: true}); + tabs.push(id); + windows.push(windowId); } let extension = ExtensionTestUtils.loadExtension({ @@ -84,7 +87,8 @@ async function runTests(options) { }); let sidebarActionId; - function checkDetails(details) { + function checkDetails(details, windowId) { + let {document} = Services.wm.getOuterWindowWithId(windowId); if (!sidebarActionId) { sidebarActionId = `${makeWidgetId(extension.id)}-sidebar-action`; } @@ -103,8 +107,8 @@ async function runTests(options) { } let awaitFinish = new Promise(resolve => { - extension.onMessage("nextTest", (expecting, testsRemaining) => { - checkDetails(expecting); + extension.onMessage("nextTest", (expecting, windowId, testsRemaining) => { + checkDetails(expecting, windowId); if (testsRemaining) { extension.sendMessage("runNextTest"); @@ -149,7 +153,7 @@ add_task(async function testTabSwitchContext() { "files": { "default.html": sidebar, - "default-2.html": sidebar, + "global.html": sidebar, "2.html": sidebar, "_locales/en/messages.json": { @@ -165,40 +169,29 @@ add_task(async function testTabSwitchContext() { }, "default.png": imageBuffer, - "default-2.png": imageBuffer, + "global.png": imageBuffer, "1.png": imageBuffer, "2.png": imageBuffer, }, - getTests: function(tabs, expectDefaults) { + getTests: function(tabs) { let details = [ {"icon": browser.runtime.getURL("default.png"), "panel": browser.runtime.getURL("default.html"), "title": "Default Title", }, {"icon": browser.runtime.getURL("1.png"), - "panel": browser.runtime.getURL("default.html"), - "title": "Default Title", }, {"icon": browser.runtime.getURL("2.png"), "panel": browser.runtime.getURL("2.html"), "title": "Title 2", }, - {"icon": browser.runtime.getURL("1.png"), - "panel": browser.runtime.getURL("default-2.html"), - "title": "Default Title 2", - }, - {"icon": browser.runtime.getURL("1.png"), - "panel": browser.runtime.getURL("default-2.html"), - "title": "Default Title 2", - }, - {"icon": browser.runtime.getURL("default-2.png"), - "panel": browser.runtime.getURL("default-2.html"), - "title": "Default Title 2", + {"icon": browser.runtime.getURL("global.png"), + "panel": browser.runtime.getURL("global.html"), + "title": "Global Title", }, {"icon": browser.runtime.getURL("1.png"), "panel": browser.runtime.getURL("2.html"), - "title": "Default Title 2", }, ]; @@ -206,23 +199,20 @@ add_task(async function testTabSwitchContext() { async expect => { browser.test.log("Initial state, expect default properties."); - await expectDefaults(details[0]); - expect(details[0]); + expect(null, null, null, details[0]); }, async expect => { browser.test.log("Change the icon in the current tab. Expect default properties excluding the icon."); await browser.sidebarAction.setIcon({tabId: tabs[0], path: "1.png"}); - await expectDefaults(details[0]); - expect(details[1]); + 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); - await expectDefaults(details[0]); - expect(details[0]); + expect(null, null, null, details[0]); }, async expect => { browser.test.log("Change properties. Expect new properties."); @@ -232,8 +222,7 @@ add_task(async function testTabSwitchContext() { browser.sidebarAction.setPanel({tabId, panel: "2.html"}), browser.sidebarAction.setTitle({tabId, title: "Title 2"}), ]); - await expectDefaults(details[0]); - expect(details[2]); + expect(details[2], null, null, details[0]); }, expect => { browser.test.log("Navigate to a new page. Expect no changes."); @@ -243,7 +232,7 @@ add_task(async function testTabSwitchContext() { browser.tabs.onUpdated.addListener(function listener(tabId, changed) { if (tabId == tabs[1] && changed.url) { browser.tabs.onUpdated.removeListener(listener); - expect(details[2]); + expect(details[2], null, null, details[0]); } }); @@ -252,53 +241,51 @@ add_task(async function testTabSwitchContext() { 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]); + expect(details[1], null, null, details[0]); }, async expect => { - browser.test.log("Change default values, expect those changes reflected."); + browser.test.log("Change global values, expect those changes reflected."); await Promise.all([ - browser.sidebarAction.setIcon({path: "default-2.png"}), - browser.sidebarAction.setPanel({panel: "default-2.html"}), - browser.sidebarAction.setTitle({title: "Default Title 2"}), + browser.sidebarAction.setIcon({path: "global.png"}), + browser.sidebarAction.setPanel({panel: "global.html"}), + browser.sidebarAction.setTitle({title: "Global Title"}), ]); - await expectDefaults(details[3]); - expect(details[3]); + expect(details[1], null, details[3], details[0]); }, async expect => { - browser.test.log("Switch back to tab 2. Expect former value, unaffected by changes to defaults in previous step."); + browser.test.log("Switch back to tab 2. Expect former tab values, and new global values from previous step."); await browser.tabs.update(tabs[1], {active: true}); - await expectDefaults(details[3]); - expect(details[2]); + expect(details[2], null, details[3], 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[4]); + expect(details[1], null, details[3], details[0]); }, async expect => { - browser.test.log("Create a new tab. Expect new default properties."); + 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(details[5]); + expect(null, null, details[3], details[0]); }, async expect => { browser.test.log("Delete tab."); await browser.tabs.remove(tabs[2]); - expect(details[4]); + expect(details[1], null, details[3], details[0]); }, async expect => { browser.test.log("Change tab panel."); let tabId = tabs[0]; await browser.sidebarAction.setPanel({tabId, panel: "2.html"}); - expect(details[6]); + expect(details[4], null, details[3], details[0]); }, async expect => { browser.test.log("Revert tab panel."); let tabId = tabs[0]; await browser.sidebarAction.setPanel({tabId, panel: null}); - expect(details[4]); + expect(details[1], null, details[3], details[0]); }, ]; }, @@ -323,53 +310,44 @@ add_task(async function testDefaultTitle() { "icon.png": imageBuffer, }, - getTests: function(tabs, expectGlobals) { + getTests: function(tabs) { let details = [ {"title": "Foo Extension", "panel": browser.runtime.getURL("sidebar.html"), "icon": browser.runtime.getURL("icon.png")}, - {"title": "Foo Title", - "panel": browser.runtime.getURL("sidebar.html"), - "icon": browser.runtime.getURL("icon.png")}, - {"title": "Bar Title", - "panel": browser.runtime.getURL("sidebar.html"), - "icon": browser.runtime.getURL("icon.png")}, + {"title": "Foo Title"}, + {"title": "Bar Title"}, ]; return [ async expect => { browser.test.log("Initial state. Expect default extension title."); - await expectGlobals(details[0]); - expect(details[0]); + expect(null, null, null, details[0]); }, async expect => { browser.test.log("Change the tab title. Expect new title."); browser.sidebarAction.setTitle({tabId: tabs[0], title: "Foo Title"}); - await expectGlobals(details[0]); - expect(details[1]); + expect(details[1], null, null, details[0]); }, async expect => { browser.test.log("Change the global title. Expect same properties."); browser.sidebarAction.setTitle({title: "Bar Title"}); - await expectGlobals(details[2]); - expect(details[1]); + expect(details[1], null, details[2], details[0]); }, async expect => { browser.test.log("Clear the tab title. Expect new global title."); browser.sidebarAction.setTitle({tabId: tabs[0], title: null}); - await expectGlobals(details[2]); - expect(details[2]); + expect(null, null, details[2], details[0]); }, async expect => { browser.test.log("Clear the global title. Expect default title."); browser.sidebarAction.setTitle({title: null}); - await expectGlobals(details[0]); - expect(details[0]); + expect(null, null, null, details[0]); }, async expect => { browser.test.assertRejects( @@ -377,8 +355,7 @@ add_task(async function testDefaultTitle() { /Access denied for URL about:addons/, "unable to set panel to about:addons"); - await expectGlobals(details[0]); - expect(details[0]); + expect(null, null, null, details[0]); }, ]; }, @@ -401,57 +378,66 @@ add_task(async function testPropertyRemoval() { files: { "default.html": sidebar, - "p1.html": sidebar, - "p2.html": sidebar, - "p3.html": sidebar, + "global.html": sidebar, + "global2.html": sidebar, + "window.html": sidebar, + "tab.html": sidebar, "default.png": imageBuffer, - "i1.png": imageBuffer, - "i2.png": imageBuffer, - "i3.png": imageBuffer, + "global.png": imageBuffer, + "global2.png": imageBuffer, + "window.png": imageBuffer, + "tab.png": imageBuffer, }, - getTests: function(tabs, expectGlobals) { + getTests: function(tabs, windows) { let defaultIcon = "chrome://browser/content/extension.svg"; let details = [ {"icon": browser.runtime.getURL("default.png"), "panel": browser.runtime.getURL("default.html"), "title": "Default Title"}, - {"icon": browser.runtime.getURL("i1.png"), - "panel": browser.runtime.getURL("p1.html"), - "title": "t1"}, - {"icon": browser.runtime.getURL("i2.png"), - "panel": browser.runtime.getURL("p2.html"), - "title": "t2"}, + {"icon": browser.runtime.getURL("global.png"), + "panel": browser.runtime.getURL("global.html"), + "title": "global"}, + {"icon": browser.runtime.getURL("window.png"), + "panel": browser.runtime.getURL("window.html"), + "title": "window"}, + {"icon": browser.runtime.getURL("tab.png"), + "panel": browser.runtime.getURL("tab.html"), + "title": "tab"}, {"icon": defaultIcon, - "panel": browser.runtime.getURL("p1.html"), "title": ""}, - {"icon": browser.runtime.getURL("i3.png"), - "panel": browser.runtime.getURL("p3.html"), - "title": "t3"}, + {"icon": browser.runtime.getURL("global2.png"), + "panel": browser.runtime.getURL("global2.html"), + "title": "global2"}, ]; return [ async expect => { browser.test.log("Initial state, expect default properties."); - await expectGlobals(details[0]); - expect(details[0]); + expect(null, null, null, details[0]); }, async expect => { browser.test.log("Set global values, expect the new values."); - browser.sidebarAction.setIcon({path: "i1.png"}); - browser.sidebarAction.setPanel({panel: "p1.html"}); - browser.sidebarAction.setTitle({title: "t1"}); - await expectGlobals(details[1]); - expect(details[1]); + browser.sidebarAction.setIcon({path: "global.png"}); + browser.sidebarAction.setPanel({panel: "global.html"}); + browser.sidebarAction.setTitle({title: "global"}); + expect(null, null, details[1], details[0]); + }, + async expect => { + browser.test.log("Set window values, expect the new values."); + let windowId = windows[0]; + browser.sidebarAction.setIcon({windowId, path: "window.png"}); + browser.sidebarAction.setPanel({windowId, panel: "window.html"}); + browser.sidebarAction.setTitle({windowId, title: "window"}); + 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.sidebarAction.setIcon({tabId, path: "i2.png"}); - browser.sidebarAction.setPanel({tabId, panel: "p2.html"}); - browser.sidebarAction.setTitle({tabId, title: "t2"}); - await expectGlobals(details[1]); - expect(details[2]); + browser.sidebarAction.setIcon({tabId, path: "tab.png"}); + browser.sidebarAction.setPanel({tabId, panel: "tab.html"}); + browser.sidebarAction.setTitle({tabId, title: "tab"}); + expect(details[3], details[2], details[1], details[0]); }, async expect => { browser.test.log("Set empty tab values."); @@ -459,33 +445,159 @@ add_task(async function testPropertyRemoval() { browser.sidebarAction.setIcon({tabId, path: ""}); browser.sidebarAction.setPanel({tabId, panel: ""}); browser.sidebarAction.setTitle({tabId, title: ""}); - await expectGlobals(details[1]); - expect(details[3]); + expect(details[4], details[2], details[1], details[0]); }, async expect => { - browser.test.log("Remove tab values, expect global values."); + browser.test.log("Remove tab values, expect window values."); let tabId = tabs[0]; browser.sidebarAction.setIcon({tabId, path: null}); browser.sidebarAction.setPanel({tabId, panel: null}); browser.sidebarAction.setTitle({tabId, title: null}); - await expectGlobals(details[1]); - expect(details[1]); + expect(null, details[2], details[1], details[0]); + }, + async expect => { + browser.test.log("Remove window values, expect global values."); + let windowId = windows[0]; + browser.sidebarAction.setIcon({windowId, path: null}); + browser.sidebarAction.setPanel({windowId, panel: null}); + browser.sidebarAction.setTitle({windowId, title: null}); + expect(null, null, details[1], details[0]); }, async expect => { browser.test.log("Change global values, expect the new values."); - browser.sidebarAction.setIcon({path: "i3.png"}); - browser.sidebarAction.setPanel({panel: "p3.html"}); - browser.sidebarAction.setTitle({title: "t3"}); - await expectGlobals(details[4]); - expect(details[4]); + browser.sidebarAction.setIcon({path: "global2.png"}); + browser.sidebarAction.setPanel({panel: "global2.html"}); + browser.sidebarAction.setTitle({title: "global2"}); + expect(null, null, details[5], details[0]); }, async expect => { browser.test.log("Remove global values, expect defaults."); browser.sidebarAction.setIcon({path: null}); browser.sidebarAction.setPanel({panel: null}); browser.sidebarAction.setTitle({title: null}); - await expectGlobals(details[0]); - expect(details[0]); + expect(null, null, null, details[0]); + }, + ]; + }, + }); +}); + +add_task(async function testMultipleWindows() { + await runTests({ + manifest: { + "name": "Foo Extension", + + "sidebar_action": { + "default_icon": "default.png", + "default_panel": "default.html", + "default_title": "Default Title", + }, + + "permissions": ["tabs"], + }, + + files: { + "default.html": sidebar, + "window1.html": sidebar, + "window2.html": sidebar, + "default.png": imageBuffer, + "window1.png": imageBuffer, + "window2.png": imageBuffer, + }, + + getTests: function(tabs, windows) { + let details = [ + {"icon": browser.runtime.getURL("default.png"), + "panel": browser.runtime.getURL("default.html"), + "title": "Default Title"}, + {"icon": browser.runtime.getURL("window1.png"), + "panel": browser.runtime.getURL("window1.html"), + "title": "window1"}, + {"icon": browser.runtime.getURL("window2.png"), + "panel": browser.runtime.getURL("window2.html"), + "title": "window2"}, + {"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.sidebarAction.setIcon({windowId, path: "window1.png"}); + browser.sidebarAction.setPanel({windowId, panel: "window1.html"}); + browser.sidebarAction.setTitle({windowId, title: "window1"}); + 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.sidebarAction.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.sidebarAction.setIcon({windowId, path: "window2.png"}); + browser.sidebarAction.setPanel({windowId, panel: "window2.html"}); + browser.sidebarAction.setTitle({windowId, title: "window2"}); + 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 cleared (bug 1451176) and 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(null, details[2], null, details[0]); + }, + async expect => { + browser.test.log("Close the tab, expect window values."); + await browser.tabs.remove(tabs[1]); + expect(null, details[2], null, details[0]); + }, + async expect => { + browser.test.log("Close the new window and go back to the previous one."); + await browser.windows.remove(windows[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"}, + setPanel: {panel: "default.html"}, + setTitle: {title: "Default Title"}, + getPanel: {}, + getTitle: {}, + }; + for (let [method, arg] of Object.entries(calls)) { + browser.test.assertThrows( + () => browser.sidebarAction[method]({...arg, windowId: -3}), + /-3 is too small \(must be at least -2\)/, + method + " with invalid windowId", + ); + await browser.test.assertRejects( + browser.sidebarAction[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]); }, ]; },