Bug 1436720 use sessionstore to track controlling extension, r?Gijs,rpl draft
authorShane Caraveo <scaraveo@mozilla.com>
Fri, 09 Feb 2018 15:19:42 -0700
changeset 753305 fb67923bfb310a8df358421ec3e7b6eed4c3c4d1
parent 752511 0ac953fcddf10132eaecdb753d72b2ba5a43c32a
push id98543
push usermixedpuppy@gmail.com
push dateFri, 09 Feb 2018 22:20:15 +0000
reviewersGijs, rpl
bugs1436720
milestone60.0a1
Bug 1436720 use sessionstore to track controlling extension, r?Gijs,rpl MozReview-Commit-ID: Ekk1I20aryO
browser/base/content/tabbrowser.xml
browser/components/extensions/ext-tabs.js
browser/components/extensions/test/browser/browser_ext_tabs_hide.js
browser/components/sessionstore/SessionStore.jsm
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -3886,37 +3886,42 @@
 
             this.tabContainer._updateCloseButtons();
 
             this.tabContainer._setPositionalAttributes();
 
             let event = document.createEvent("Events");
             event.initEvent("TabShow", true, false);
             aTab.dispatchEvent(event);
+            SessionStore.deleteTabValue(aTab, "hiddenBy");
           }
         ]]>
         </body>
       </method>
 
       <method name="hideTab">
         <parameter name="aTab"/>
+        <parameter name="aSource"/>
         <body>
         <![CDATA[
           if (!aTab.hidden && !aTab.pinned && !aTab.selected &&
               !aTab.closing && !aTab._sharingState) {
             aTab.setAttribute("hidden", "true");
             this._visibleTabs = null; // invalidate cache
 
             this.tabContainer._updateCloseButtons();
 
             this.tabContainer._setPositionalAttributes();
 
             let event = document.createEvent("Events");
             event.initEvent("TabHide", true, false);
             aTab.dispatchEvent(event);
+            if (aSource) {
+              SessionStore.setTabValue(aTab, "hiddenBy", aSource);
+            }
           }
         ]]>
         </body>
       </method>
 
       <method name="selectTabAtIndex">
         <parameter name="aIndex"/>
         <parameter name="aEvent"/>
--- a/browser/components/extensions/ext-tabs.js
+++ b/browser/components/extensions/ext-tabs.js
@@ -6,30 +6,29 @@
 /* import-globals-from ext-browser.js */
 
 ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
                                "resource://gre/modules/PrivateBrowsingUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "PromiseUtils",
                                "resource://gre/modules/PromiseUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "Services",
                                "resource://gre/modules/Services.jsm");
+ChromeUtils.defineModuleGetter(this, "SessionStore",
+                               "resource:///modules/sessionstore/SessionStore.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "strBundle", function() {
   return Services.strings.createBundle("chrome://global/locale/extensions.properties");
 });
 
 var {
   ExtensionError,
 } = ExtensionUtils;
 
 const TABHIDE_PREFNAME = "extensions.webextensions.tabhide.enabled";
 
-// WeakMap[Tab -> ExtensionID]
-let hiddenTabs = new WeakMap();
-
 let tabListener = {
   tabReadyInitialized: false,
   tabReadyPromises: new WeakMap(),
   initializingTabs: new WeakSet(),
 
   initTabReady() {
     if (!this.tabReadyInitialized) {
       windowTracker.addListener("progress", this);
@@ -88,21 +87,19 @@ this.tabs = class extends ExtensionAPI {
     }
     if (reason == "ADDON_DISABLE" ||
         reason == "ADDON_UNINSTALL") {
       // Show all hidden tabs if a tab managing extension is uninstalled or
       // disabled.  If a user has more than one, the extensions will need to
       // self-manage re-hiding tabs.
       for (let tab of this.extension.tabManager.query()) {
         let nativeTab = tabTracker.getTab(tab.id);
-        if (hiddenTabs.get(nativeTab) === this.extension.id) {
-          hiddenTabs.delete(nativeTab);
-          if (nativeTab.ownerGlobal) {
-            nativeTab.ownerGlobal.gBrowser.showTab(nativeTab);
-          }
+        if (nativeTab.hidden && nativeTab.ownerGlobal &&
+            SessionStore.getTabValue(nativeTab, "hiddenBy") === this.extension.id) {
+          nativeTab.ownerGlobal.gBrowser.showTab(nativeTab);
         }
       }
     }
   }
 
   getAPI(context) {
     let {extension} = context;
 
@@ -296,18 +293,16 @@ this.tabs = class extends ExtensionAPI {
               needed.push("pinned");
             } else if (event.type == "TabBrowserInserted" &&
                        !event.detail.insertedOnTabCreation) {
               needed.push("discarded");
             } else if (event.type == "TabBrowserDiscarded") {
               needed.push("discarded");
             } else if (event.type == "TabShow") {
               needed.push("hidden");
-              // Always remove the tab from the hiddenTabs map.
-              hiddenTabs.delete(event.originalTarget);
             } else if (event.type == "TabHide") {
               needed.push("hidden");
             }
 
             let tab = tabManager.getWrapper(event.originalTarget);
             let changeInfo = {};
             for (let prop of needed) {
               changeInfo[prop] = tab[prop];
@@ -1059,17 +1054,16 @@ this.tabs = class extends ExtensionAPI {
 
           if (!Array.isArray(tabIds)) {
             tabIds = [tabIds];
           }
 
           for (let tabId of tabIds) {
             let tab = tabTracker.getTab(tabId);
             if (tab.ownerGlobal) {
-              hiddenTabs.delete(tab);
               tab.ownerGlobal.gBrowser.showTab(tab);
             }
           }
         },
 
         hide(tabIds) {
           if (!Services.prefs.getBoolPref(TABHIDE_PREFNAME, false)) {
             throw new ExtensionError(`tabs.hide is currently experimental and must be enabled with the ${TABHIDE_PREFNAME} preference.`);
@@ -1078,19 +1072,18 @@ this.tabs = class extends ExtensionAPI {
           if (!Array.isArray(tabIds)) {
             tabIds = [tabIds];
           }
 
           let hidden = [];
           let tabs = tabIds.map(tabId => tabTracker.getTab(tabId));
           for (let tab of tabs) {
             if (tab.ownerGlobal && !tab.hidden) {
-              tab.ownerGlobal.gBrowser.hideTab(tab);
+              tab.ownerGlobal.gBrowser.hideTab(tab, extension.id);
               if (tab.hidden) {
-                hiddenTabs.set(tab, extension.id);
                 hidden.push(tabTracker.getId(tab));
               }
             }
           }
           return hidden;
         },
       },
     };
--- a/browser/components/extensions/test/browser/browser_ext_tabs_hide.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_hide.js
@@ -1,10 +1,14 @@
 "use strict";
 
+ChromeUtils.defineModuleGetter(this, "SessionStore",
+                               "resource:///modules/sessionstore/SessionStore.jsm");
+ChromeUtils.defineModuleGetter(this, "TabStateFlusher",
+                               "resource:///modules/sessionstore/TabStateFlusher.jsm");
 const {Utils} = ChromeUtils.import("resource://gre/modules/sessionstore/Utils.jsm", {});
 const triggeringPrincipal_base64 = Utils.SERIALIZED_SYSTEMPRINCIPAL;
 
 // Ensure the pref prevents API use when the extension has the tabHide permission.
 add_task(async function test_pref_disabled() {
   async function background() {
     let tabs = await browser.tabs.query({hidden: false});
     let ids = tabs.map(tab => tab.id);
@@ -111,19 +115,37 @@ add_task(async function test_tabs_showhi
   for (let win of BrowserWindowIterator()) {
     if (win != window) {
       otherwin = win;
     }
     let tabs = Array.from(win.gBrowser.tabs.values());
     ok(!tabs[0].hidden, "first tab not hidden");
     for (let i = 1; i < tabs.length; i++) {
       ok(tabs[i].hidden, "tab hidden value is correct");
+      let id = SessionStore.getTabValue(tabs[i], "hiddenBy");
+      is(id, extension.id, "tab hiddenby value is correct");
+      await TabStateFlusher.flush(tabs[i].linkedBrowser);
     }
   }
 
+  // Close the other window then restore it to test that the tabs are
+  // restored with proper hidden state, and the correct extension id.
+  await BrowserTestUtils.closeWindow(otherwin);
+
+  // restored = TestUtils.topicObserved("sessionstore-closed-objects-changed");
+  otherwin = SessionStore.undoCloseWindow(0);
+  await BrowserTestUtils.waitForEvent(otherwin, "load");
+  let tabs = Array.from(otherwin.gBrowser.tabs.values());
+  ok(!tabs[0].hidden, "first tab not hidden");
+  for (let i = 1; i < tabs.length; i++) {
+    ok(tabs[i].hidden, "tab hidden value is correct");
+    let id = SessionStore.getTabValue(tabs[i], "hiddenBy");
+    is(id, extension.id, "tab hiddenby value is correct");
+  }
+
   // Test closing the last visible tab, the next tab which is hidden should become
   // the selectedTab and will be visible.
   ok(!otherwin.gBrowser.selectedTab.hidden, "selected tab is not hidden");
   await BrowserTestUtils.removeTab(otherwin.gBrowser.selectedTab);
   ok(!otherwin.gBrowser.selectedTab.hidden, "tab was unhidden");
 
   // Showall will unhide any remaining hidden tabs.
   extension.sendMessage("showall");
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -3431,17 +3431,17 @@ var SessionStoreInternal = {
           tabbrowser.selectedTab = tab;
           tabbrowser.removeTab(leftoverTab);
         }
       }
 
       tabs.push(tab);
 
       if (tabData.hidden) {
-        tabbrowser.hideTab(tab);
+        tabbrowser.hideTab(tab, tabData.extData && tabData.extData.hiddenBy);
       }
 
       if (tabData.pinned) {
         tabbrowser.pinTab(tab);
       }
     }
 
     // Move the originally open tabs to the end.