Bug 1451176: Use Tab wrapper as context object for TabContext. r?mixedpuppy draft
authorKris Maglione <maglione.k@gmail.com>
Sat, 14 Apr 2018 13:13:52 -0700
changeset 782214 6ed8bff21c6da57eab6c3c228cacfaf5680f724e
parent 782208 c23d9c9be5edda4da192c3e3d5c34f1dea90c5d1
child 782215 303f6f4f784f91a32ad06b97e52caf5823bd1338
push id106501
push usermaglione.k@gmail.com
push dateSat, 14 Apr 2018 21:50:15 +0000
reviewersmixedpuppy
bugs1451176
milestone61.0a1
Bug 1451176: Use Tab wrapper as context object for TabContext. r?mixedpuppy This allows us to automatically keep the same context data when tabs are moved between windows. MozReview-Commit-ID: HLliAEsZA1H
browser/components/extensions/parent/ext-browser.js
toolkit/components/extensions/parent/ext-tabs-base.js
--- a/browser/components/extensions/parent/ext-browser.js
+++ b/browser/components/extensions/parent/ext-browser.js
@@ -1,16 +1,18 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 // This file provides some useful code for the |tabs| and |windows|
 // modules. All of the code is installed on |global|, which is a scope
 // shared among the different ext-*.js scripts.
 
+ChromeUtils.defineModuleGetter(this, "ExtensionParent",
+                               "resource://gre/modules/ExtensionParent.jsm");
 ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
                                "resource://gre/modules/PrivateBrowsingUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "RecentWindow",
                                "resource:///modules/RecentWindow.jsm");
 
 var {
   ExtensionError,
   defineLazyGetter,
@@ -109,41 +111,43 @@ global.TabContext = class extends EventE
 
     this.tabData = new WeakMap();
 
     windowTracker.addListener("progress", this);
     windowTracker.addListener("TabSelect", this);
   }
 
   get(nativeTab) {
-    if (!this.tabData.has(nativeTab)) {
-      this.tabData.set(nativeTab, this.getDefaults(nativeTab));
+    let tab = this.extension.tabManager.getWrapper(nativeTab);
+    if (!this.tabData.has(tab)) {
+      this.tabData.set(tab, this.getDefaults(nativeTab));
     }
 
-    return this.tabData.get(nativeTab);
+    return this.tabData.get(tab);
   }
 
   clear(nativeTab) {
-    this.tabData.delete(nativeTab);
+    let tab = this.extension.tabManager.getWrapper(nativeTab);
+    this.tabData.delete(tab);
   }
 
   handleEvent(event) {
     if (event.type == "TabSelect") {
       let nativeTab = event.target;
       this.emit("tab-select", nativeTab);
       this.emit("location-change", nativeTab);
     }
   }
 
   onLocationChange(browser, webProgress, request, locationURI, flags) {
     let gBrowser = browser.ownerGlobal.gBrowser;
-    let tab = gBrowser.getTabForBrowser(browser);
+    let nativeTab = gBrowser.getTabForBrowser(browser);
     // fromBrowse will be false in case of e.g. a hash change or history.pushState
     let fromBrowse = !(flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT);
-    this.emit("location-change", tab, fromBrowse);
+    this.emit("location-change", nativeTab, fromBrowse);
   }
 
   shutdown() {
     windowTracker.removeListener("progress", this);
     windowTracker.removeListener("TabSelect", this);
   }
 };
 
@@ -237,16 +241,23 @@ class TabTracker extends TabTrackerBase 
   setId(nativeTab, id) {
     this._tabs.set(nativeTab, id);
     if (nativeTab.linkedBrowser) {
       this._browsers.set(nativeTab.linkedBrowser, id);
     }
     this._tabIds.set(id, nativeTab);
   }
 
+  adopt(nativeTab, adoptedBy) {
+    this.setId(nativeTab, this.getId(adoptedBy));
+    for (let extension of ExtensionParent.GlobalManager.extensionMap.values()) {
+      extension.tabManager.adopt(nativeTab, adoptedBy);
+    }
+  }
+
   _handleTabDestroyed(event, {nativeTab}) {
     let id = this._tabs.get(nativeTab);
     if (id) {
       this._tabs.delete(nativeTab);
       if (this._tabIds.get(id) === nativeTab) {
         this._tabIds.delete(id);
       }
     }
@@ -300,17 +311,17 @@ class TabTracker extends TabTrackerBase 
     switch (event.type) {
       case "TabOpen":
         let {adoptedTab} = event.detail;
         if (adoptedTab) {
           this.adoptedTabs.set(adoptedTab, event.target);
 
           // This tab is being created to adopt a tab from a different window.
           // Copy the ID from the old tab to the new.
-          this.setId(nativeTab, this.getId(adoptedTab));
+          this.adopt(nativeTab, adoptedTab);
 
           adoptedTab.linkedBrowser.messageManager.sendAsyncMessage("Extension:SetFrameData", {
             windowId: windowTracker.getId(nativeTab.ownerGlobal),
           });
         }
 
         // Save the current tab, since the newly-created tab will likely be
         // active by the time the promise below resolves and the event is
@@ -330,17 +341,17 @@ class TabTracker extends TabTrackerBase 
 
       case "TabClose":
         let {adoptedBy} = event.detail;
         if (adoptedBy) {
           // This tab is being closed because it was adopted by a new window.
           // Copy its ID to the new tab, in case it was created as the first tab
           // of a new window, and did not have an `adoptedTab` detail when it was
           // opened.
-          this.setId(adoptedBy, this.getId(nativeTab));
+          this.adopt(adoptedBy, nativeTab);
 
           this.emitDetached(nativeTab, adoptedBy);
         } else {
           this.emitRemoved(nativeTab, false);
         }
         break;
 
       case "TabSelect":
@@ -386,17 +397,17 @@ class TabTracker extends TabTrackerBase 
       // delayed startup code in browser.js, which is currently triggered
       // by the first MozAfterPaint event. That code handles finally
       // adopting the tab, and clears it from the arguments list in the
       // process, so if we run later than it, we're too late.
       let nativeTab = window.arguments[0];
       let adoptedBy = window.gBrowser.tabs[0];
 
       this.adoptedTabs.set(nativeTab, adoptedBy);
-      this.setId(adoptedBy, this.getId(nativeTab));
+      this.adopt(adoptedBy, nativeTab);
 
       // We need to be sure to fire this event after the onDetached event
       // for the original tab.
       let listener = (event, details) => {
         if (details.nativeTab === nativeTab) {
           this.off("tab-detached", listener);
 
           Promise.resolve().then(() => {
--- a/toolkit/components/extensions/parent/ext-tabs-base.js
+++ b/toolkit/components/extensions/parent/ext-tabs-base.js
@@ -1790,16 +1790,22 @@ class TabManagerBase {
    *
    * @returns {TabBase}
    *        The wrapper for this tab.
    */
   getWrapper(nativeTab) {
     return this._tabs.get(nativeTab);
   }
 
+  adopt(nativeTab, adoptedBy) {
+    let wrapper = this.getWrapper(adoptedBy);
+    wrapper.nativeTab = nativeTab;
+    this._tabs.set(nativeTab, wrapper);
+  }
+
   /**
    * Converts the given native tab to a JSON-compatible object, in the format
    * required to be returned by WebExtension APIs, which may be safely passed to
    * extension code.
    *
    * @param {NativeTab} nativeTab
    *        The native tab to convert.
    * @param {NativeTab} [fallbackTab]