Bug 1465170 - Implement support for the 'highlighted' API for multiselect tabs with tabs.query
MozReview-Commit-ID: 6eFnxrXJXXB
--- a/browser/base/content/tabbrowser.js
+++ b/browser/base/content/tabbrowser.js
@@ -2608,40 +2608,38 @@ window._gBrowser = {
return tabsToEnd;
},
removeTabsToTheEndFrom(aTab) {
if (!this.warnAboutClosingTabs(this.closingTabsEnum.TO_END, aTab))
return;
let tabs = this.getTabsToTheEndFrom(aTab);
- this.removeCollectionOfTabs(tabs);
+ this.removeTabs(tabs);
},
removeAllTabsBut(aTab) {
if (!this.warnAboutClosingTabs(this.closingTabsEnum.OTHER)) {
return;
}
let tabs = this.visibleTabs.filter(tab => tab != aTab && !tab.pinned);
this.selectedTab = aTab;
- this.removeCollectionOfTabs(tabs);
+ this.removeTabs(tabs);
},
removeMultiSelectedTabs() {
if (!this.warnAboutClosingTabs(this.closingTabsEnum.MULTI_SELECTED)) {
return;
}
- let selectedTabs = ChromeUtils.nondeterministicGetWeakSetKeys(this._multiSelectedTabsSet)
- .filter(tab => tab.isConnected);
- this.removeCollectionOfTabs(selectedTabs);
+ this.removeTabs(this.selectedTabs);
},
- removeCollectionOfTabs(tabs) {
+ removeTabs(tabs) {
let tabsWithBeforeUnload = [];
let lastToClose;
let aParams = {animation: true};
for (let tab of tabs) {
if (tab.selected)
lastToClose = tab;
else if (this._hasBeforeUnload(tab))
tabsWithBeforeUnload.push(tab);
@@ -3674,28 +3672,35 @@ window._gBrowser = {
return;
}
aTab.removeAttribute("multiselected");
this.tabContainer._setPositionalAttributes();
this._multiSelectedTabsSet.delete(aTab);
},
clearMultiSelectedTabs(updatePositionalAttributes) {
- const selectedTabs = ChromeUtils.nondeterministicGetWeakSetKeys(this._multiSelectedTabsSet);
- for (let tab of selectedTabs) {
- if (tab.isConnected && tab.multiselected) {
- tab.removeAttribute("multiselected");
- }
+ for (let tab of this.selectedTabs) {
+ tab.removeAttribute("multiselected");
}
this._multiSelectedTabsSet = new WeakSet();
if (updatePositionalAttributes) {
this.tabContainer._setPositionalAttributes();
}
},
+ get selectedTabs() {
+ let {selectedTab, _multiSelectedTabsSet} = this;
+ let tabs = ChromeUtils.nondeterministicGetWeakSetKeys(_multiSelectedTabsSet)
+ .filter(tab => tab.isConnected && !tab.closing);
+ if (!_multiSelectedTabsSet.has(selectedTab)) {
+ tabs.push(selectedTab);
+ }
+ return tabs;
+ },
+
get multiSelectedTabsCount() {
return ChromeUtils.nondeterministicGetWeakSetKeys(this._multiSelectedTabsSet)
.filter(tab => tab.isConnected && !tab.closing)
.length;
},
get lastMultiSelectedTab() {
let tab = this._lastMultiSelectedTabRef ? this._lastMultiSelectedTabRef.get() : null;
@@ -3705,27 +3710,24 @@ window._gBrowser = {
return gBrowser.selectedTab;
},
set lastMultiSelectedTab(aTab) {
this._lastMultiSelectedTabRef = Cu.getWeakReference(aTab);
},
toggleMuteAudioOnMultiSelectedTabs(aTab) {
- const selectedTabs = ChromeUtils.nondeterministicGetWeakSetKeys(this._multiSelectedTabsSet)
- .filter(tab => tab.isConnected);
let tabsToToggle;
-
if (aTab.activeMediaBlocked) {
- tabsToToggle = selectedTabs.filter(tab =>
+ tabsToToggle = this.selectedTabs.filter(tab =>
tab.activeMediaBlocked || tab.linkedBrowser.audioMuted
);
} else {
let tabMuted = aTab.linkedBrowser.audioMuted;
- tabsToToggle = selectedTabs.filter(tab =>
+ tabsToToggle = this.selectedTabs.filter(tab =>
// When a user is looking to mute selected tabs, then media-blocked tabs
// should not be toggled. Otherwise those media-blocked tabs are going into a
// playing and unmuted state.
tab.linkedBrowser.audioMuted == tabMuted && !tab.activeMediaBlocked ||
tab.activeMediaBlocked && tabMuted
);
}
for (let tab of tabsToToggle) {
--- a/browser/components/extensions/parent/ext-browser.js
+++ b/browser/components/extensions/parent/ext-browser.js
@@ -933,16 +933,23 @@ class Window extends WindowBase {
let {tabManager} = this.extension;
for (let nativeTab of this.window.gBrowser.tabs) {
yield tabManager.getWrapper(nativeTab);
}
}
+ * getHighlightedTabs() {
+ let {tabManager} = this.extension;
+ for (let tab of this.window.gBrowser.selectedTabs) {
+ yield tabManager.getWrapper(tab);
+ }
+ }
+
get activeTab() {
let {tabManager} = this.extension;
// A new window is being opened and it is adopting a tab, and we do not create
// a TabWrapper for the tab being adopted because it will go away once the tab
// adoption has been completed (See Bug 1458918 for rationale).
if (this.window.gBrowserInit.isAdoptingTab()) {
return null;
--- a/browser/components/extensions/test/browser/browser_ext_tabs_highlight.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_highlight.js
@@ -18,16 +18,21 @@ add_task(async function test_highlighted
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);
}
+ let highlightedTabs = await browser.tabs.query({currentWindow: true, highlighted: true});
+ browser.test.assertEq(
+ highlightedIndices.concat(activeIndex).sort((a, b) => a - b).join(),
+ highlightedTabs.map(tab => tab.index).sort((a, b) => a - b).join(),
+ "Check tabs.query with highlighted:true provides the expected tabs");
}
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]);
--- a/mobile/android/components/extensions/ext-utils.js
+++ b/mobile/android/components/extensions/ext-utils.js
@@ -662,16 +662,20 @@ class Window extends WindowBase {
* getTabs() {
let {tabManager} = this.extension;
for (let nativeTab of this.window.BrowserApp.tabs) {
yield tabManager.getWrapper(nativeTab);
}
}
+ * getHighlightedTabs() {
+ yield this.activeTab;
+ }
+
get activeTab() {
let {BrowserApp} = this.window;
let {selectedTab} = BrowserApp;
// If the current tab is an extension popup tab, we use the parentId to retrieve
// and return the tab that was selected when the popup tab has been opened.
if (selectedTab === tabTracker.extensionPopupTab) {
selectedTab = BrowserApp.getTabForId(selectedTab.parentId);
--- a/toolkit/components/extensions/parent/ext-tabs-base.js
+++ b/toolkit/components/extensions/parent/ext-tabs-base.js
@@ -1044,16 +1044,25 @@ class WindowBase {
*
* @returns {Iterator<TabBase>}
*/
getTabs() {
throw new Error("Not implemented");
}
/**
+ * Returns an iterator of TabBase objects for each highlighted tab in this window.
+ *
+ * @returns {Iterator<TabBase>}
+ */
+ getHighlightedTabs() {
+ throw new Error("Not implemented");
+ }
+
+ /**
* @property {TabBase} The window's currently active tab.
*/
get activeTab() {
throw new Error("Not implemented");
}
/**
* Returns the window's tab at the specified index.
@@ -1838,27 +1847,31 @@ class TabManagerBase {
* Used to determine the current window for relevant properties.
*
* @returns {Iterator<TabBase>}
*/
* query(queryInfo = null, context = null) {
function* candidates(windowWrapper) {
if (queryInfo) {
let {active, highlighted, index} = queryInfo;
- if (active === true || highlighted === true) {
+ if (active === true) {
yield windowWrapper.activeTab;
return;
}
if (index != null) {
let tabWrapper = windowWrapper.getTabAtIndex(index);
if (tabWrapper) {
yield tabWrapper;
}
return;
}
+ if (highlighted === true) {
+ yield* windowWrapper.getHighlightedTabs();
+ return;
+ }
}
yield* windowWrapper.getTabs();
}
let windowWrappers = this.extension.windowManager.query(queryInfo, context);
for (let windowWrapper of windowWrappers) {
for (let tabWrapper of candidates(windowWrapper)) {
if (!queryInfo || tabWrapper.matches(queryInfo)) {
yield tabWrapper;