Bug 1260548: Part 2 - Factor out the excuteScript/insertCSS logic. r?aswan
MozReview-Commit-ID: 8FxlX7MKZsN
--- 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) {