tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

PageStyleChild.sys.mjs (6008B)


      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/. */
      4 
      5 export class PageStyleChild extends JSWindowActorChild {
      6  actorCreated() {
      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
     13    // same things here:
     14    if (!this.browsingContext || !this.browsingContext.associatedWindow) {
     15      return;
     16    }
     17    let { document } = this.browsingContext.associatedWindow;
     18    if (document.readyState != "complete") {
     19      return;
     20    }
     21    // If we've already seen a pageshow, send stylesheets now:
     22    this.#collectAndSendSheets();
     23  }
     24 
     25  handleEvent(event) {
     26    if (event?.type != "pageshow") {
     27      throw new Error("Unexpected event!");
     28    }
     29 
     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");
     35    }
     36 
     37    this.#collectAndSendSheets();
     38  }
     39 
     40  receiveMessage(msg) {
     41    switch (msg.name) {
     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;
     46        }
     47        this.docShell.docViewer.authorStyleDisabled = false;
     48        this._switchStylesheet(msg.data.title);
     49        break;
     50      // Sent when "No Style" is chosen.
     51      case "PageStyle:Disable":
     52        if (this.browsingContext.top == this.browsingContext) {
     53          this.browsingContext.authorStyleDisabledDefault = true;
     54        }
     55        this.docShell.docViewer.authorStyleDisabled = true;
     56        break;
     57    }
     58  }
     59 
     60  /**
     61   * Returns links that would represent stylesheets once loaded.
     62   */
     63  _collectLinks(document) {
     64    let result = [];
     65    for (let link of document.querySelectorAll("link")) {
     66      if (link.namespaceURI !== "http://www.w3.org/1999/xhtml") {
     67        continue;
     68      }
     69      let isStyleSheet = Array.from(link.relList).some(
     70        r => r.toLowerCase() == "stylesheet"
     71      );
     72      if (!isStyleSheet) {
     73        continue;
     74      }
     75      if (!link.href) {
     76        continue;
     77      }
     78      result.push(link);
     79    }
     80    return result;
     81  }
     82 
     83  /**
     84   * Switch the stylesheet so that only the sheet with the given title is enabled.
     85   */
     86  _switchStylesheet(title) {
     87    let document = this.document;
     88    let docStyleSheets = Array.from(document.styleSheets);
     89    let links;
     90 
     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;
     95    if (title) {
     96      links = this._collectLinks(document);
     97      docContainsStyleSheet =
     98        docStyleSheets.some(sheet => sheet.title == title) ||
     99        links.some(link => link.title == title);
    100    }
    101 
    102    for (let sheet of docStyleSheets) {
    103      if (sheet.title) {
    104        if (docContainsStyleSheet) {
    105          sheet.disabled = sheet.title !== title;
    106        }
    107      } else if (sheet.disabled) {
    108        sheet.disabled = false;
    109      }
    110    }
    111 
    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
    114    // there.
    115    //
    116    // We don't want to enable <link rel="stylesheet" disabled> without title
    117    // that were not enabled before.
    118    if (title) {
    119      for (let link of links) {
    120        if (link.title == title && link.disabled) {
    121          link.disabled = false;
    122        }
    123      }
    124    }
    125  }
    126 
    127  #collectAndSendSheets() {
    128    let window = this.browsingContext.associatedWindow;
    129    window.requestIdleCallback(() => {
    130      if (!window || window.closed) {
    131        return;
    132      }
    133      let filteredStyleSheets = this.#collectStyleSheets(window);
    134      this.sendAsyncMessage("PageStyle:Add", {
    135        filteredStyleSheets,
    136        preferredStyleSheetSet: this.document.preferredStyleSheetSet,
    137      });
    138    });
    139  }
    140 
    141  /**
    142   * Get the stylesheets that have a title (and thus can be switched) in this
    143   * webpage.
    144   *
    145   * @param content     The window object for the page.
    146   */
    147  #collectStyleSheets(content) {
    148    let result = [];
    149    let document = content.document;
    150 
    151    for (let sheet of document.styleSheets) {
    152      let title = sheet.title;
    153      if (!title) {
    154        // Sheets without a title are not alternates.
    155        continue;
    156      }
    157 
    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) {
    161        continue;
    162      }
    163 
    164      // We skip links here, see below.
    165      if (
    166        sheet.href &&
    167        sheet.ownerNode &&
    168        sheet.ownerNode.nodeName.toLowerCase() == "link"
    169      ) {
    170        continue;
    171      }
    172 
    173      let disabled = sheet.disabled;
    174      result.push({ title, disabled });
    175    }
    176 
    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;
    181      if (!title) {
    182        continue;
    183      }
    184 
    185      let media = link.media;
    186      if (media && !content.matchMedia(media).matches) {
    187        continue;
    188      }
    189 
    190      let disabled =
    191        link.disabled ||
    192        !!link.sheet?.disabled ||
    193        document.preferredStyleSheetSet != title;
    194      result.push({ title, disabled });
    195    }
    196 
    197    return result;
    198  }
    199 }