Bug 1464862 - Expose multiselected status to "tabs.Tab.highlighted", and allow to change it via "browser.tabs.highlight()"
MozReview-Commit-ID: H2SiqM5ksCH
--- a/browser/components/extensions/parent/ext-browser.js
+++ b/browser/components/extensions/parent/ext-browser.js
@@ -702,16 +702,21 @@ class Tab extends TabBase {
get pinned() {
return this.nativeTab.pinned;
}
get active() {
return this.nativeTab.selected;
}
+ get highlighted() {
+ let {selected, multiselected} = this.nativeTab;
+ return selected || multiselected;
+ }
+
get selected() {
return this.nativeTab.selected;
}
get status() {
if (this.nativeTab.getAttribute("busy") === "true") {
return "loading";
}
--- a/browser/components/extensions/parent/ext-tabs.js
+++ b/browser/components/extensions/parent/ext-tabs.js
@@ -342,17 +342,17 @@ this.tabs = class extends ExtensionAPI {
static onUninstall(id) {
tabHidePopup.clearConfirmation(id);
}
getAPI(context) {
let {extension} = context;
- let {tabManager} = extension;
+ let {tabManager, windowManager} = extension;
function getTabOrActive(tabId) {
if (tabId !== null) {
return tabTracker.getTab(tabId);
}
return tabTracker.activeTab;
}
@@ -1230,13 +1230,42 @@ this.tabs = class extends ExtensionAPI {
}
}
if (hidden.length > 0) {
let win = Services.wm.getMostRecentWindow("navigator:browser");
tabHidePopup.open(win, extension.id);
}
return hidden;
},
+
+ highlight(highlightInfo) {
+ if (!Services.prefs.getBoolPref("browser.tabs.multiselect")) {
+ throw new ExtensionError("Multiple tab selection is not enabled.");
+ }
+ let {windowId, tabs} = highlightInfo;
+ if (windowId == null) {
+ windowId = Window.WINDOW_ID_CURRENT;
+ }
+ let window = windowTracker.getWindow(windowId, context);
+ if (!Array.isArray(tabs)) {
+ tabs = [tabs];
+ } else if (tabs.length == 0) {
+ throw new ExtensionError("No highlighted tab.");
+ }
+ tabs = tabs.map((tabIndex) => {
+ let tab = window.gBrowser.tabs[tabIndex];
+ if (!tab) {
+ throw new ExtensionError("No tab at index: " + tabIndex);
+ }
+ return tab;
+ });
+ window.gBrowser.clearMultiSelectedTabs();
+ window.gBrowser.selectedTab = tabs[0];
+ for (let tab of tabs) {
+ window.gBrowser.addToMultiSelectedTabs(tab);
+ }
+ return windowManager.convert(window, {populate: true});
+ },
},
};
return self;
}
};
--- a/browser/components/extensions/schemas/tabs.json
+++ b/browser/components/extensions/schemas/tabs.json
@@ -764,17 +764,16 @@
}
]
},
{
"name": "highlight",
"type": "function",
"description": "Highlights the given tabs.",
"async": "callback",
- "unsupported": "true",
"parameters": [
{
"type": "object",
"name": "highlightInfo",
"properties": {
"windowId": {
"type": "integer",
"optional": true,
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -172,16 +172,17 @@ skip-if = (verify && !debug && (os == 'm
[browser_ext_tabs_executeScript_good.js]
[browser_ext_tabs_executeScript_bad.js]
[browser_ext_tabs_executeScript_multiple.js]
[browser_ext_tabs_executeScript_no_create.js]
[browser_ext_tabs_executeScript_runAt.js]
[browser_ext_tabs_getCurrent.js]
[browser_ext_tabs_hide.js]
[browser_ext_tabs_hide_update.js]
+[browser_ext_tabs_highlight.js]
[browser_ext_tabs_insertCSS.js]
[browser_ext_tabs_lastAccessed.js]
[browser_ext_tabs_lazy.js]
[browser_ext_tabs_removeCSS.js]
[browser_ext_tabs_move_array.js]
[browser_ext_tabs_move_window.js]
[browser_ext_tabs_move_window_multiple.js]
[browser_ext_tabs_move_window_pinned.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_highlight.js
@@ -0,0 +1,80 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+/* global gBrowser SessionStore */
+"use strict";
+
+add_task(async function test_highlighted() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.tabs.multiselect", true],
+ ],
+ });
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+ },
+
+ background: async function() {
+ async function testHighlighted(activeIndex, highlightedIndices) {
+ let tabs = await browser.tabs.query({currentWindow: true});
+ for (let {index, active, highlighted} of tabs) {
+ browser.test.assertEq(index == activeIndex, active, "Check Tab.active: " + index);
+ let expected = highlightedIndices.includes(index) || index == activeIndex;
+ browser.test.assertEq(expected, highlighted, "Check Tab.highlighted: " + index);
+ }
+ }
+
+ browser.test.log("Check that last tab is active, and no other is highlighted");
+ await testHighlighted(2, []);
+
+ browser.test.log("Highlight first and second tabs");
+ await browser.tabs.highlight({tabs: [0, 1]});
+ await testHighlighted(0, [1]);
+
+ browser.test.log("Highlight second and first tabs");
+ await browser.tabs.highlight({tabs: [1, 0]});
+ await testHighlighted(1, [0]);
+
+ browser.test.log("Test that highlight fails for invalid data");
+ await browser.test.assertRejects(
+ browser.tabs.highlight({tabs: []}),
+ /No highlighted tab/,
+ "Attempt to highlight no tab should throw");
+ await browser.test.assertRejects(
+ browser.tabs.highlight({windowId: 999999999, tabs: 0}),
+ /Invalid window ID: 999999999/,
+ "Attempt to highlight tabs in invalid window should throw");
+ await browser.test.assertRejects(
+ browser.tabs.highlight({tabs: 999999999}),
+ /No tab at index: 999999999/,
+ "Attempt to highlight invalid tab index should throw");
+ await browser.test.assertRejects(
+ browser.tabs.highlight({tabs: [2, 999999999]}),
+ /No tab at index: 999999999/,
+ "Attempt to highlight invalid tab index should throw");
+
+ browser.test.log("Highlighted tabs shouldn't be affected by failures above");
+ await testHighlighted(1, [0]);
+
+ browser.test.log("Highlight last tab");
+ let window = await browser.tabs.highlight({tabs: 2});
+ await testHighlighted(2, []);
+
+ browser.test.assertEq(3, window.tabs.length, "Returned window should be populated");
+
+ browser.test.notifyPass("test-finished");
+ },
+ });
+
+ let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
+ let tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
+
+ await extension.startup();
+ await extension.awaitFinish("test-finished");
+ await extension.unload();
+
+ BrowserTestUtils.removeTab(tab1);
+ BrowserTestUtils.removeTab(tab2);
+});
+
--- a/mobile/android/components/extensions/ext-utils.js
+++ b/mobile/android/components/extensions/ext-utils.js
@@ -515,16 +515,20 @@ class Tab extends TabBase {
// all the active tabs.
if (tabTracker.extensionPopupTab === this.nativeTab) {
return false;
}
}
return this.nativeTab.getActive();
}
+ get highlighted() {
+ return this.active;
+ }
+
get selected() {
return this.nativeTab.getActive();
}
get status() {
if (this.browser.webProgress.isLoadingDocument) {
return "loading";
}
--- a/toolkit/components/extensions/parent/ext-tabs-base.js
+++ b/toolkit/components/extensions/parent/ext-tabs-base.js
@@ -395,22 +395,22 @@ class TabBase {
* @abstract
*/
get active() {
throw new Error("Not implemented");
}
/**
* @property {boolean} highlighted
- * Alias for `active`.
+ * Returns true if the tab is highlighted.
* @readonly
* @abstract
*/
get highlighted() {
- return this.active;
+ throw new Error("Not implemented");
}
/**
* @property {boolean} selected
* An alias for `active`.
* @readonly
* @abstract
*/