Bug 1465170 - Implement support for the 'highlighted' API for multiselect tabs with tabs.query r=jaws,mixedpuppy
authorOriol Brufau <oriol-bugzilla@hotmail.com>
Sun, 03 Jun 2018 02:53:59 +0200
changeset 423969 5ac6067017a84f2d533d965f587acfd829d2575b
parent 423968 6a6418171ceef49a79d6952c0d71d082054fc6eb
child 423970 a4a9b4cc6cb512d7f87f79263ff2fe8bceff8413
push id34197
push usercsabou@mozilla.com
push dateThu, 28 Jun 2018 09:44:02 +0000
treeherdermozilla-central@db455160668d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjaws, mixedpuppy
bugs1465170
milestone63.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1465170 - Implement support for the 'highlighted' API for multiselect tabs with tabs.query r=jaws,mixedpuppy MozReview-Commit-ID: 6eFnxrXJXXB
browser/base/content/tabbrowser.js
browser/components/extensions/parent/ext-browser.js
browser/components/extensions/test/browser/browser_ext_tabs_highlight.js
mobile/android/components/extensions/ext-utils.js
toolkit/components/extensions/parent/ext-tabs-base.js
--- 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;