Bug 1260548: Part 2 - Factor out the excuteScript/insertCSS logic. r?aswan draft
authorKris Maglione <maglione.k@gmail.com>
Sat, 28 Jan 2017 16:56:30 -0800
changeset 467786 676df4b7af61e1495d31b09f0c85776412d55f04
parent 467785 deca6e126f817ab863c470b51a52835342345074
child 467787 0f5a2803125c7006f9f41bba988a928ebfd842d3
push id43274
push usermaglione.k@gmail.com
push dateSun, 29 Jan 2017 20:10:11 +0000
reviewersaswan
bugs1260548
milestone54.0a1
Bug 1260548: Part 2 - Factor out the excuteScript/insertCSS logic. r?aswan MozReview-Commit-ID: 8FxlX7MKZsN
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
@@ -88,17 +88,17 @@ let tabListener = {
       windowTracker.addListener("progress", this);
 
       this.tabReadyInitialized = true;
     }
   },
 
   onLocationChange(browser, webProgress, request, locationURI, flags) {
     if (webProgress.isTopLevel) {
-      let gBrowser = browser.ownerGlobal.gBrowser;
+      let {gBrowser} = browser.ownerGlobal;
       let tab = gBrowser.getTabForBrowser(browser);
 
       // Now we are certain that the first page in the tab was loaded.
       this.initializingTabs.delete(tab);
 
       // browser.innerWindowID is now set, resolve the promises if any.
       let deferred = this.tabReadyPromises.get(tab);
       if (deferred) {
@@ -116,17 +116,18 @@ let tabListener = {
    *
    * @param {XULElement} tab The <tab> element.
    * @returns {Promise} Resolves with the given tab once ready.
    */
   awaitTabReady(tab) {
     let deferred = this.tabReadyPromises.get(tab);
     if (!deferred) {
       deferred = PromiseUtils.defer();
-      if (!this.initializingTabs.has(tab) && tab.linkedBrowser.innerWindowID) {
+      if (!this.initializingTabs.has(tab) && (tab.linkedBrowser.innerWindowID ||
+                                              tab.linkedBrowser.currentURI.spec === "about:blank")) {
         deferred.resolve(tab);
       } else {
         this.initTabReady();
         this.tabReadyPromises.set(tab, deferred);
       }
     }
     return deferred.promise;
   },
@@ -139,16 +140,29 @@ extensions.registerSchemaAPI("tabs", "ad
 
   function getTabOrActive(tabId) {
     if (tabId !== null) {
       return tabTracker.getTab(tabId);
     }
     return tabTracker.activeTab;
   }
 
+  async function promiseTabOrActive(tabId) {
+    let tab;
+    if (tabId !== null) {
+      tab = tabManager.get(tabId);
+    } else {
+      tab = tabManager.getWrapper(tabTracker.activeTab);
+    }
+
+    await tabListener.awaitTabReady(tab.tab);
+
+    return tab;
+  }
+
   let self = {
     tabs: {
       onActivated: new WindowEventManager(context, "tabs.onActivated", "TabSelect", (fire, event) => {
         let tab = event.originalTarget;
         let tabId = tabTracker.getId(tab);
         let windowId = windowTracker.getId(tab.ownerGlobal);
         fire({tabId, windowId});
       }).api(),
@@ -439,17 +453,17 @@ extensions.registerSchemaAPI("tabs", "ad
           if (createProperties.index !== null) {
             window.gBrowser.moveTabTo(tab, createProperties.index);
           }
 
           if (createProperties.pinned) {
             window.gBrowser.pinTab(tab);
           }
 
-          if (createProperties.url && !createProperties.url.startsWith("about:")) {
+          if (createProperties.url && createProperties.url !== window.BROWSER_NEW_TAB_URL) {
             // We can't wait for a location change event for about:newtab,
             // since it may be pre-rendered, in which case its initial
             // location change event has already fired.
 
             // Mark the tab as initializing, so that operations like
             // `executeScript` wait until the requested URL is loaded in
             // the tab before dispatching messages to the inner window
             // that contains the URL we're attempting to load.
@@ -592,93 +606,32 @@ extensions.registerSchemaAPI("tabs", "ad
           let browser = tab.linkedBrowser;
           let recipient = {innerWindowID: browser.innerWindowID};
 
           return context.sendMessage(browser.messageManager, "Extension:DetectLanguage",
                                      {}, {recipient});
         });
       },
 
-      // Used to executeScript, insertCSS and removeCSS.
-      _execute: function(tabId, details, kind, method) {
-        let tab = getTabOrActive(tabId);
-
-        let options = {
-          js: [],
-          css: [],
-          remove_css: method == "removeCSS",
-        };
-
-        // We require a `code` or a `file` property, but we can't accept both.
-        if ((details.code === null) == (details.file === null)) {
-          return Promise.reject({message: `${method} requires either a 'code' or a 'file' property, but not both`});
-        }
-
-        if (details.frameId !== null && details.allFrames) {
-          return Promise.reject({message: `'frameId' and 'allFrames' are mutually exclusive`});
-        }
-
-        if (tabManager.hasActiveTabPermission(tab)) {
-          // If we have the "activeTab" permission for this tab, ignore
-          // the host whitelist.
-          options.matchesHost = ["<all_urls>"];
-        } else {
-          options.matchesHost = extension.whiteListedHosts.serialize();
-        }
+      async executeScript(tabId, details) {
+        let tab = await promiseTabOrActive(tabId);
 
-        if (details.code !== null) {
-          options[kind + "Code"] = details.code;
-        }
-        if (details.file !== null) {
-          let url = context.uri.resolve(details.file);
-          if (!extension.isExtensionURL(url)) {
-            return Promise.reject({message: "Files to be injected must be within the extension"});
-          }
-          options[kind].push(url);
-        }
-        if (details.allFrames) {
-          options.all_frames = details.allFrames;
-        }
-        if (details.frameId !== null) {
-          options.frame_id = details.frameId;
-        }
-        if (details.matchAboutBlank) {
-          options.match_about_blank = details.matchAboutBlank;
-        }
-        if (details.runAt !== null) {
-          options.run_at = details.runAt;
-        } else {
-          options.run_at = "document_idle";
-        }
-        if (details.cssOrigin !== null) {
-          options.css_origin = details.cssOrigin;
-        } else {
-          options.css_origin = "author";
-        }
-
-        return tabListener.awaitTabReady(tab).then(() => {
-          let browser = tab.linkedBrowser;
-          let recipient = {
-            innerWindowID: browser.innerWindowID,
-          };
-
-          return context.sendMessage(browser.messageManager, "Extension:Execute", {options}, {recipient});
-        });
+        return tab.executeScript(context, details);
       },
 
-      executeScript: function(tabId, details) {
-        return self.tabs._execute(tabId, details, "js", "executeScript");
+      async insertCSS(tabId, details) {
+        let tab = await promiseTabOrActive(tabId);
+
+        return tab.insertCSS(context, details);
       },
 
-      insertCSS: function(tabId, details) {
-        return self.tabs._execute(tabId, details, "css", "insertCSS").then(() => {});
-      },
+      async removeCSS(tabId, details) {
+        let tab = await promiseTabOrActive(tabId);
 
-      removeCSS: function(tabId, details) {
-        return self.tabs._execute(tabId, details, "css", "removeCSS").then(() => {});
+        return tab.removeCSS(context, details);
       },
 
       async move(tabIds, moveProperties) {
         let index = moveProperties.index;
         let tabsMoved = [];
         if (!Array.isArray(tabIds)) {
           tabIds = [tabIds];
         }
--- a/toolkit/components/extensions/ExtensionTabs.jsm
+++ b/toolkit/components/extensions/ExtensionTabs.jsm
@@ -134,16 +134,90 @@ class TabBase {
         if (val) {
           result[prop] = val;
         }
       }
     }
 
     return result;
   }
+
+  _execute(context, details, kind, method) {
+    let options = {
+      js: [],
+      css: [],
+      remove_css: method == "removeCSS",
+    };
+
+    // We require a `code` or a `file` property, but we can't accept both.
+    if ((details.code === null) == (details.file === null)) {
+      return Promise.reject({message: `${method} requires either a 'code' or a 'file' property, but not both`});
+    }
+
+    if (details.frameId !== null && details.allFrames) {
+      return Promise.reject({message: `'frameId' and 'allFrames' are mutually exclusive`});
+    }
+
+    if (this.hasActiveTabPermission) {
+      // If we have the "activeTab" permission for this tab, ignore
+      // the host whitelist.
+      options.matchesHost = ["<all_urls>"];
+    } else {
+      options.matchesHost = this.extension.whiteListedHosts.serialize();
+    }
+
+    if (details.code !== null) {
+      options[`${kind}Code`] = details.code;
+    }
+    if (details.file !== null) {
+      let url = context.uri.resolve(details.file);
+      if (!this.extension.isExtensionURL(url)) {
+        return Promise.reject({message: "Files to be injected must be within the extension"});
+      }
+      options[kind].push(url);
+    }
+    if (details.allFrames) {
+      options.all_frames = details.allFrames;
+    }
+    if (details.frameId !== null) {
+      options.frame_id = details.frameId;
+    }
+    if (details.matchAboutBlank) {
+      options.match_about_blank = details.matchAboutBlank;
+    }
+    if (details.runAt !== null) {
+      options.run_at = details.runAt;
+    } else {
+      options.run_at = "document_idle";
+    }
+    if (details.cssOrigin !== null) {
+      options.css_origin = details.cssOrigin;
+    } else {
+      options.css_origin = "author";
+    }
+
+    let {browser} = this;
+    let recipient = {
+      innerWindowID: browser.innerWindowID,
+    };
+
+    return context.sendMessage(browser.messageManager, "Extension:Execute", {options}, {recipient});
+  }
+
+  executeScript(context, details) {
+    return this._execute(context, details, "js", "executeScript");
+  }
+
+  insertCSS(context, details) {
+    return this._execute(context, details, "css", "insertCSS").then(() => {});
+  }
+
+  removeCSS(context, details) {
+    return this._execute(context, details, "css", "removeCSS").then(() => {});
+  }
 }
 
 // Note: These must match the values in windows.json.
 const WINDOW_ID_NONE = -1;
 const WINDOW_ID_CURRENT = -2;
 
 class WindowBase {
   constructor(extension, window, id) {