1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 export class PageStyleChild extends JSWindowActorChild {
7 // C++ can create the actor and call us here once an "interesting" link
8 // element gets added to the DOM. If pageload hasn't finished yet, just
9 // wait for that by doing nothing; the actor registration event
10 // listeners will ensure we get the pageshow event.
11 // It is also possible we get created in response to the parent
12 // sending us a message - in that case, it's still worth doing the
14 if (!this.browsingContext || !this.browsingContext.associatedWindow) {
17 let { document } = this.browsingContext.associatedWindow;
18 if (document.readyState != "complete") {
21 // If we've already seen a pageshow, send stylesheets now:
22 this.#collectAndSendSheets();
26 if (event?.type != "pageshow") {
27 throw new Error("Unexpected event!");
30 // On page show, tell the parent all of the stylesheets this document
31 // has. If we are in the topmost browsing context, delete the stylesheets
32 // from the previous page.
33 if (this.browsingContext.top === this.browsingContext) {
34 this.sendAsyncMessage("PageStyle:Clear");
37 this.#collectAndSendSheets();
42 // Sent when the page's enabled style sheet is changed.
43 case "PageStyle:Switch":
44 if (this.browsingContext.top == this.browsingContext) {
45 this.browsingContext.authorStyleDisabledDefault = false;
47 this.docShell.docViewer.authorStyleDisabled = false;
48 this._switchStylesheet(msg.data.title);
50 // Sent when "No Style" is chosen.
51 case "PageStyle:Disable":
52 if (this.browsingContext.top == this.browsingContext) {
53 this.browsingContext.authorStyleDisabledDefault = true;
55 this.docShell.docViewer.authorStyleDisabled = true;
61 * Returns links that would represent stylesheets once loaded.
63 _collectLinks(document) {
65 for (let link of document.querySelectorAll("link")) {
66 if (link.namespaceURI !== "http://www.w3.org/1999/xhtml") {
69 let isStyleSheet = Array.from(link.relList).some(
70 r => r.toLowerCase() == "stylesheet"
84 * Switch the stylesheet so that only the sheet with the given title is enabled.
86 _switchStylesheet(title) {
87 let document = this.document;
88 let docStyleSheets = Array.from(document.styleSheets);
91 // Does this doc contain a stylesheet with this title?
92 // If not, it's a subframe's stylesheet that's being changed,
93 // so no need to disable stylesheets here.
94 let docContainsStyleSheet = !title;
96 links = this._collectLinks(document);
97 docContainsStyleSheet =
98 docStyleSheets.some(sheet => sheet.title == title) ||
99 links.some(link => link.title == title);
102 for (let sheet of docStyleSheets) {
104 if (docContainsStyleSheet) {
105 sheet.disabled = sheet.title !== title;
107 } else if (sheet.disabled) {
108 sheet.disabled = false;
112 // If there's no title, we just need to disable potentially-enabled
113 // stylesheets via document.styleSheets, so no need to deal with links
116 // We don't want to enable <link rel="stylesheet" disabled> without title
117 // that were not enabled before.
119 for (let link of links) {
120 if (link.title == title && link.disabled) {
121 link.disabled = false;
127 #collectAndSendSheets() {
128 let window = this.browsingContext.associatedWindow;
129 window.requestIdleCallback(() => {
130 if (!window || window.closed) {
133 let filteredStyleSheets = this.#collectStyleSheets(window);
134 this.sendAsyncMessage("PageStyle:Add", {
136 preferredStyleSheetSet: this.document.preferredStyleSheetSet,
142 * Get the stylesheets that have a title (and thus can be switched) in this
145 * @param content The window object for the page.
147 #collectStyleSheets(content) {
149 let document = content.document;
151 for (let sheet of document.styleSheets) {
152 let title = sheet.title;
154 // Sheets without a title are not alternates.
158 // Skip any stylesheets that don't match the screen media type.
159 let media = sheet.media.mediaText;
160 if (media && !content.matchMedia(media).matches) {
164 // We skip links here, see below.
168 sheet.ownerNode.nodeName.toLowerCase() == "link"
173 let disabled = sheet.disabled;
174 result.push({ title, disabled });
177 // This is tricky, because we can't just rely on document.styleSheets, as
178 // `<link disabled>` makes the sheet don't appear there at all.
179 for (let link of this._collectLinks(document)) {
180 let title = link.title;
185 let media = link.media;
186 if (media && !content.matchMedia(media).matches) {
192 !!link.sheet?.disabled ||
193 document.preferredStyleSheetSet != title;
194 result.push({ title, disabled });