Backed out changeset 68806639c031 (bug 1287209) for failing browser-chrome test browser_ext_popup_corners.js. r=backout

This commit is contained in:
Sebastian Hengst 2016-10-20 09:17:03 +02:00
Родитель 304f2d4ce5
Коммит 0c23c32b56
11 изменённых файлов: 275 добавлений и 484 удалений

Просмотреть файл

@ -87,7 +87,6 @@ BrowserAction.prototype = {
onDestroyed: document => {
let view = document.getElementById(this.viewId);
if (view) {
CustomizableUI.hidePanelForNode(view);
view.remove();
}
},

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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