mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 21:01:08 +00:00
15c1a58822
Differential Revision: https://phabricator.services.mozilla.com/D194370
200 lines
5.9 KiB
JavaScript
200 lines
5.9 KiB
JavaScript
/* 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/. */
|
|
|
|
export class PageStyleChild extends JSWindowActorChild {
|
|
actorCreated() {
|
|
// C++ can create the actor and call us here once an "interesting" link
|
|
// element gets added to the DOM. If pageload hasn't finished yet, just
|
|
// wait for that by doing nothing; the actor registration event
|
|
// listeners will ensure we get the pageshow event.
|
|
// It is also possible we get created in response to the parent
|
|
// sending us a message - in that case, it's still worth doing the
|
|
// same things here:
|
|
if (!this.browsingContext || !this.browsingContext.associatedWindow) {
|
|
return;
|
|
}
|
|
let { document } = this.browsingContext.associatedWindow;
|
|
if (document.readyState != "complete") {
|
|
return;
|
|
}
|
|
// If we've already seen a pageshow, send stylesheets now:
|
|
this.#collectAndSendSheets();
|
|
}
|
|
|
|
handleEvent(event) {
|
|
if (event?.type != "pageshow") {
|
|
throw new Error("Unexpected event!");
|
|
}
|
|
|
|
// On page show, tell the parent all of the stylesheets this document
|
|
// has. 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");
|
|
}
|
|
|
|
this.#collectAndSendSheets();
|
|
}
|
|
|
|
receiveMessage(msg) {
|
|
switch (msg.name) {
|
|
// Sent when the page's enabled style sheet is changed.
|
|
case "PageStyle:Switch":
|
|
if (this.browsingContext.top == this.browsingContext) {
|
|
this.browsingContext.authorStyleDisabledDefault = false;
|
|
}
|
|
this.docShell.docViewer.authorStyleDisabled = false;
|
|
this._switchStylesheet(msg.data.title);
|
|
break;
|
|
// Sent when "No Style" is chosen.
|
|
case "PageStyle:Disable":
|
|
if (this.browsingContext.top == this.browsingContext) {
|
|
this.browsingContext.authorStyleDisabledDefault = true;
|
|
}
|
|
this.docShell.docViewer.authorStyleDisabled = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns links that would represent stylesheets once loaded.
|
|
*/
|
|
_collectLinks(document) {
|
|
let result = [];
|
|
for (let link of document.querySelectorAll("link")) {
|
|
if (link.namespaceURI !== "http://www.w3.org/1999/xhtml") {
|
|
continue;
|
|
}
|
|
let isStyleSheet = Array.from(link.relList).some(
|
|
r => r.toLowerCase() == "stylesheet"
|
|
);
|
|
if (!isStyleSheet) {
|
|
continue;
|
|
}
|
|
if (!link.href) {
|
|
continue;
|
|
}
|
|
result.push(link);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Switch the stylesheet so that only the sheet with the given title is enabled.
|
|
*/
|
|
_switchStylesheet(title) {
|
|
let document = this.document;
|
|
let docStyleSheets = Array.from(document.styleSheets);
|
|
let links;
|
|
|
|
// 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 = !title;
|
|
if (title) {
|
|
links = this._collectLinks(document);
|
|
docContainsStyleSheet =
|
|
docStyleSheets.some(sheet => sheet.title == title) ||
|
|
links.some(link => link.title == title);
|
|
}
|
|
|
|
for (let sheet of docStyleSheets) {
|
|
if (sheet.title) {
|
|
if (docContainsStyleSheet) {
|
|
sheet.disabled = sheet.title !== title;
|
|
}
|
|
} else if (sheet.disabled) {
|
|
sheet.disabled = false;
|
|
}
|
|
}
|
|
|
|
// If there's no title, we just need to disable potentially-enabled
|
|
// stylesheets via document.styleSheets, so no need to deal with links
|
|
// there.
|
|
//
|
|
// We don't want to enable <link rel="stylesheet" disabled> without title
|
|
// that were not enabled before.
|
|
if (title) {
|
|
for (let link of links) {
|
|
if (link.title == title && link.disabled) {
|
|
link.disabled = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#collectAndSendSheets() {
|
|
let window = this.browsingContext.associatedWindow;
|
|
window.requestIdleCallback(() => {
|
|
if (!window || window.closed) {
|
|
return;
|
|
}
|
|
let filteredStyleSheets = this.#collectStyleSheets(window);
|
|
this.sendAsyncMessage("PageStyle:Add", {
|
|
filteredStyleSheets,
|
|
preferredStyleSheetSet: this.document.preferredStyleSheetSet,
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get the stylesheets that have a title (and thus can be switched) in this
|
|
* webpage.
|
|
*
|
|
* @param content The window object for the page.
|
|
*/
|
|
#collectStyleSheets(content) {
|
|
let result = [];
|
|
let document = content.document;
|
|
|
|
for (let sheet of document.styleSheets) {
|
|
let title = sheet.title;
|
|
if (!title) {
|
|
// Sheets without a title are not alternates.
|
|
continue;
|
|
}
|
|
|
|
// Skip any stylesheets that don't match the screen media type.
|
|
let media = sheet.media.mediaText;
|
|
if (media && !content.matchMedia(media).matches) {
|
|
continue;
|
|
}
|
|
|
|
// We skip links here, see below.
|
|
if (
|
|
sheet.href &&
|
|
sheet.ownerNode &&
|
|
sheet.ownerNode.nodeName.toLowerCase() == "link"
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
let disabled = sheet.disabled;
|
|
result.push({ title, disabled });
|
|
}
|
|
|
|
// This is tricky, because we can't just rely on document.styleSheets, as
|
|
// `<link disabled>` makes the sheet don't appear there at all.
|
|
for (let link of this._collectLinks(document)) {
|
|
let title = link.title;
|
|
if (!title) {
|
|
continue;
|
|
}
|
|
|
|
let media = link.media;
|
|
if (media && !content.matchMedia(media).matches) {
|
|
continue;
|
|
}
|
|
|
|
let disabled =
|
|
link.disabled ||
|
|
!!link.sheet?.disabled ||
|
|
document.preferredStyleSheetSet != title;
|
|
result.push({ title, disabled });
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|