summaryrefslogtreecommitdiffstats
path: root/browser/actors/PageStyleChild.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'browser/actors/PageStyleChild.sys.mjs')
-rw-r--r--browser/actors/PageStyleChild.sys.mjs199
1 files changed, 199 insertions, 0 deletions
diff --git a/browser/actors/PageStyleChild.sys.mjs b/browser/actors/PageStyleChild.sys.mjs
new file mode 100644
index 0000000000..f7d08bab08
--- /dev/null
+++ b/browser/actors/PageStyleChild.sys.mjs
@@ -0,0 +1,199 @@
+/* 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.contentViewer.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.contentViewer.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;
+ }
+}