Bug 1368289: Simplify frameloader global metadata tagging. r?mixedpuppy
MozReview-Commit-ID: 1hgTtWysmya
--- a/browser/components/extensions/ext-utils.js
+++ b/browser/components/extensions/ext-utils.js
@@ -227,17 +227,17 @@ class TabTracker extends TabTrackerBase
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));
- adoptedTab.linkedBrowser.messageManager.sendAsyncMessage("Extension:SetTabAndWindowId", {
+ 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
// dispatched.
let currentTab = nativeTab.ownerGlobal.gBrowser.selectedTab;
--- a/toolkit/components/extensions/ExtensionPageChild.jsm
+++ b/toolkit/components/extensions/ExtensionPageChild.jsm
@@ -21,16 +21,21 @@ const Cr = Components.results;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Schemas",
"resource://gre/modules/Schemas.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WebNavigationFrames",
"resource://gre/modules/WebNavigationFrames.jsm");
+XPCOMUtils.defineLazyGetter(
+ this, "processScript",
+ () => Cc["@mozilla.org/webextensions/extension-process-script;1"]
+ .getService().wrappedJSObject);
+
const CATEGORY_EXTENSION_SCRIPTS_ADDON = "webextension-scripts-addon";
const CATEGORY_EXTENSION_SCRIPTS_DEVTOOLS = "webextension-scripts-devtools";
Cu.import("resource://gre/modules/ExtensionCommon.jsm");
Cu.import("resource://gre/modules/ExtensionChild.jsm");
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
const {
@@ -47,16 +52,20 @@ const {
const {
ChildAPIManager,
Messenger,
} = ExtensionChild;
var ExtensionPageChild;
+function getFrameData(global) {
+ return processScript.getFrameData(global, true);
+}
+
var apiManager = new class extends SchemaAPIManager {
constructor() {
super("addon");
this.initialized = false;
}
lazyInit() {
if (!this.initialized) {
@@ -143,18 +152,18 @@ class ExtensionBaseContextChild extends
}
get principal() {
return this.contentWindow.document.nodePrincipal;
}
get windowId() {
if (["tab", "popup", "sidebar"].includes(this.viewType)) {
- let globalView = ExtensionPageChild.contentGlobals.get(this.messageManager);
- return globalView ? globalView.windowId : -1;
+ let frameData = getFrameData(this.messageManager);
+ return frameData ? frameData.windowId : -1;
}
return -1;
}
get tabId() {
// Will be overwritten in the constructor if necessary.
return -1;
}
@@ -285,150 +294,53 @@ defineLazyGetter(DevToolsContextChild.pr
incognito: this.incognito,
});
this.callOnClose(childManager);
return childManager;
});
-// All subframes in a tab, background page, popup, etc. have the same view type.
-// This class keeps track of such global state.
-// Note that this is created even for non-extension tabs because at present we
-// do not have a way to distinguish regular tabs from extension tabs at the
-// initialization of a frame script.
-class ContentGlobal {
- /**
- * @param {nsIContentFrameMessageManager} global The frame script's global.
- */
- constructor(global) {
- this.global = global;
- // Unless specified otherwise assume that the extension page is in a tab,
- // because the majority of all class instances are going to be a tab. Any
- // special views (background page, extension popup) will immediately send an
- // Extension:InitExtensionView message to change the viewType.
- this.viewType = "tab";
- this.tabId = -1;
- this.windowId = -1;
- this.initialized = false;
-
- this.global.addMessageListener("Extension:InitExtensionView", this);
- this.global.addMessageListener("Extension:SetTabAndWindowId", this);
- }
-
- uninit() {
- this.global.removeMessageListener("Extension:InitExtensionView", this);
- this.global.removeMessageListener("Extension:SetTabAndWindowId", this);
- }
-
- ensureInitialized() {
- if (!this.initialized) {
- // Request tab and window ID in case "Extension:InitExtensionView" is not
- // sent (e.g. when `viewType` is "tab").
- let reply = this.global.sendSyncMessage("Extension:GetTabAndWindowId");
- this.handleSetTabAndWindowId(reply[0] || {});
- }
- return this;
- }
-
- receiveMessage({name, data}) {
- switch (name) {
- case "Extension:InitExtensionView":
- // The view type is initialized once and then fixed.
- this.global.removeMessageListener("Extension:InitExtensionView", this);
- this.viewType = data.viewType;
-
- // Force external links to open in tabs.
- if (["popup", "sidebar"].includes(this.viewType)) {
- this.global.docShell.isAppTab = true;
- }
-
- if (data.devtoolsToolboxInfo) {
- this.devtoolsToolboxInfo = data.devtoolsToolboxInfo;
- }
-
- promiseEvent(this.global, "DOMContentLoaded", true).then(() => {
- let windowId = getInnerWindowID(this.global.content);
- let context = ExtensionPageChild.extensionContexts.get(windowId);
-
- this.global.sendAsyncMessage("Extension:ExtensionViewLoaded",
- {childId: context && context.childManager.id});
- });
-
- /* FALLTHROUGH */
- case "Extension:SetTabAndWindowId":
- this.handleSetTabAndWindowId(data);
- break;
- }
- }
-
- handleSetTabAndWindowId(data) {
- let {tabId, windowId} = data;
-
- if (tabId) {
- // Tab IDs are not expected to change.
- if (this.tabId !== -1 && tabId !== this.tabId) {
- throw new Error("Attempted to change a tabId after it was set");
- }
- this.tabId = tabId;
- }
-
- if (windowId !== undefined) {
- // Window IDs may change if a tab is moved to a different location.
- // Note: This is the ID of the browser window for the extension API.
- // Do not confuse it with the innerWindowID of DOMWindows!
- this.windowId = windowId;
- }
- this.initialized = true;
- }
-}
-
ExtensionPageChild = {
- // Map<nsIContentFrameMessageManager, ContentGlobal>
- contentGlobals: new Map(),
-
// Map<innerWindowId, ExtensionPageContextChild>
extensionContexts: new Map(),
initialized: false,
_init() {
if (this.initialized) {
return;
}
this.initialized = true;
Services.obs.addObserver(this, "inner-window-destroyed"); // eslint-ignore-line mozilla/balanced-listeners
},
- init(global) {
- if (!WebExtensionPolicy.isExtensionProcess) {
- throw new Error("Cannot init extension page global in current process");
- }
-
- if (!this.contentGlobals.has(global)) {
- this.contentGlobals.set(global, new ContentGlobal(global));
- }
- },
-
- uninit(global) {
- if (this.contentGlobals.has(global)) {
- this.contentGlobals.get(global).uninit();
- this.contentGlobals.delete(global);
- }
- },
-
observe(subject, topic, data) {
if (topic === "inner-window-destroyed") {
let windowId = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
this.destroyExtensionContext(windowId);
}
},
+ expectViewLoad(global, viewType) {
+ if (["popup", "sidebar"].includes(viewType)) {
+ global.docShell.isAppTab = true;
+ }
+
+ promiseEvent(global, "DOMContentLoaded", true).then(() => {
+ let windowId = getInnerWindowID(global.content);
+ let context = this.extensionContexts.get(windowId);
+
+ global.sendAsyncMessage("Extension:ExtensionViewLoaded",
+ {childId: context && context.childManager.id});
+ });
+ },
+
/**
* Create a privileged context at document-element-inserted.
*
* @param {BrowserExtensionContent} extension
* The extension for which the context should be created.
* @param {nsIDOMWindow} contentWindow The global of the page.
*/
initExtensionContext(extension, contentWindow) {
@@ -447,17 +359,17 @@ ExtensionPageChild = {
throw new Error("An extension context was already initialized for this frame");
}
let mm = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDocShell)
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIContentFrameMessageManager);
- let {viewType, tabId, devtoolsToolboxInfo} = this.contentGlobals.get(mm).ensureInitialized();
+ let {viewType, tabId, devtoolsToolboxInfo} = getFrameData(mm) || {};
let uri = contentWindow.document.documentURIObject;
if (devtoolsToolboxInfo) {
context = new DevToolsContextChild(extension, {
viewType, contentWindow, uri, tabId, devtoolsToolboxInfo,
});
} else {
--- a/toolkit/components/extensions/ExtensionParent.jsm
+++ b/toolkit/components/extensions/ExtensionParent.jsm
@@ -138,17 +138,17 @@ let apiManager = new class extends Schem
receiveMessage({name, target, sync}) {
if (name === "Extension:GetTabAndWindowId") {
let result = this.global.tabTracker.getBrowserData(target);
if (result.tabId) {
if (sync) {
return result;
}
- target.messageManager.sendAsyncMessage("Extension:SetTabAndWindowId", result);
+ target.messageManager.sendAsyncMessage("Extension:SetFrameData", result);
}
}
}
}();
// Subscribes to messages related to the extension messaging API and forwards it
// to the relevant message manager. The "sender" field for the `onMessage` and
// `onConnect` events are updated if needed.
@@ -276,17 +276,17 @@ GlobalManager = {
let viewType = browser.getAttribute("webextension-view-type");
if (viewType) {
let data = {viewType};
let {tabTracker} = apiManager.global;
Object.assign(data, tabTracker.getBrowserData(browser), additionalData);
- browser.messageManager.sendAsyncMessage("Extension:InitExtensionView",
+ browser.messageManager.sendAsyncMessage("Extension:SetFrameData",
data);
}
},
getExtension(extensionId) {
return this.extensionMap.get(extensionId);
},
--- a/toolkit/components/extensions/extension-process-script.js
+++ b/toolkit/components/extensions/extension-process-script.js
@@ -78,31 +78,54 @@ function getMessageManager(window) {
}
var DocumentManager;
var ExtensionManager;
class ExtensionGlobal {
constructor(global) {
this.global = global;
+ this.global.addMessageListener("Extension:SetFrameData", this);
+
+ this.frameData = null;
MessageChannel.addListener(global, "Extension:Capture", this);
MessageChannel.addListener(global, "Extension:DetectLanguage", this);
MessageChannel.addListener(global, "Extension:Execute", this);
MessageChannel.addListener(global, "WebNavigation:GetFrame", this);
MessageChannel.addListener(global, "WebNavigation:GetAllFrames", this);
}
get messageFilterStrict() {
return {
innerWindowID: getInnerWindowID(this.global.content),
};
}
- receiveMessage({target, messageName, recipient, data}) {
+ getFrameData(force = false) {
+ if (!this.frameData && force) {
+ this.frameData = this.global.sendSyncMessage("Extension:GetTabAndWindowId")[0];
+ }
+ return this.frameData;
+ }
+
+ receiveMessage({target, messageName, recipient, data, name}) {
+ switch (name) {
+ case "Extension:SetFrameData":
+ if (this.frameData) {
+ Object.assign(this.frameData, data);
+ } else {
+ this.frameData = data;
+ }
+ if (data.viewType && WebExtensionPolicy.isExtensionProcess) {
+ ExtensionPageChild.expectViewLoad(this.global, data.viewType);
+ }
+ return;
+ }
+
switch (messageName) {
case "Extension:Capture":
return ExtensionContent.handleExtensionCapture(this.global, data.width, data.height, data.options);
case "Extension:DetectLanguage":
return ExtensionContent.handleDetectLanguage(this.global, target);
case "Extension:Execute":
let policy = WebExtensionPolicy.getByID(recipient.extensionId);
@@ -125,59 +148,31 @@ class ExtensionGlobal {
return ExtensionContent.handleWebNavigationGetAllFrames(this.global);
}
}
}
// Responsible for creating ExtensionContexts and injecting content
// scripts into them when new documents are created.
DocumentManager = {
- globals: new Map(),
+ globals: new WeakMap(),
// Initialize listeners that we need regardless of whether extensions are
// enabled.
earlyInit() {
Services.obs.addObserver(this, "tab-content-frameloader-created"); // eslint-disable-line mozilla/balanced-listeners
},
- extensionProcessInitialized: false,
- initExtensionProcess() {
- if (this.extensionProcessInitialized || !WebExtensionPolicy.isExtensionProcess) {
- return;
- }
- this.extensionProcessInitialized = true;
-
- for (let global of this.globals.keys()) {
- ExtensionPageChild.init(global);
- }
- },
-
// Initialize a frame script global which extension contexts may be loaded
// into.
initGlobal(global) {
- // Note: {once: true} does not work as expected here.
- global.addEventListener("unload", event => { // eslint-disable-line mozilla/balanced-listeners
- this.uninitGlobal(global);
- });
-
this.globals.set(global, new ExtensionGlobal(global));
- if (this.extensionProcessInitialized && WebExtensionPolicy.isExtensionProcess) {
- ExtensionPageChild.init(global);
- }
- },
- uninitGlobal(global) {
- if (this.extensionProcessInitialized) {
- ExtensionPageChild.uninit(global);
- }
- this.globals.delete(global);
},
initExtension(extension) {
- this.initExtensionProcess();
-
this.injectExtensionScripts(extension);
},
// Listeners
observe(subject, topic, data) {
if (topic == "tab-content-frameloader-created") {
this.initGlobal(subject);
@@ -378,16 +373,21 @@ function ExtensionProcessScript() {
ExtensionProcessScript.singleton = null;
ExtensionProcessScript.prototype = {
classID: Components.ID("{21f9819e-4cdf-49f9-85a0-850af91a5058}"),
QueryInterface: XPCOMUtils.generateQI([Ci.mozIExtensionProcessScript]),
get wrappedJSObject() { return this; },
+ getFrameData(global, force) {
+ let extGlobal = DocumentManager.globals.get(global);
+ return extGlobal && extGlobal.getFrameData(force);
+ },
+
initExtension(data) {
return ExtensionManager.initExtensionPolicy(data);
},
initExtensionDocument(policy, doc) {
if (DocumentManager.globals.has(getMessageManager(doc.defaultView))) {
DocumentManager.loadInto(policy, doc.defaultView);
}