Bug 1260548: Part 4 - Factor out tab status listener logic into shared module. r?aswan draft
authorKris Maglione <maglione.k@gmail.com>
Sat, 28 Jan 2017 20:00:24 -0800
changeset 467788 70a182894898860ee08f60944d61834de08f4b46
parent 467787 0f5a2803125c7006f9f41bba988a928ebfd842d3
child 467789 1ffa5569b4360e7c0a54050dc40f7a6a54c67375
push id43274
push usermaglione.k@gmail.com
push dateSun, 29 Jan 2017 20:10:11 +0000
reviewersaswan
bugs1260548
milestone54.0a1
Bug 1260548: Part 4 - Factor out tab status listener logic into shared module. r?aswan MozReview-Commit-ID: Ff9gLKdGQHX
browser/components/extensions/ext-tabs.js
toolkit/components/extensions/ExtensionTabs.jsm
--- a/browser/components/extensions/ext-tabs.js
+++ b/browser/components/extensions/ext-tabs.js
@@ -279,24 +279,20 @@ extensions.registerSchemaAPI("tabs", "ad
             if (extension.hasPermission("tabs") || !restricted.includes(prop)) {
               nonempty = true;
               result[prop] = changeInfo[prop];
             }
           }
           return [nonempty, result];
         }
 
-        let fireForBrowser = (browser, changed) => {
+        let fireForTab = (tab, changed) => {
           let [needed, changeInfo] = sanitize(extension, changed);
           if (needed) {
-            let gBrowser = browser.ownerGlobal.gBrowser;
-            let tabElem = gBrowser.getTabForBrowser(browser);
-
-            let tab = tabManager.convert(tabElem);
-            fire(tab.id, changeInfo, tab);
+            fire(tab.id, changeInfo, tab.convert());
           }
         };
 
         let listener = event => {
           let needed = [];
           if (event.type == "TabAttrModified") {
             let changed = event.detail.changed;
             if (changed.includes("image")) {
@@ -312,70 +308,45 @@ extensions.registerSchemaAPI("tabs", "ad
               needed.push("title");
             }
           } else if (event.type == "TabPinned") {
             needed.push("pinned");
           } else if (event.type == "TabUnpinned") {
             needed.push("pinned");
           }
 
-          if (needed.length && !extension.hasPermission("tabs")) {
-            needed = needed.filter(attr => !restricted.includes(attr));
+          let tab = tabManager.getWrapper(event.originalTarget);
+          let changeInfo = {};
+          for (let prop of needed) {
+            changeInfo[prop] = tab[prop];
           }
 
-          if (needed.length) {
-            let tab = tabManager.convert(event.originalTarget);
+          fireForTab(tab, changeInfo);
+        };
 
-            let changeInfo = {};
-            for (let prop of needed) {
-              changeInfo[prop] = tab[prop];
-            }
-            fire(tab.id, changeInfo, tab);
-          }
-        };
-        let progressListener = {
-          onStateChange(browser, webProgress, request, stateFlags, statusCode) {
-            if (!webProgress.isTopLevel) {
-              return;
+        let statusListener = ({browser, status, url}) => {
+          let {gBrowser} = browser.ownerGlobal;
+          let tabElem = gBrowser.getTabForBrowser(browser);
+          if (tabElem) {
+            let changed = {status};
+            if (url) {
+              changed.url = url;
             }
 
-            let status;
-            if (stateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) {
-              if (stateFlags & Ci.nsIWebProgressListener.STATE_START) {
-                status = "loading";
-              } else if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
-                status = "complete";
-              }
-            } else if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
-                       statusCode == Cr.NS_BINDING_ABORTED) {
-              status = "complete";
-            }
-
-            fireForBrowser(browser, {status});
-          },
-
-          onLocationChange(browser, webProgress, request, locationURI, flags) {
-            if (!webProgress.isTopLevel) {
-              return;
-            }
-
-            fireForBrowser(browser, {
-              status: webProgress.isLoadingDocument ? "loading" : "complete",
-              url: locationURI.spec,
-            });
-          },
+            fireForTab(tabManager.wrapTab(tabElem), changed);
+          }
         };
 
-        windowTracker.addListener("progress", progressListener);
+        windowTracker.addListener("status", statusListener);
         windowTracker.addListener("TabAttrModified", listener);
         windowTracker.addListener("TabPinned", listener);
         windowTracker.addListener("TabUnpinned", listener);
 
         return () => {
-          windowTracker.removeListener("progress", progressListener);
+          windowTracker.removeListener("status", statusListener);
           windowTracker.removeListener("TabAttrModified", listener);
           windowTracker.removeListener("TabPinned", listener);
           windowTracker.removeListener("TabUnpinned", listener);
         };
       }).api(),
 
       create(createProperties) {
         return new Promise((resolve, reject) => {
--- a/toolkit/components/extensions/ExtensionTabs.jsm
+++ b/toolkit/components/extensions/ExtensionTabs.jsm
@@ -305,27 +305,66 @@ class TabTrackerBase extends EventEmitte
     if (!this.initialized) {
       this.init();
     }
 
     return super.on(...args); // eslint-disable-line mozilla/balanced-listeners
   }
 }
 
+class StatusListener {
+  constructor(listener) {
+    this.listener = listener;
+  }
+
+  onStateChange(browser, webProgress, request, stateFlags, statusCode) {
+    if (!webProgress.isTopLevel) {
+      return;
+    }
+
+    let status;
+    if (stateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) {
+      if (stateFlags & Ci.nsIWebProgressListener.STATE_START) {
+        status = "loading";
+      } else if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
+        status = "complete";
+      }
+    } else if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
+               statusCode == Cr.NS_BINDING_ABORTED) {
+      status = "complete";
+    }
+
+    if (status) {
+      this.listener({browser, status});
+    }
+  }
+
+  onLocationChange(browser, webProgress, request, locationURI, flags) {
+    if (webProgress.isTopLevel) {
+      let status = webProgress.isLoadingDocument ? "loading" : "complete";
+      this.listener({browser, status, url: locationURI.spec});
+    }
+  }
+}
+
 class WindowTrackerBase extends EventEmitter {
   constructor() {
     super();
 
     this.handleWindowOpened = this.handleWindowOpened.bind(this);
 
     this._openListeners = new Set();
     this._closeListeners = new Set();
 
     this._listeners = new DefaultMap(() => new Set());
 
+    this._statusListeners = new DefaultWeakMap(listener => {
+      return new StatusListener(listener);
+    });
+
     this._windowIds = new DefaultWeakMap(window => {
       window.QueryInterface(Ci.nsIInterfaceRequestor);
 
       return window.getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
     });
   }
 
   isBrowserWindow(window) {
@@ -479,31 +518,41 @@ class WindowTrackerBase extends EventEmi
     } else if (type === "domwindowclosed") {
       return this.addCloseListener(listener);
     }
 
     if (this._listeners.size === 0) {
       this.addOpenListener(this.handleWindowOpened);
     }
 
+    if (type === "status") {
+      listener = this._statusListeners.get(listener);
+      type = "progress";
+    }
+
     this._listeners.get(type).add(listener);
 
     // Register listener on all existing windows.
     for (let window of this.browserWindows()) {
       this.addWindowListener(window, type, listener);
     }
   }
 
   removeListener(eventType, listener) {
     if (eventType === "domwindowopened") {
       return this.removeOpenListener(listener);
     } else if (eventType === "domwindowclosed") {
       return this.removeCloseListener(listener);
     }
 
+    if (eventType === "status") {
+      listener = this._statusListeners.get(listener);
+      eventType = "progress";
+    }
+
     let listeners = this._listeners.get(eventType);
     listeners.delete(listener);
 
     if (listeners.size === 0) {
       this._listeners.delete(eventType);
       if (this._listeners.size === 0) {
         this.removeOpenListener(this.handleWindowOpened);
       }