Bug 1576918 - Port PageStyle actor, which handles alternative stylesheets, to JSWindowActors for Fission compatibility. r=mconley,Gijs

Differential Revision: https://phabricator.services.mozilla.com/D46861

--HG--
extra : moz-landing-system : lando
This commit is contained in:
James Jahns 2019-10-24 19:53:42 +00:00
Родитель d5a1276fec
Коммит cb5acc50ed
8 изменённых файлов: 190 добавлений и 113 удалений

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

@ -1,103 +1,90 @@
/* 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 { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
var EXPORTED_SYMBOLS = ["PageStyleChild"];
const { ActorChild } = ChromeUtils.import(
"resource://gre/modules/ActorChild.jsm"
);
class PageStyleChild extends JSWindowActorChild {
handleEvent(event) {
// On page show, tell the parent all of the stylesheets this document has.
if (event.type == "pageshow") {
// If we are in the topmost browsing context,
// delete the stylesheets from the previous page.
if (this.browsingContext.top === this.browsingContext) {
this.sendAsyncMessage("PageStyle:Clear");
}
class PageStyleChild extends ActorChild {
getViewer(content) {
return content.docShell.contentViewer;
}
let window = event.target.ownerGlobal;
window.requestIdleCallback(() => {
if (!window || window.closed) {
return;
}
let styleSheets = Array.from(this.document.styleSheets);
let filteredStyleSheets = this._filterStyleSheets(styleSheets, window);
sendStyleSheetInfo(mm) {
let content = mm.content;
content.requestIdleCallback(() => {
let filteredStyleSheets = this._filterStyleSheets(
this.getAllStyleSheets(content),
content
);
mm.sendAsyncMessage("PageStyle:StyleSheets", {
filteredStyleSheets,
authorStyleDisabled: this.getViewer(content).authorStyleDisabled,
preferredStyleSheetSet: content.document.preferredStyleSheetSet,
this.sendAsyncMessage("PageStyle:Add", {
filteredStyleSheets,
authorStyleDisabled: this.docShell.contentViewer.authorStyleDisabled,
preferredStyleSheetSet: this.document.preferredStyleSheetSet,
});
});
});
}
getAllStyleSheets(frameset) {
let selfSheets = Array.from(frameset.document.styleSheets);
let subSheets = Array.from(frameset.frames, frame =>
this.getAllStyleSheets(frame)
);
return selfSheets.concat(...subSheets);
}
}
receiveMessage(msg) {
let content = msg.target.content;
switch (msg.name) {
// Sent when the page's enabled style sheet is changed.
case "PageStyle:Switch":
this.getViewer(content).authorStyleDisabled = false;
this._stylesheetSwitchAll(content, msg.data.title);
this.docShell.contentViewer.authorStyleDisabled = false;
this._switchStylesheet(msg.data.title);
break;
// Sent when "No Style" is chosen.
case "PageStyle:Disable":
this.getViewer(content).authorStyleDisabled = true;
this.docShell.contentViewer.authorStyleDisabled = true;
break;
}
this.sendStyleSheetInfo(msg.target);
}
handleEvent(event) {
let win = event.target.ownerGlobal;
if (win != win.top) {
return;
/**
* Switch the stylesheet so that only the sheet with the given title is enabled.
*/
_switchStylesheet(title) {
let docStyleSheets = this.document.styleSheets;
// Does this doc contain a stylesheet with this title?
// If not, it's a subframe's stylesheet that's being changed,
// so no need to disable stylesheets here.
let docContainsStyleSheet = false;
for (let docStyleSheet of docStyleSheets) {
if (docStyleSheet.title === title) {
docContainsStyleSheet = true;
break;
}
}
let mm = win.docShell.messageManager;
this.sendStyleSheetInfo(mm);
}
_stylesheetSwitchAll(frameset, title) {
if (!title || this._stylesheetInFrame(frameset, title)) {
this._stylesheetSwitchFrame(frameset, title);
}
for (let i = 0; i < frameset.frames.length; i++) {
// Recurse into sub-frames.
this._stylesheetSwitchAll(frameset.frames[i], title);
}
}
_stylesheetSwitchFrame(frame, title) {
var docStyleSheets = frame.document.styleSheets;
for (let i = 0; i < docStyleSheets.length; ++i) {
let docStyleSheet = docStyleSheets[i];
for (let docStyleSheet of docStyleSheets) {
if (docStyleSheet.title) {
docStyleSheet.disabled = docStyleSheet.title != title;
if (docContainsStyleSheet) {
docStyleSheet.disabled = docStyleSheet.title !== title;
}
} else if (docStyleSheet.disabled) {
docStyleSheet.disabled = false;
}
}
}
_stylesheetInFrame(frame, title) {
return Array.from(frame.document.styleSheets).some(
styleSheet => styleSheet.title == title
);
}
/**
* Filter the stylesheets that actually apply to this webpage.
* @param styleSheets The list of stylesheets from the document.
* @param content The window object that the webpage lives in.
*/
_filterStyleSheets(styleSheets, content) {
let result = [];
// Only stylesheets with a title can act as an alternative stylesheet.
for (let currentStyleSheet of styleSheets) {
if (!currentStyleSheet.title) {
continue;
@ -115,7 +102,7 @@ class PageStyleChild extends ActorChild {
try {
if (
!currentStyleSheet.ownerNode ||
// special-case style nodes, which have no href
// Special-case style nodes, which have no href.
currentStyleSheet.ownerNode.nodeName.toLowerCase() != "style"
) {
URI = Services.io.newURI(currentStyleSheet.href);

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

@ -0,0 +1,33 @@
/* 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";
var EXPORTED_SYMBOLS = ["PageStyleParent"];
class PageStyleParent extends JSWindowActorParent {
receiveMessage(msg) {
// The top browser.
let browser = this.browsingContext.top.embedderElement;
let permanentKey = browser.permanentKey;
let window = browser.ownerGlobal;
if (window.closed) {
return;
}
let styleMenu = window.gPageStyleMenu;
switch (msg.name) {
case "PageStyle:Add":
if (browser.outerBrowser) {
// We are in RDM mode and we probably
// want to work with the outer browser.
browser = browser.outerBrowser;
}
styleMenu.addBrowserStyleSheets(msg.data, permanentKey);
break;
case "PageStyle:Clear":
styleMenu.clearBrowserStyleSheets(permanentKey);
break;
}
}
}

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

@ -41,6 +41,7 @@ FINAL_TARGET_FILES.actors += [
'OfflineAppsChild.jsm',
'PageInfoChild.jsm',
'PageStyleChild.jsm',
'PageStyleParent.jsm',
'PluginChild.jsm',
'PluginParent.jsm',
'PromptParent.jsm',

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

@ -1808,7 +1808,6 @@ var gBrowserInit = {
// message sent between when the frame script is loaded and when
// the listener is registered.
DOMEventHandler.init();
gPageStyleMenu.init();
LanguageDetectionListener.init();
BrowserOnClick.init();
CaptivePortalWatcher.init();
@ -7730,17 +7729,28 @@ var gPageStyleMenu = {
//
_pageStyleSheets: new WeakMap(),
init() {
let mm = window.messageManager;
mm.addMessageListener("PageStyle:StyleSheets", msg => {
if (msg.target.permanentKey) {
this._pageStyleSheets.set(msg.target.permanentKey, msg.data);
}
});
/**
* Add/append styleSheets to the _pageStyleSheets weakmap.
* @param styleSheets
* The stylesheets to add, including the preferred
* stylesheet set for this document.
* @param permanentKey
* The permanent key of the browser that
* these stylesheets come from.
*/
addBrowserStyleSheets(styleSheets, permanentKey) {
let sheetData = this._pageStyleSheets.get(permanentKey);
if (!sheetData) {
this._pageStyleSheets.set(permanentKey, styleSheets);
return;
}
sheetData.filteredStyleSheets.push(...styleSheets.filteredStyleSheets);
sheetData.preferredStyleSheetSet =
sheetData.preferredStyleSheetSet || styleSheets.preferredStyleSheetSet;
},
/**
* Returns an array of Objects representing stylesheets in a
* Return an array of Objects representing stylesheets in a
* browser. Note that the pageshow event needs to fire in content
* before this information will be available.
*
@ -7764,6 +7774,10 @@ var gPageStyleMenu = {
return data.filteredStyleSheets;
},
clearBrowserStyleSheets(permanentKey) {
this._pageStyleSheets.delete(permanentKey);
},
_getStyleSheetInfo(browser) {
let data = this._pageStyleSheets.get(browser.permanentKey);
if (!data) {
@ -7832,14 +7846,56 @@ var gPageStyleMenu = {
sep.hidden = (noStyle.hidden && persistentOnly.hidden) || !haveAltSheets;
},
switchStyleSheet(title) {
let mm = gBrowser.selectedBrowser.messageManager;
mm.sendAsyncMessage("PageStyle:Switch", { title });
/**
* Send a message to all PageStyleParents by walking the BrowsingContext tree.
* @param message
* The string message to send to each PageStyleChild.
* @param data
* The data to send to each PageStyleChild within the message.
*/
_sendMessageToAll(message, data) {
let contextsToVisit = [gBrowser.selectedBrowser.browsingContext];
while (contextsToVisit.length) {
let currentContext = contextsToVisit.pop();
let global = currentContext.currentWindowGlobal;
if (!global) {
continue;
}
let actor = global.getActor("PageStyle");
actor.sendAsyncMessage(message, data);
contextsToVisit.push(...currentContext.getChildren());
}
},
/**
* Switch the stylesheet of all documents in the current browser.
* @param title The title of the stylesheet to switch to.
*/
switchStyleSheet(title) {
let { permanentKey } = gBrowser.selectedBrowser;
let sheetData = this._pageStyleSheets.get(permanentKey);
if (sheetData && sheetData.filteredStyleSheets) {
sheetData.authorStyleDisabled = false;
for (let sheet of sheetData.filteredStyleSheets) {
sheet.disabled = sheet.title !== title;
}
}
this._sendMessageToAll("PageStyle:Switch", { title });
},
/**
* Disable all stylesheets. Called with View > Page Style > No Style.
*/
disableStyle() {
let mm = gBrowser.selectedBrowser.messageManager;
mm.sendAsyncMessage("PageStyle:Disable");
let { permanentKey } = gBrowser.selectedBrowser;
let sheetData = this._pageStyleSheets.get(permanentKey);
if (sheetData) {
sheetData.authorStyleDisabled = true;
}
this._sendMessageToAll("PageStyle:Disable", {});
},
};

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

@ -15,7 +15,7 @@ add_task(async function() {
);
let browser = tab.linkedBrowser;
await BrowserTestUtils.loadURI(browser, PAGE);
await promiseStylesheetsUpdated(browser);
await promiseStylesheetsLoaded(tab, 17);
let menupopup = document.getElementById("pageStyleMenu").menupopup;
gPageStyleMenu.fillPopup(menupopup);

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

@ -14,9 +14,8 @@ add_task(async function() {
false
);
let browser = tab.linkedBrowser;
await BrowserTestUtils.loadURI(browser, PAGE);
await promiseStylesheetsUpdated(browser);
await promiseStylesheetsLoaded(tab, 17);
let menupopup = document.getElementById("pageStyleMenu").menupopup;
gPageStyleMenu.fillPopup(menupopup);
@ -34,10 +33,6 @@ add_task(async function() {
let target = menupopup.querySelector("menuitem[label='1']");
target.click();
// Now we need to wait for the content process to send its stylesheet
// update for the selected tab to the parent.
await promiseStylesheetsUpdated(browser);
gPageStyleMenu.fillPopup(menupopup);
// gPageStyleMenu empties out the menu between opens, so we need
// to get a new reference to the selected menuitem

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

@ -530,18 +530,20 @@ async function loadBadCertPage(url) {
}
/**
* Waits for the message from content to update the Page Style menu.
* Waits for the stylesheets to be loaded into the browser menu.
*
* @param browser
* The <xul:browser> to wait for.
* @param tab
* The tab that contains the webpage we're testing.
* @param styleSheetCount
* How many stylesheets we expect to be loaded.
* @return Promise
*/
async function promiseStylesheetsUpdated(browser) {
await BrowserTestUtils.waitForMessage(
browser.messageManager,
"PageStyle:StyleSheets"
);
// Resolve on the next tick of the event loop to give the Page Style
// menu code an opportunity to update.
await new Promise(resolve => Services.tm.dispatchToMainThread(resolve));
async function promiseStylesheetsLoaded(tab, styleSheetCount) {
let styleMenu = tab.ownerGlobal.gPageStyleMenu;
let permanentKey = tab.permanentKey;
await TestUtils.waitForCondition(() => {
let menu = styleMenu._pageStyleSheets.get(permanentKey);
return menu && menu.filteredStyleSheets.length >= styleSheetCount;
}, "waiting for style sheets to load");
}

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

@ -104,6 +104,23 @@ let ACTORS = {
allFrames: true,
},
PageStyle: {
parent: {
moduleURI: "resource:///actors/PageStyleParent.jsm",
},
child: {
moduleURI: "resource:///actors/PageStyleChild.jsm",
events: {
pageshow: {},
},
},
// Only matching web pages, as opposed to internal about:, chrome: or
// resource: pages. See https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Match_patterns
matches: ["*://*/*"],
allFrames: true,
},
Plugin: {
parent: {
moduleURI: "resource:///actors/PluginParent.jsm",
@ -313,20 +330,6 @@ let LEGACY_ACTORS = {
},
},
PageStyle: {
child: {
module: "resource:///actors/PageStyleChild.jsm",
group: "browsers",
events: {
pageshow: {},
},
messages: ["PageStyle:Switch", "PageStyle:Disable"],
// Only matching web pages, as opposed to internal about:, chrome: or
// resource: pages. See https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Match_patterns
matches: ["*://*/*"],
},
},
SearchTelemetry: {
child: {
module: "resource:///actors/SearchTelemetryChild.jsm",