Backed out 2 changesets (bug 1287209) for failures in browser_ext_pageAction_popup_resize.js

Backed out changeset 50dcca551b63 (bug 1287209)
Backed out changeset cc7503f09572 (bug 1287209)

MozReview-Commit-ID: A5q4SnWzgOa
This commit is contained in:
Phil Ringnalda 2016-10-20 19:23:33 -07:00
parent 6f89530374
commit ad36def0ad
14 changed files with 409 additions and 663 deletions

View File

@ -26,15 +26,6 @@ const POPUP_PRELOAD_TIMEOUT_MS = 200;
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
function isAncestorOrSelf(target, node) {
for (; node; node = node.parentNode) {
if (node === target) {
return true;
}
}
return false;
}
// WeakMap[Extension -> BrowserAction]
var browserActionMap = new WeakMap();
@ -96,8 +87,6 @@ BrowserAction.prototype = {
onDestroyed: document => {
let view = document.getElementById(this.viewId);
if (view) {
this.clearPopup();
CustomizableUI.hidePanelForNode(view);
view.remove();
}
},
@ -209,8 +198,7 @@ BrowserAction.prototype = {
// If we have a pending pre-loaded popup, cancel it after we've waited
// long enough that we can be relatively certain it won't be opening.
if (this.pendingPopup) {
let {node} = this.widget.forWindow(window);
if (isAncestorOrSelf(node, event.originalTarget)) {
if (event.target === this.widget.forWindow(window).node) {
this.pendingPopupTimeout = setTimeout(() => this.clearPopup(),
POPUP_PRELOAD_TIMEOUT_MS);
} else {

View File

@ -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(`<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="10">
<path d="M 0,10 L 10,0 20,10 z" fill="${borderColor}"/>
<path d="M 1,10 L 10,1 19,10 z" fill="${background}"/>
</svg>
`)}")`;
}
}
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(`<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="10">
<path d="M 0,10 L 10,0 20,10 z" fill="${borderColor}"/>
<path d="M 1,10 L 10,1 19,10 z" fill="${background}"/>
</svg>
`)}")`;
}
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,14 +441,10 @@ class PanelPopup extends BasePopup {
super(extension, panel, popupURL, browserStyle);
this.ignoreResizes = false;
this.contentReady.then(() => {
panel.openPopup(imageNode, "bottomcenter topright", 0, 0, false, false);
let event = new this.window.CustomEvent("WebExtPopupLoaded", {
bubbles: true,
detail: {extension},
});
this.browser.dispatchEvent(event);
});
}
@ -371,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;
@ -386,8 +480,6 @@ class ViewPopup extends BasePopup {
super(extension, panel, popupURL, browserStyle, fixedWidth);
this.ignoreResizes = true;
this.attached = false;
this.tempPanel = panel;
@ -425,10 +517,6 @@ class ViewPopup extends BasePopup {
]),
]);
if (!this.destroyed && !this.panel) {
this.destroy();
}
if (this.destroyed) {
return false;
}
@ -461,19 +549,11 @@ 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;
let event = new this.window.CustomEvent("WebExtPopupLoaded", {
bubbles: true,
detail: {extension: this.extension},
});
this.browser.dispatchEvent(event);
return true;
}.bind(this));
}
@ -498,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.

View File

@ -8,6 +8,18 @@ function* openPanel(extension, win = window) {
return yield awaitExtensionPanel(extension, win);
}
function* awaitResize(browser) {
// Debouncing code makes this a bit racy.
// Try to skip the first, early resize, and catch the resize event we're
// looking for, but don't wait longer than a few seconds.
return Promise.race([
BrowserTestUtils.waitForEvent(browser, "WebExtPopupResized")
.then(() => BrowserTestUtils.waitForEvent(browser, "WebExtPopupResized")),
new Promise(resolve => setTimeout(resolve, 5000)),
]);
}
add_task(function* testBrowserActionPopupResize() {
let extension = ExtensionTestUtils.loadExtension({
manifest: {
@ -18,33 +30,33 @@ add_task(function* testBrowserActionPopupResize() {
},
files: {
"popup.html": '<!DOCTYPE html><html><head><meta charset="utf-8"></head></html>',
"popup.html": '<html><head><meta charset="utf-8"></head></html>',
},
});
yield extension.startup();
clickBrowserAction(extension, window);
let browser = yield openPanel(extension);
let panelWindow = browser.contentWindow;
let panelBody = panelWindow.document.body;
function* checkSize(expected) {
let dims = yield promiseContentDimensions(browser);
is(dims.window.innerHeight, expected, `Panel window should be ${expected}px tall`);
is(dims.body.clientHeight, dims.body.scrollHeight,
function checkSize(expected) {
is(panelWindow.innerHeight, expected, `Panel window should be ${expected}px tall`);
is(panelBody.clientHeight, panelBody.scrollHeight,
"Panel body should be tall enough to fit its contents");
// Tolerate if it is 1px too wide, as that may happen with the current resizing method.
ok(Math.abs(dims.window.innerWidth - expected) <= 1, `Panel window should be ${expected}px wide`);
is(dims.body.clientWidth, dims.body.scrollWidth,
ok(Math.abs(panelWindow.innerWidth - expected) <= 1, `Panel window should be ${expected}px wide`);
is(panelBody.clientWidth, panelBody.scrollWidth,
"Panel body should be wide enough to fit its contents");
}
/* eslint-disable mozilla/no-cpows-in-tests */
function setSize(size) {
content.document.body.style.height = `${size}px`;
content.document.body.style.width = `${size}px`;
panelBody.style.height = `${size}px`;
panelBody.style.width = `${size}px`;
}
/* eslint-enable mozilla/no-cpows-in-tests */
let sizes = [
200,
@ -53,8 +65,9 @@ add_task(function* testBrowserActionPopupResize() {
];
for (let size of sizes) {
yield alterContent(browser, setSize, size);
yield checkSize(size);
setSize(size);
yield awaitResize(browser);
checkSize(size);
}
yield closeBrowserAction(extension);
@ -109,32 +122,27 @@ function* testPopupSize(standardsMode, browserWin = window, arrowSide = "top") {
yield extension.startup();
/* eslint-disable mozilla/no-cpows-in-tests */
if (arrowSide == "top") {
// Test the standalone panel for a toolbar button.
let browser = yield openPanel(extension, browserWin);
let win = browser.contentWindow;
let body = win.document.body;
let dims = yield promiseContentDimensions(browser);
let isStandards = win.document.compatMode != "BackCompat";
is(isStandards, standardsMode, "Document has the expected compat mode");
is(dims.isStandards, standardsMode, "Document has the expected compat mode");
let {innerWidth, innerHeight} = win;
let {innerWidth, innerHeight} = dims.window;
body.classList.add("bigger");
yield awaitResize(browser);
dims = yield alterContent(browser, () => {
content.document.body.classList.add("bigger");
});
let win = dims.window;
is(win.innerHeight, innerHeight, "Window height should not change");
ok(win.innerWidth > innerWidth, `Window width should increase (${win.innerWidth} > ${innerWidth})`);
dims = yield alterContent(browser, () => {
content.document.body.classList.remove("bigger");
});
body.classList.remove("bigger");
yield awaitResize(browser);
win = dims.window;
is(win.innerHeight, innerHeight, "Window height should not change");
// The getContentSize calculation is not always reliable to single-pixel
@ -151,6 +159,8 @@ function* testPopupSize(standardsMode, browserWin = window, arrowSide = "top") {
CustomizableUI.addWidgetToArea(widget.id, CustomizableUI.AREA_PANEL);
let browser = yield openPanel(extension, browserWin);
let win = browser.contentWindow;
let body = win.document.body;
let {panel} = browserWin.PanelUI;
let origPanelRect = panel.getBoundingClientRect();
@ -164,7 +174,7 @@ function* testPopupSize(standardsMode, browserWin = window, arrowSide = "top") {
ok(panelRect.top, origPanelRect.top, "Panel has not moved downwards");
ok(panelRect.bottom >= origPanelRect.bottom, `Panel has not shrunk from original size (${panelRect.bottom} >= ${origPanelRect.bottom})`);
let screenBottom = browserWin.screen.availTop + browserWin.screen.availHeight;
let screenBottom = browserWin.screen.availTop + win.screen.availHeight;
let panelBottom = browserWin.mozInnerScreenY + panelRect.bottom;
ok(panelBottom <= screenBottom, `Bottom of popup should be on-screen. (${panelBottom} <= ${screenBottom})`);
} else {
@ -176,33 +186,28 @@ function* testPopupSize(standardsMode, browserWin = window, arrowSide = "top") {
}
};
let isStandards = win.document.compatMode != "BackCompat";
is(isStandards, standardsMode, "Document has the expected compat mode");
// Wait long enough to make sure the initial resize debouncing timer has
// expired.
yield new Promise(resolve => setTimeout(resolve, 100));
let dims = yield promiseContentDimensions(browser);
is(dims.isStandards, standardsMode, "Document has the expected compat mode");
// If the browser's preferred height is smaller than the initial height of the
// panel, then it will still take up the full available vertical space. Even
// so, we need to check that we've gotten the preferred height calculation
// correct, so check that explicitly.
let getHeight = () => parseFloat(browser.style.height);
let {innerWidth, innerHeight} = dims.window;
let {innerWidth, innerHeight} = win;
let height = getHeight();
let setClass = className => {
content.document.body.className = className;
};
info("Increase body children's width. " +
"Expect them to wrap, and the frame to grow vertically rather than widen.");
dims = yield alterContent(browser, setClass, "big");
let win = dims.window;
body.className = "big";
yield awaitResize(browser);
ok(getHeight() > height, `Browser height should increase (${getHeight()} > ${height})`);
@ -215,9 +220,8 @@ function* testPopupSize(standardsMode, browserWin = window, arrowSide = "top") {
info("Increase body children's width and height. " +
"Expect them to wrap, and the frame to grow vertically rather than widen.");
dims = yield alterContent(browser, setClass, "bigger");
win = dims.window;
body.className = "bigger";
yield awaitResize(browser);
ok(getHeight() > height, `Browser height should increase (${getHeight()} > ${height})`);
@ -230,9 +234,8 @@ function* testPopupSize(standardsMode, browserWin = window, arrowSide = "top") {
info("Increase body height beyond the height of the screen. " +
"Expect the panel to grow to accommodate, but not larger than the height of the screen.");
dims = yield alterContent(browser, setClass, "huge");
win = dims.window;
body.className = "huge";
yield awaitResize(browser);
ok(getHeight() > height, `Browser height should increase (${getHeight()} > ${height})`);
@ -245,8 +248,8 @@ function* testPopupSize(standardsMode, browserWin = window, arrowSide = "top") {
info("Restore original styling. Expect original dimensions.");
dims = yield alterContent(browser, setClass, "");
win = dims.window;
body.className = "";
yield awaitResize(browser);
is(getHeight(), height, "Browser height should return to its original value");

View File

@ -2,9 +2,19 @@
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
add_task(function* testPageActionPopupResize() {
let browser;
function* awaitResize(browser) {
// Debouncing code makes this a bit racy.
// Try to skip the first, early resize, and catch the resize event we're
// looking for, but don't wait longer than a few seconds.
return Promise.race([
BrowserTestUtils.waitForEvent(browser, "WebExtPopupResized")
.then(() => BrowserTestUtils.waitForEvent(browser, "WebExtPopupResized")),
new Promise(resolve => setTimeout(resolve, 5000)),
]);
}
add_task(function* testPageActionPopupResize() {
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"page_action": {
@ -13,7 +23,6 @@ add_task(function* testPageActionPopupResize() {
},
},
background: function() {
/* global browser */
browser.tabs.query({active: true, currentWindow: true}, tabs => {
const tabId = tabs[0].id;
@ -33,31 +42,35 @@ 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");
});
function* checkSize(expected) {
let dims = yield promiseContentDimensions(browser);
let {body, root} = dims;
let panelWindow = panelDocument.defaultView;
let panelBody = panelDocument.body.firstChild;
let body = panelDocument.body;
let root = panelDocument.documentElement;
is(dims.window.innerHeight, expected, `Panel window should be ${expected}px tall`);
function checkSize(expected) {
is(panelWindow.innerHeight, expected, `Panel window should be ${expected}px tall`);
is(body.clientHeight, body.scrollHeight,
"Panel body should be tall enough to fit its contents");
is(root.clientHeight, root.scrollHeight,
"Panel root should be tall enough to fit its contents");
// Tolerate if it is 1px too wide, as that may happen with the current resizing method.
ok(Math.abs(dims.window.innerWidth - expected) <= 1, `Panel window should be ${expected}px wide`);
ok(Math.abs(panelWindow.innerWidth - expected) <= 1, `Panel window should be ${expected}px wide`);
is(body.clientWidth, body.scrollWidth,
"Panel body should be wide enough to fit its contents");
}
/* eslint-disable mozilla/no-cpows-in-tests */
function setSize(size) {
let elem = content.document.body.firstChild;
elem.style.height = `${size}px`;
elem.style.width = `${size}px`;
panelBody.style.height = `${size}px`;
panelBody.style.width = `${size}px`;
return BrowserTestUtils.waitForEvent(panelWindow, "resize");
}
/* eslint-enable mozilla/no-cpows-in-tests */
let sizes = [
200,
@ -66,25 +79,22 @@ add_task(function* testPageActionPopupResize() {
];
for (let size of sizes) {
yield alterContent(browser, setSize, size);
yield checkSize(size);
yield setSize(size);
checkSize(size);
}
yield alterContent(browser, setSize, 1400);
let dims = yield promiseContentDimensions(browser);
let {body, root} = dims;
yield setSize(1400);
if (AppConstants.platform == "win") {
ok(dims.window.innerWidth >= 750 && dims.window.innerWidth <= 800,
`Panel window width ${dims.window.innerWidth} is in acceptable range`);
ok(panelWindow.innerWidth >= 750 && panelWindow.innerWidth <= 800,
`Panel window width ${panelWindow.innerWidth} is in acceptable range`);
} else {
is(dims.window.innerWidth, 800, "Panel window width");
is(panelWindow.innerWidth, 800, "Panel window width");
}
ok(body.clientWidth <= 800, `Panel body width ${body.clientWidth} is less than 800`);
is(body.scrollWidth, 1400, "Panel body scroll width");
is(dims.window.innerHeight, 600, "Panel window height");
is(panelWindow.innerHeight, 600, "Panel window height");
ok(root.clientHeight <= 600, `Panel root height (${root.clientHeight}px) is less than 600px`);
is(root.scrollHeight, 1400, "Panel root scroll height");
@ -131,27 +141,29 @@ add_task(function* testPageActionPopupReflow() {
browser = yield awaitExtensionPanel(extension);
/* eslint-disable mozilla/no-cpows-in-tests */
let win = browser.contentWindow;
let body = win.document.body;
let root = win.document.documentElement;
function setSize(size) {
content.document.body.style.fontSize = `${size}px`;
body.style.fontSize = `${size}px`;
return awaitResize(browser);
}
/* eslint-enable mozilla/no-cpows-in-tests */
yield alterContent(browser, setSize, 18);
yield setSize(18);
let dims = yield promiseContentDimensions(browser);
is(dims.window.innerWidth, 800, "Panel window should be 800px wide");
is(dims.body.clientWidth, 800, "Panel body should be 800px wide");
is(dims.body.clientWidth, dims.body.scrollWidth,
is(win.innerWidth, 800, "Panel window should be 800px wide");
is(body.clientWidth, 800, "Panel body should be 800px wide");
is(body.clientWidth, body.scrollWidth,
"Panel body should be wide enough to fit its contents");
ok(dims.window.innerHeight > 36,
`Panel window height (${dims.window.innerHeight}px) should be taller than two lines of text.`);
ok(win.innerHeight > 36,
`Panel window height (${win.innerHeight}px) should be taller than two lines of text.`);
is(dims.body.clientHeight, dims.body.scrollHeight,
is(body.clientHeight, body.scrollHeight,
"Panel body should be tall enough to fit its contents");
is(dims.root.clientHeight, dims.root.scrollHeight,
is(root.clientHeight, root.scrollHeight,
"Panel root should be tall enough to fit its contents");
yield extension.unload();

View File

@ -2,6 +2,18 @@
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
function* awaitResize(browser) {
// Debouncing code makes this a bit racy.
// Try to skip the first, early resize, and catch the resize event we're
// looking for, but don't wait longer than a few seconds.
return Promise.race([
BrowserTestUtils.waitForEvent(browser, "WebExtPopupResized")
.then(() => BrowserTestUtils.waitForEvent(browser, "WebExtPopupResized")),
new Promise(resolve => setTimeout(resolve, 5000)),
]);
}
add_task(function* testPopupBackground() {
let extension = ExtensionTestUtils.loadExtension({
background() {
@ -67,34 +79,26 @@ add_task(function* testPopupBackground() {
isnot(borderIndex, backgroundIndex, "Border and background fills are separate elements");
};
function getBackground(browser) {
return ContentTask.spawn(browser, null, function* () {
return content.getComputedStyle(content.document.body)
.backgroundColor;
});
}
/* eslint-disable mozilla/no-cpows-in-tests */
let setBackground = color => {
content.document.body.style.backgroundColor = color;
};
/* eslint-enable mozilla/no-cpows-in-tests */
let win = browser.contentWindow;
let body = win.document.body;
yield new Promise(resolve => setTimeout(resolve, 100));
info("Test that initial background color is applied");
checkArrow(yield getBackground(browser));
checkArrow(win.getComputedStyle(body).backgroundColor);
info("Test that dynamically-changed background color is applied");
yield alterContent(browser, setBackground, "black");
body.style.backgroundColor = "black";
yield awaitResize(browser);
checkArrow(yield getBackground(browser));
checkArrow(win.getComputedStyle(body).backgroundColor);
info("Test that non-opaque background color results in default styling");
yield alterContent(browser, setBackground, "rgba(1, 2, 3, .9)");
body.style.backgroundColor = "rgba(1, 2, 3, .9)";
yield awaitResize(browser);
checkArrow(null);
}

View File

@ -42,24 +42,17 @@ add_task(function* testPopupBorderRadius() {
let viewNode = browser.parentNode === panel ? browser : browser.parentNode;
let viewStyle = getComputedStyle(viewNode);
let props = ["borderTopLeftRadius", "borderTopRightRadius",
"borderBottomRightRadius", "borderBottomLeftRadius"];
let win = browser.contentWindow;
let bodyStyle = win.getComputedStyle(win.document.body);
/* eslint-disable mozilla/no-cpows-in-tests */
let bodyStyle = yield ContentTask.spawn(browser, props, function* (props) {
let bodyStyle = content.getComputedStyle(content.document.body);
return new Map(props.map(prop => [prop, bodyStyle[prop]]));
});
/* eslint-enable mozilla/no-cpows-in-tests */
for (let prop of props) {
for (let prop of ["borderTopLeftRadius", "borderTopRightRadius",
"borderBottomRightRadius", "borderBottomLeftRadius"]) {
if (standAlone) {
is(viewStyle[prop], panelStyle[prop], `Panel and view ${prop} should be the same`);
is(bodyStyle.get(prop), panelStyle[prop], `Panel and body ${prop} should be the same`);
is(bodyStyle[prop], panelStyle[prop], `Panel and body ${prop} should be the same`);
} else {
is(viewStyle[prop], "0px", `View node ${prop} should be 0px`);
is(bodyStyle.get(prop), "0px", `Body node ${prop} should be 0px`);
is(bodyStyle[prop], "0px", `Body node ${prop} should be 0px`);
}
}
}

View File

@ -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");
});

View File

@ -11,8 +11,7 @@
* openContextMenu closeContextMenu
* openExtensionContextMenu closeExtensionContextMenu
* imageBuffer getListStyleImage getPanelForNode
* awaitExtensionPanel awaitPopupResize
* promiseContentDimensions alterContent
* awaitExtensionPanel
*/
var {AppConstants} = Cu.import("resource://gre/modules/AppConstants.jsm");
@ -85,44 +84,6 @@ function promisePopupHidden(popup) {
});
}
function promiseContentDimensions(browser) {
return ContentTask.spawn(browser, null, function* () {
function copyProps(obj, props) {
let res = {};
for (let prop of props) {
res[prop] = obj[prop];
}
return res;
}
return {
window: copyProps(content,
["innerWidth", "innerHeight", "outerWidth", "outerHeight",
"scrollX", "scrollY", "scrollMaxX", "scrollMaxY"]),
body: copyProps(content.document.body,
["clientWidth", "clientHeight", "scrollWidth", "scrollHeight"]),
root: copyProps(content.document.documentElement,
["clientWidth", "clientHeight", "scrollWidth", "scrollHeight"]),
isStandards: content.document.compatMode !== "BackCompat",
};
});
}
function* awaitPopupResize(browser) {
return BrowserTestUtils.waitForEvent(browser, "WebExtPopupResized",
event => event.detail === "delayed");
}
function alterContent(browser, task, arg = null) {
return Promise.all([
ContentTask.spawn(browser, arg, task),
awaitPopupResize(browser),
]).then(() => {
return promiseContentDimensions(browser);
});
}
function getPanelForNode(node) {
while (node.localName != "panel") {
node = node.parentNode;
@ -130,10 +91,19 @@ function getPanelForNode(node) {
return node;
}
var awaitExtensionPanel = Task.async(function* (extension, win = window) {
let {originalTarget: browser} = yield BrowserTestUtils.waitForEvent(
win.document, "WebExtPopupLoaded", true,
event => event.detail.extension.id === extension.id);
var awaitExtensionPanel = Task.async(function* (extension, win = window, filename = "popup.html") {
let {target} = yield BrowserTestUtils.waitForEvent(win.document, "load", true, (event) => {
return event.target.location && event.target.location.href.endsWith(filename);
});
let browser = target.defaultView
.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDocShell)
.chromeEventHandler;
if (browser.matches(".webextension-preload-browser")) {
let event = yield BrowserTestUtils.waitForEvent(browser, "SwapDocShells");
browser = event.detail;
}
yield promisePopupShown(getPanelForNode(browser));

View File

@ -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();
}

View File

@ -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");

View File

@ -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<Event>}
*/
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,

View File

@ -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 <browsers>, we currently receive a load
// event on the <browser> 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);

View File

@ -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

View File

@ -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 <browser>s as if they were top-level or
* remote.
*
* @param {Element} browser
* A XUL <browser> 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 <browser>. 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);
});