diff --git a/browser/components/extensions/ext-browserAction.js b/browser/components/extensions/ext-browserAction.js
index 5ce062aa0be2..f630516fedb6 100644
--- a/browser/components/extensions/ext-browserAction.js
+++ b/browser/components/extensions/ext-browserAction.js
@@ -87,7 +87,6 @@ BrowserAction.prototype = {
onDestroyed: document => {
let view = document.getElementById(this.viewId);
if (view) {
- CustomizableUI.hidePanelForNode(view);
view.remove();
}
},
diff --git a/browser/components/extensions/ext-utils.js b/browser/components/extensions/ext-utils.js
index 47377d98d417..fcc38c192f8d 100644
--- a/browser/components/extensions/ext-utils.js
+++ b/browser/components/extensions/ext-utils.js
@@ -28,10 +28,12 @@ const POPUP_LOAD_TIMEOUT_MS = 200;
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+// Minimum time between two resizes.
+const RESIZE_TIMEOUT = 100;
+
var {
DefaultWeakMap,
EventManager,
- promiseEvent,
} = ExtensionUtils;
// This file provides some useful code for the |tabs| and |windows|
@@ -57,11 +59,17 @@ function promisePopupShown(popup) {
});
}
-XPCOMUtils.defineLazyGetter(this, "popupStylesheets", () => {
- let stylesheets = ["chrome://browser/content/extension.css"];
+XPCOMUtils.defineLazyGetter(this, "stylesheets", () => {
+ let styleSheetURI = NetUtil.newURI("chrome://browser/content/extension.css");
+ let styleSheet = styleSheetService.preloadSheet(styleSheetURI,
+ styleSheetService.AGENT_SHEET);
+ let stylesheets = [styleSheet];
if (AppConstants.platform === "macosx") {
- stylesheets.push("chrome://browser/content/extension-mac.css");
+ styleSheetURI = NetUtil.newURI("chrome://browser/content/extension-mac.css");
+ let macStyleSheet = styleSheetService.preloadSheet(styleSheetURI,
+ styleSheetService.AGENT_SHEET);
+ stylesheets.push(macStyleSheet);
}
return stylesheets;
});
@@ -70,10 +78,16 @@ XPCOMUtils.defineLazyGetter(this, "standaloneStylesheets", () => {
let stylesheets = [];
if (AppConstants.platform === "macosx") {
- stylesheets.push("chrome://browser/content/extension-mac-panel.css");
+ let styleSheetURI = NetUtil.newURI("chrome://browser/content/extension-mac-panel.css");
+ let macStyleSheet = styleSheetService.preloadSheet(styleSheetURI,
+ styleSheetService.AGENT_SHEET);
+ stylesheets.push(macStyleSheet);
}
if (AppConstants.platform === "win") {
- stylesheets.push("chrome://browser/content/extension-win-panel.css");
+ let styleSheetURI = NetUtil.newURI("chrome://browser/content/extension-win-panel.css");
+ let winStyleSheet = styleSheetService.preloadSheet(styleSheetURI,
+ styleSheetService.AGENT_SHEET);
+ stylesheets.push(winStyleSheet);
}
return stylesheets;
});
@@ -95,6 +109,7 @@ class BasePopup {
this.window = viewNode.ownerGlobal;
this.destroyed = false;
this.fixedWidth = fixedWidth;
+ this.ignoreResizes = true;
this.contentReady = new Promise(resolve => {
this._resolveContentReady = resolve;
@@ -142,16 +157,12 @@ class BasePopup {
}
destroyBrowser(browser) {
- let mm = browser.messageManager;
- // If the browser has already been removed from the document, because the
- // popup was closed externally, there will be no message manager here.
- if (mm) {
- mm.removeMessageListener("DOMTitleChanged", this);
- mm.removeMessageListener("Extension:BrowserBackgroundChanged", this);
- mm.removeMessageListener("Extension:BrowserContentLoaded", this);
- mm.removeMessageListener("Extension:BrowserResized", this);
- mm.removeMessageListener("Extension:DOMWindowClose", this);
- }
+ browser.removeEventListener("DOMWindowCreated", this, true);
+ browser.removeEventListener("load", this, true);
+ browser.removeEventListener("DOMContentLoaded", this, true);
+ browser.removeEventListener("DOMTitleChanged", this, true);
+ browser.removeEventListener("DOMWindowClose", this, true);
+ browser.removeEventListener("MozScrolledAreaChanged", this, true);
}
// Returns the name of the event fired on `viewNode` when the popup is being
@@ -160,19 +171,6 @@ class BasePopup {
throw new Error("Not implemented");
}
- get STYLESHEETS() {
- let sheets = [];
-
- if (this.browserStyle) {
- sheets.push(...popupStylesheets);
- }
- if (!this.fixedWidth) {
- sheets.push(...standaloneStylesheets);
- }
-
- return sheets;
- }
-
get panel() {
let panel = this.viewNode;
while (panel && panel.localName != "panel") {
@@ -181,40 +179,70 @@ class BasePopup {
return panel;
}
- receiveMessage({name, data}) {
- switch (name) {
- case "DOMTitleChanged":
- this.viewNode.setAttribute("aria-label", this.browser.contentTitle);
- break;
-
- case "Extension:BrowserBackgroundChanged":
- this.setBackground(data.background);
- break;
-
- case "Extension:BrowserContentLoaded":
- this.browserLoadedDeferred.resolve();
- break;
-
- case "Extension:BrowserResized":
- this._resolveContentReady();
- if (this.ignoreResizes) {
- this.dimensions = data;
- } else {
- this.resizeBrowser(data);
- }
- break;
-
- case "Extension:DOMWindowClose":
- this.closePopup();
- break;
- }
- }
-
handleEvent(event) {
switch (event.type) {
case this.DESTROY_EVENT:
this.destroy();
break;
+
+ case "DOMWindowCreated":
+ if (event.target === this.browser.contentDocument) {
+ let winUtils = this.browser.contentWindow
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+
+ if (this.browserStyle) {
+ for (let stylesheet of stylesheets) {
+ winUtils.addSheet(stylesheet, winUtils.AGENT_SHEET);
+ }
+ }
+ if (!this.fixedWidth) {
+ for (let stylesheet of standaloneStylesheets) {
+ winUtils.addSheet(stylesheet, winUtils.AGENT_SHEET);
+ }
+ }
+ }
+ break;
+
+ case "DOMWindowClose":
+ if (event.target === this.browser.contentWindow) {
+ event.preventDefault();
+ this.closePopup();
+ }
+ break;
+
+ case "DOMTitleChanged":
+ this.viewNode.setAttribute("aria-label", this.browser.contentTitle);
+ break;
+
+ case "DOMContentLoaded":
+ this.browserLoadedDeferred.resolve();
+ this.resizeBrowser(true);
+ break;
+
+ case "load":
+ // We use a capturing listener, so we get this event earlier than any
+ // load listeners in the content page. Resizing after a timeout ensures
+ // that we calculate the size after the entire event cycle has completed
+ // (unless someone spins the event loop, anyway), and hopefully after
+ // the content has made any modifications.
+ Promise.resolve().then(() => {
+ this.resizeBrowser(true);
+ });
+
+ // Mutation observer to make sure the panel shrinks when the content does.
+ new this.browser.contentWindow.MutationObserver(this.resizeBrowser.bind(this)).observe(
+ this.browser.contentDocument.documentElement, {
+ attributes: true,
+ characterData: true,
+ childList: true,
+ subtree: true,
+ });
+ break;
+
+ case "MozScrolledAreaChanged":
+ this.resizeBrowser();
+ break;
}
}
@@ -241,12 +269,12 @@ class BasePopup {
viewNode.appendChild(this.browser);
let initBrowser = browser => {
- let mm = browser.messageManager;
- mm.addMessageListener("DOMTitleChanged", this);
- mm.addMessageListener("Extension:BrowserBackgroundChanged", this);
- mm.addMessageListener("Extension:BrowserContentLoaded", this);
- mm.addMessageListener("Extension:BrowserResized", this);
- mm.addMessageListener("Extension:DOMWindowClose", this, true);
+ browser.addEventListener("DOMWindowCreated", this, true);
+ browser.addEventListener("load", this, true);
+ browser.addEventListener("DOMContentLoaded", this, true);
+ browser.addEventListener("DOMTitleChanged", this, true);
+ browser.addEventListener("DOMWindowClose", this, true);
+ browser.addEventListener("MozScrolledAreaChanged", this, true);
};
if (!popupURL) {
@@ -254,28 +282,82 @@ class BasePopup {
return this.browser;
}
- return promiseEvent(this.browser, "load").then(() => {
+ return new Promise(resolve => {
+ // The first load event is for about:blank.
+ // We can't finish setting up the browser until the binding has fully
+ // initialized. Waiting for the first load event guarantees that it has.
+ let loadListener = event => {
+ this.browser.removeEventListener("load", loadListener, true);
+ resolve();
+ };
+ this.browser.addEventListener("load", loadListener, true);
+ }).then(() => {
initBrowser(this.browser);
- let mm = this.browser.messageManager;
+ let {contentWindow} = this.browser;
- mm.loadFrameScript(
- "chrome://extensions/content/ext-browser-content.js", false);
-
- mm.sendAsyncMessage("Extension:InitBrowser", {
- allowScriptsToClose: true,
- fixedWidth: this.fixedWidth,
- maxWidth: 800,
- maxHeight: 600,
- stylesheets: this.STYLESHEETS,
- });
+ contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils)
+ .allowScriptsToClose();
this.browser.setAttribute("src", popupURL);
});
}
- resizeBrowser({width, height, detail}) {
+ // Resizes the browser to match the preferred size of the content (debounced).
+ resizeBrowser(ignoreThrottling = false) {
+ if (this.ignoreResizes) {
+ return;
+ }
+
+ if (ignoreThrottling && this.resizeTimeout) {
+ this.window.clearTimeout(this.resizeTimeout);
+ this.resizeTimeout = null;
+ }
+
+ if (this.resizeTimeout == null) {
+ this.resizeTimeout = this.window.setTimeout(() => {
+ try {
+ this._resizeBrowser();
+ } finally {
+ this.resizeTimeout = null;
+ }
+ }, RESIZE_TIMEOUT);
+
+ this._resizeBrowser();
+ }
+ }
+
+ _resizeBrowser() {
+ let doc = this.browser && this.browser.contentDocument;
+ if (!doc || !doc.documentElement) {
+ return;
+ }
+
+ let root = doc.documentElement;
+ let body = doc.body;
+ if (!body || doc.compatMode == "BackCompat") {
+ // In quirks mode, the root element is used as the scroll frame, and the
+ // body lies about its scroll geometry, and returns the values for the
+ // root instead.
+ body = root;
+ }
+
+
if (this.fixedWidth) {
+ // If we're in a fixed-width area (namely a slide-in subview of the main
+ // menu panel), we need to calculate the view height based on the
+ // preferred height of the content document's root scrollable element at the
+ // current width, rather than the complete preferred dimensions of the
+ // content window.
+
+ // Compensate for any offsets (margin, padding, ...) between the scroll
+ // area of the body and the outer height of the document.
+ let getHeight = elem => elem.getBoundingClientRect(elem).height;
+ let bodyPadding = getHeight(root) - getHeight(body);
+
+ let height = Math.ceil(body.scrollHeight + bodyPadding);
+
// Figure out how much extra space we have on the side of the panel
// opposite the arrow.
let side = this.panel.getAttribute("side") == "top" ? "bottom" : "top";
@@ -292,32 +374,48 @@ class BasePopup {
height = Math.max(height, this.viewHeight);
this.viewNode.style.maxHeight = `${height}px`;
} else {
+ // Copy the background color of the document's body to the panel if it's
+ // fully opaque.
+ let panelBackground = "";
+ let panelArrow = "";
+
+ let background = doc.defaultView.getComputedStyle(body).backgroundColor;
+ if (background != "transparent") {
+ let bgColor = colorUtils.colorToRGBA(background);
+ if (bgColor.a == 1) {
+ panelBackground = background;
+ let borderColor = this.borderColor || background;
+
+ panelArrow = `url("data:image/svg+xml,${encodeURIComponent(`
+
+ `)}")`;
+ }
+ }
+
+ this.panel.style.setProperty("--arrowpanel-background", panelBackground);
+ this.panel.style.setProperty("--panel-arrow-image-vertical", panelArrow);
+
+
+ // Adjust the size of the browser based on its content's preferred size.
+ let {contentViewer} = this.browser.docShell;
+ let ratio = this.window.devicePixelRatio;
+
+ let w = {}, h = {};
+ contentViewer.getContentSizeConstrained(800 * ratio, 600 * ratio, w, h);
+ let width = Math.ceil(w.value / ratio);
+ let height = Math.ceil(h.value / ratio);
+
this.browser.style.width = `${width}px`;
this.browser.style.height = `${height}px`;
}
- let event = new this.window.CustomEvent("WebExtPopupResized", {detail});
+ let event = new this.window.CustomEvent("WebExtPopupResized");
this.browser.dispatchEvent(event);
- }
- setBackground(background) {
- let panelBackground = "";
- let panelArrow = "";
-
- if (background) {
- let borderColor = this.borderColor || background;
-
- panelBackground = background;
- panelArrow = `url("data:image/svg+xml,${encodeURIComponent(`
-
- `)}")`;
- }
-
- this.panel.style.setProperty("--arrowpanel-background", panelBackground);
- this.panel.style.setProperty("--panel-arrow-image-vertical", panelArrow);
+ this._resolveContentReady();
}
}
@@ -328,7 +426,7 @@ class BasePopup {
*/
BasePopup.instances = new DefaultWeakMap(() => new WeakMap());
-class PanelPopup extends BasePopup {
+global.PanelPopup = class PanelPopup extends BasePopup {
constructor(extension, imageNode, popupURL, browserStyle) {
let document = imageNode.ownerDocument;
@@ -343,6 +441,8 @@ class PanelPopup extends BasePopup {
super(extension, panel, popupURL, browserStyle);
+ this.ignoreResizes = false;
+
this.contentReady.then(() => {
panel.openPopup(imageNode, "bottomcenter topright", 0, 0, false, false);
});
@@ -365,9 +465,9 @@ class PanelPopup extends BasePopup {
}
});
}
-}
+};
-class ViewPopup extends BasePopup {
+global.ViewPopup = class ViewPopup extends BasePopup {
constructor(extension, window, popupURL, browserStyle, fixedWidth) {
let document = window.document;
@@ -380,8 +480,6 @@ class ViewPopup extends BasePopup {
super(extension, panel, popupURL, browserStyle, fixedWidth);
- this.ignoreResizes = true;
-
this.attached = false;
this.tempPanel = panel;
@@ -451,9 +549,7 @@ class ViewPopup extends BasePopup {
this.destroyBrowser(browser);
this.ignoreResizes = false;
- if (this.dimensions) {
- this.resizeBrowser(this.dimensions);
- }
+ this.resizeBrowser(true);
this.tempPanel.remove();
this.tempPanel = null;
@@ -482,9 +578,7 @@ class ViewPopup extends BasePopup {
this.destroy();
}
}
-}
-
-Object.assign(global, {PanelPopup, ViewPopup});
+};
// Manages tab-specific context data, and dispatching tab select events
// across all windows.
diff --git a/browser/components/extensions/test/browser/browser_ext_browserAction_popup_resize.js b/browser/components/extensions/test/browser/browser_ext_browserAction_popup_resize.js
index 8bda566ee400..d485f9c79143 100644
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_popup_resize.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_popup_resize.js
@@ -14,7 +14,8 @@ function* awaitResize(browser) {
// looking for, but don't wait longer than a few seconds.
return Promise.race([
- BrowserTestUtils.waitForEvent(browser, "WebExtPopupResized", event => event.detail === "delayed"),
+ BrowserTestUtils.waitForEvent(browser, "WebExtPopupResized")
+ .then(() => BrowserTestUtils.waitForEvent(browser, "WebExtPopupResized")),
new Promise(resolve => setTimeout(resolve, 5000)),
]);
}
diff --git a/browser/components/extensions/test/browser/browser_ext_pageAction_popup_resize.js b/browser/components/extensions/test/browser/browser_ext_pageAction_popup_resize.js
index 78d57a51b4dc..671cfa5d8f00 100644
--- a/browser/components/extensions/test/browser/browser_ext_pageAction_popup_resize.js
+++ b/browser/components/extensions/test/browser/browser_ext_pageAction_popup_resize.js
@@ -15,8 +15,6 @@ function* awaitResize(browser) {
}
add_task(function* testPageActionPopupResize() {
- let browser;
-
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"page_action": {
@@ -25,7 +23,6 @@ add_task(function* testPageActionPopupResize() {
},
},
background: function() {
- /* global browser */
browser.tabs.query({active: true, currentWindow: true}, tabs => {
const tabId = tabs[0].id;
@@ -45,10 +42,12 @@ add_task(function* testPageActionPopupResize() {
clickPageAction(extension, window);
- browser = yield awaitExtensionPanel(extension);
+ let {target: panelDocument} = yield BrowserTestUtils.waitForEvent(document, "load", true, (event) => {
+ info(`Loaded ${event.target.location}`);
+ return event.target.location && event.target.location.href.endsWith("popup.html");
+ });
- let panelWindow = browser.contentWindow;
- let panelDocument = panelWindow.document;
+ let panelWindow = panelDocument.defaultView;
let panelBody = panelDocument.body.firstChild;
let body = panelDocument.body;
let root = panelDocument.documentElement;
@@ -70,7 +69,7 @@ add_task(function* testPageActionPopupResize() {
panelBody.style.height = `${size}px`;
panelBody.style.width = `${size}px`;
- return BrowserTestUtils.waitForEvent(browser, "WebExtPopupResized");
+ return BrowserTestUtils.waitForEvent(panelWindow, "resize");
}
let sizes = [
diff --git a/browser/components/extensions/test/browser/browser_ext_popup_shutdown.js b/browser/components/extensions/test/browser/browser_ext_popup_shutdown.js
index c20eb3e1b9ed..19035476e46a 100644
--- a/browser/components/extensions/test/browser/browser_ext_popup_shutdown.js
+++ b/browser/components/extensions/test/browser/browser_ext_popup_shutdown.js
@@ -70,7 +70,5 @@ add_task(function* testPageAction() {
yield extension.unload();
- yield new Promise(resolve => setTimeout(resolve, 0));
-
is(panel.parentNode, null, "Panel should be removed from the document");
});
diff --git a/devtools/client/aboutdebugging/test/addons/test-devtools-webextension/popup.js b/devtools/client/aboutdebugging/test/addons/test-devtools-webextension/popup.js
index 035375682d67..7c463a91a908 100644
--- a/devtools/client/aboutdebugging/test/addons/test-devtools-webextension/popup.js
+++ b/devtools/client/aboutdebugging/test/addons/test-devtools-webextension/popup.js
@@ -10,4 +10,5 @@
// browser_addons_debug_webextension.js
function myWebExtensionPopupAddonFunction() { // eslint-disable-line no-unused-vars
console.log("Popup page function called", browser.runtime.getManifest());
+ window.close();
}
diff --git a/toolkit/components/extensions/Extension.jsm b/toolkit/components/extensions/Extension.jsm
index 6f0db2dbc7c6..f52917637cb7 100644
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -54,13 +54,17 @@ XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
"resource://gre/modules/Preferences.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "require",
- "resource://devtools/shared/Loader.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Schemas",
"resource://gre/modules/Schemas.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task",
"resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyGetter(this, "require", () => {
+ let obj = {};
+ Cu.import("resource://devtools/shared/Loader.jsm", obj);
+ return obj.require;
+});
+
Cu.import("resource://gre/modules/ExtensionContent.jsm");
Cu.import("resource://gre/modules/ExtensionManagement.jsm");
diff --git a/toolkit/components/extensions/ExtensionUtils.jsm b/toolkit/components/extensions/ExtensionUtils.jsm
index f682a8ae469e..bb3890b9395e 100644
--- a/toolkit/components/extensions/ExtensionUtils.jsm
+++ b/toolkit/components/extensions/ExtensionUtils.jsm
@@ -28,8 +28,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "Locale",
"resource://gre/modules/Locale.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "MessageChannel",
"resource://gre/modules/MessageChannel.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
- "resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
"resource://gre/modules/Preferences.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
@@ -37,10 +35,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
XPCOMUtils.defineLazyModuleGetter(this, "Schemas",
"resource://gre/modules/Schemas.jsm");
-XPCOMUtils.defineLazyServiceGetter(this, "styleSheetService",
- "@mozilla.org/content/style-sheet-service;1",
- "nsIStyleSheetService");
-
function getConsole() {
return new ConsoleAPI({
maxLogLevelPref: "extensions.webextensions.log.level",
@@ -154,20 +148,6 @@ class DefaultWeakMap extends WeakMap {
}
}
-class DefaultMap extends Map {
- constructor(defaultConstructor, init) {
- super(init);
- this.defaultConstructor = defaultConstructor;
- }
-
- get(key) {
- if (!this.has(key)) {
- this.set(key, this.defaultConstructor(key));
- }
- return super.get(key);
- }
-}
-
class SpreadArgs extends Array {
constructor(args) {
super();
@@ -1137,35 +1117,6 @@ function promiseDocumentLoaded(doc) {
});
}
-/**
- * Returns a Promise which resolves when the given event is dispatched to the
- * given element.
- *
- * @param {Element} element
- * The element on which to listen.
- * @param {string} eventName
- * The event to listen for.
- * @param {boolean} [useCapture = true]
- * If true, listen for the even in the capturing rather than
- * bubbling phase.
- * @param {Event} [test]
- * An optional test function which, when called with the
- * observer's subject and data, should return true if this is the
- * expected event, false otherwise.
- * @returns {Promise}
- */
-function promiseEvent(element, eventName, useCapture = true, test = event => true) {
- return new Promise(resolve => {
- function listener(event) {
- if (test(event)) {
- element.removeEventListener(eventName, listener, useCapture);
- resolve(event);
- }
- }
- element.addEventListener(eventName, listener, useCapture);
- });
-}
-
/**
* Returns a Promise which resolves the given observer topic has been
* observed.
@@ -2016,11 +1967,6 @@ function normalizeTime(date) {
? parseInt(date, 10) : date);
}
-const stylesheetMap = new DefaultMap(url => {
- let uri = NetUtil.newURI(url);
- return styleSheetService.preloadSheet(uri, styleSheetService.AGENT_SHEET);
-});
-
this.ExtensionUtils = {
detectLanguage,
extend,
@@ -2033,13 +1979,11 @@ this.ExtensionUtils = {
normalizeTime,
promiseDocumentLoaded,
promiseDocumentReady,
- promiseEvent,
promiseObserved,
runSafe,
runSafeSync,
runSafeSyncWithoutClone,
runSafeWithoutClone,
- stylesheetMap,
BaseContext,
DefaultWeakMap,
EventEmitter,
diff --git a/toolkit/components/extensions/ext-browser-content.js b/toolkit/components/extensions/ext-browser-content.js
deleted file mode 100644
index e14ca50d6afb..000000000000
--- a/toolkit/components/extensions/ext-browser-content.js
+++ /dev/null
@@ -1,217 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
-
-Cu.import("resource://gre/modules/ExtensionUtils.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "clearTimeout",
- "resource://gre/modules/Timer.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
- "resource://gre/modules/NetUtil.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "require",
- "resource://devtools/shared/Loader.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
- "resource://gre/modules/Timer.jsm");
-
-XPCOMUtils.defineLazyGetter(this, "colorUtils", () => {
- return require("devtools/shared/css/color").colorUtils;
-});
-
-const {
- stylesheetMap,
-} = ExtensionUtils;
-
-/* globals addMessageListener, content, docShell, sendAsyncMessage */
-
-// Minimum time between two resizes.
-const RESIZE_TIMEOUT = 100;
-
-const BrowserListener = {
- init({allowScriptsToClose, fixedWidth, maxHeight, maxWidth, stylesheets}) {
- this.fixedWidth = fixedWidth;
- this.stylesheets = stylesheets || [];
-
- this.maxWidth = maxWidth;
- this.maxHeight = maxHeight;
-
- this.oldBackground = null;
-
- if (allowScriptsToClose) {
- content.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils)
- .allowScriptsToClose();
- }
-
- addEventListener("DOMWindowCreated", this, true);
- addEventListener("load", this, true);
- addEventListener("DOMContentLoaded", this, true);
- addEventListener("DOMWindowClose", this, true);
- addEventListener("MozScrolledAreaChanged", this, true);
- },
-
- destroy() {
- removeEventListener("DOMWindowCreated", this, true);
- removeEventListener("load", this, true);
- removeEventListener("DOMContentLoaded", this, true);
- removeEventListener("DOMWindowClose", this, true);
- removeEventListener("MozScrolledAreaChanged", this, true);
- },
-
- receiveMessage({name, data}) {
- if (name === "Extension:InitBrowser") {
- this.init(data);
- }
- },
-
- handleEvent(event) {
- switch (event.type) {
- case "DOMWindowCreated":
- if (event.target === content.document) {
- let winUtils = content.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils);
-
- for (let url of this.stylesheets) {
- winUtils.addSheet(stylesheetMap.get(url), winUtils.AGENT_SHEET);
- }
- }
- break;
-
- case "DOMWindowClose":
- if (event.target === content) {
- event.preventDefault();
-
- sendAsyncMessage("Extension:DOMWindowClose");
- }
- break;
-
- case "DOMContentLoaded":
- if (event.target === content.document) {
- sendAsyncMessage("Extension:BrowserContentLoaded", {url: content.location.href});
- this.handleDOMChange(true);
- }
- break;
-
- case "load":
- if (event.target.contentWindow === content) {
- // For about:addons inline , we currently receive a load
- // event on the element, but no load or DOMContentLoaded
- // events from the content window.
- sendAsyncMessage("Extension:BrowserContentLoaded", {url: content.location.href});
- } else if (event.target !== content.document) {
- break;
- }
-
- // We use a capturing listener, so we get this event earlier than any
- // load listeners in the content page. Resizing after a timeout ensures
- // that we calculate the size after the entire event cycle has completed
- // (unless someone spins the event loop, anyway), and hopefully after
- // the content has made any modifications.
- Promise.resolve().then(() => {
- this.handleDOMChange(true);
- });
-
- // Mutation observer to make sure the panel shrinks when the content does.
- new content.MutationObserver(this.handleDOMChange.bind(this)).observe(
- content.document.documentElement, {
- attributes: true,
- characterData: true,
- childList: true,
- subtree: true,
- });
- break;
-
- case "MozScrolledAreaChanged":
- this.handleDOMChange();
- break;
- }
- },
-
- // Resizes the browser to match the preferred size of the content (debounced).
- handleDOMChange(ignoreThrottling = false) {
- if (ignoreThrottling && this.resizeTimeout) {
- clearTimeout(this.resizeTimeout);
- this.resizeTimeout = null;
- }
-
- if (this.resizeTimeout == null) {
- this.resizeTimeout = setTimeout(() => {
- try {
- if (content) {
- this._handleDOMChange("delayed");
- }
- } finally {
- this.resizeTimeout = null;
- }
- }, RESIZE_TIMEOUT);
-
- this._handleDOMChange();
- }
- },
-
- _handleDOMChange(detail) {
- let doc = content.document;
-
- let body = doc.body;
- if (!body || doc.compatMode === "BackCompat") {
- // In quirks mode, the root element is used as the scroll frame, and the
- // body lies about its scroll geometry, and returns the values for the
- // root instead.
- body = doc.documentElement;
- }
-
-
- let result;
- if (this.fixedWidth) {
- // If we're in a fixed-width area (namely a slide-in subview of the main
- // menu panel), we need to calculate the view height based on the
- // preferred height of the content document's root scrollable element at the
- // current width, rather than the complete preferred dimensions of the
- // content window.
-
- // Compensate for any offsets (margin, padding, ...) between the scroll
- // area of the body and the outer height of the document.
- let getHeight = elem => elem.getBoundingClientRect(elem).height;
- let bodyPadding = getHeight(doc.documentElement) - getHeight(body);
-
- let height = Math.ceil(body.scrollHeight + bodyPadding);
-
- result = {height, detail};
- } else {
- let background = doc.defaultView.getComputedStyle(body).backgroundColor;
- let bgColor = colorUtils.colorToRGBA(background);
- if (bgColor.a !== 1) {
- // Ignore non-opaque backgrounds.
- background = null;
- }
-
- if (background !== this.oldBackground) {
- sendAsyncMessage("Extension:BrowserBackgroundChanged", {background});
- }
- this.oldBackground = background;
-
-
- // Adjust the size of the browser based on its content's preferred size.
- let {contentViewer} = docShell;
- let ratio = content.devicePixelRatio;
-
- let w = {}, h = {};
- contentViewer.getContentSizeConstrained(this.maxWidth * ratio,
- this.maxHeight * ratio,
- w, h);
-
- let width = Math.ceil(w.value / ratio);
- let height = Math.ceil(h.value / ratio);
-
- result = {width, height, detail};
- }
-
- sendAsyncMessage("Extension:BrowserResized", result);
- },
-};
-
-addMessageListener("Extension:InitBrowser", BrowserListener);
diff --git a/toolkit/components/extensions/jar.mn b/toolkit/components/extensions/jar.mn
index fde6a3516762..3d0f1fc905b6 100644
--- a/toolkit/components/extensions/jar.mn
+++ b/toolkit/components/extensions/jar.mn
@@ -6,7 +6,6 @@ toolkit.jar:
% content extensions %content/extensions/
content/extensions/ext-alarms.js
content/extensions/ext-backgroundPage.js
- content/extensions/ext-browser-content.js
content/extensions/ext-cookies.js
content/extensions/ext-downloads.js
content/extensions/ext-management.js
diff --git a/toolkit/mozapps/extensions/content/extensions.js b/toolkit/mozapps/extensions/content/extensions.js
index 7bbcee96505b..fec280ca8acf 100644
--- a/toolkit/mozapps/extensions/content/extensions.js
+++ b/toolkit/mozapps/extensions/content/extensions.js
@@ -83,79 +83,6 @@ XPCOMUtils.defineLazyGetter(gStrings, "appVersion", function() {
document.addEventListener("load", initialize, true);
window.addEventListener("unload", shutdown, false);
-class MessageDispatcher {
- constructor(target) {
- this.listeners = new Map();
- this.target = target;
- }
-
- addMessageListener(name, handler) {
- if (!this.listeners.has(name)) {
- this.listeners.set(name, new Set());
- }
-
- this.listeners.get(name).add(handler);
- }
-
- removeMessageListener(name, handler) {
- if (this.listeners.has(name)) {
- this.listeners.get(name).delete(handler);
- }
- }
-
- sendAsyncMessage(name, data) {
- for (let handler of this.listeners.get(name) || new Set()) {
- Promise.resolve().then(() => {
- handler.receiveMessage({
- name,
- data,
- target: this.target,
- });
- });
- }
- }
-}
-
-/**
- * A mock FrameMessageManager global to allow frame scripts to run in
- * non-top-level, non-remote s as if they were top-level or
- * remote.
- *
- * @param {Element} browser
- * A XUL element.
- */
-class FakeFrameMessageManager {
- constructor(browser) {
- let dispatcher = new MessageDispatcher(browser);
- let frameDispatcher = new MessageDispatcher(null);
-
- this.sendAsyncMessage = frameDispatcher.sendAsyncMessage.bind(frameDispatcher);
- this.addMessageListener = dispatcher.addMessageListener.bind(dispatcher);
- this.removeMessageListener = dispatcher.removeMessageListener.bind(dispatcher);
-
- this.frame = {
- get content() {
- return browser.contentWindow;
- },
-
- get docShell() {
- return browser.docShell;
- },
-
- addEventListener: browser.addEventListener.bind(browser),
- removeEventListener: browser.removeEventListener.bind(browser),
-
- sendAsyncMessage: dispatcher.sendAsyncMessage.bind(dispatcher),
- addMessageListener: frameDispatcher.addMessageListener.bind(frameDispatcher),
- removeMessageListener: frameDispatcher.removeMessageListener.bind(frameDispatcher),
- }
- }
-
- loadFrameScript(url) {
- Services.scriptloader.loadSubScript(url, Object.create(this.frame));
- }
-}
-
var gPendingInitializations = 1;
Object.defineProperty(this, "gIsInitializing", {
get: () => gPendingInitializations > 0
@@ -3526,30 +3453,72 @@ var gDetailView = {
browser.setAttribute("disableglobalhistory", "true");
browser.setAttribute("class", "inline-options-browser");
- return new Promise((resolve, reject) => {
- let messageListener = {
- receiveMessage({name, data}) {
- if (name === "Extension:BrowserResized")
- browser.style.height = `${data.height}px`;
- else if (name === "Extension:BrowserContentLoaded")
- resolve(browser);
- },
- };
+ // Resize at most 10 times per second.
+ const TIMEOUT = 100;
+ let timeout;
+ function resizeBrowser() {
+ if (timeout == null) {
+ _resizeBrowser();
+ timeout = setTimeout(_resizeBrowser, TIMEOUT);
+ }
+ }
+
+ function _resizeBrowser() {
+ timeout = null;
+
+ let doc = browser.contentDocument;
+ if (!doc) {
+ return;
+ }
+
+ let body = doc.body || doc.documentElement;
+
+ let docHeight = doc.documentElement.getBoundingClientRect().height;
+
+ let height = Math.ceil(body.scrollHeight +
+ // Compensate for any offsets between the scroll
+ // area of the body and the outer height of the
+ // document.
+ docHeight - body.clientHeight);
+
+ // Note: This will trigger another MozScrolledAreaChanged event
+ // if it's different from the previous height.
+ browser.style.height = `${height}px`;
+ }
+
+ return new Promise((resolve, reject) => {
let onload = () => {
browser.removeEventListener("load", onload, true);
- let mm = new FakeFrameMessageManager(browser);
- mm.loadFrameScript("chrome://extensions/content/ext-browser-content.js",
- false);
- mm.addMessageListener("Extension:BrowserContentLoaded", messageListener);
- mm.addMessageListener("Extension:BrowserResized", messageListener);
- mm.sendAsyncMessage("Extension:InitBrowser", {fixedWidth: true});
+ browser.addEventListener("error", reject);
+ browser.addEventListener("load", event => {
+ // We only get load events targetted at one of these elements.
+ // If we're running in a tab, it's the . If we're
+ // running in a dialog, it's the content document.
+ if (event.target != browser && event.target != browser.contentDocument)
+ return;
+
+ resolve(browser);
+
+ browser.contentWindow.addEventListener("MozScrolledAreaChanged", event => {
+ resizeBrowser();
+ }, true);
+
+ new browser.contentWindow.MutationObserver(resizeBrowser).observe(
+ browser.contentDocument.documentElement, {
+ attributes: true,
+ characterData: true,
+ childList: true,
+ subtree: true,
+ });
+
+ resizeBrowser();
+ }, true);
browser.setAttribute("src", this._addon.optionsURL);
};
browser.addEventListener("load", onload, true);
- browser.addEventListener("error", reject);
parentNode.appendChild(browser);
});