browser/modules/PageStyleHandler.jsm
author Boris Zbarsky <bzbarsky@mit.edu>
Thu, 02 Aug 2018 23:49:09 -0400
changeset 430085 cd76057ca094e27d7c633a1e9565f79cd04583c4
parent 430017 90193bf8d83d86f9e4f3a07de1c258dc4a77e008
permissions -rw-r--r--
Bug 1479569 part 2. Use the new messageManager getter on docshell. r=kmag I generally tried to preserve the behavior of consumers where they treated an exception from getInterface(Ci.nsIContentFrameMessageManager) as a signal to use some sort of fallback. I did change the behavior of consumers that walked up to the root same-type docshell before getting the message manager to just get it directly from the docshell they have. Please review those parts carefully, and let me know if you want me to ask some subject area experts to review those.

/* 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/. */

ChromeUtils.import("resource://gre/modules/Services.jsm");

var EXPORTED_SYMBOLS = ["PageStyleHandler"];

var PageStyleHandler = {
  getViewer(content) {
    return content.docShell.contentViewer;
  },

  sendStyleSheetInfo(mm) {
    let content = mm.content;
    let filteredStyleSheets = this._filterStyleSheets(this.getAllStyleSheets(content), content);

    mm.sendAsyncMessage("PageStyle:StyleSheets", {
      filteredStyleSheets,
      authorStyleDisabled: this.getViewer(content).authorStyleDisabled,
      preferredStyleSheetSet: content.document.preferredStyleSheetSet
    });
  },

  getAllStyleSheets(frameset) {
    let selfSheets = Array.slice(frameset.document.styleSheets);
    let subSheets = Array.map(frameset.frames, frame => this.getAllStyleSheets(frame));
    return selfSheets.concat(...subSheets);
  },

  receiveMessage(msg) {
    let content = msg.target.content;
    switch (msg.name) {
      case "PageStyle:Switch":
        this.getViewer(content).authorStyleDisabled = false;
        this._stylesheetSwitchAll(content, msg.data.title);
        break;

      case "PageStyle:Disable":
        this.getViewer(content).authorStyleDisabled = true;
        break;
    }

    this.sendStyleSheetInfo(msg.target);
  },

  handleEvent(event) {
    let win = event.target.ownerGlobal;
    if (win != win.top) {
      return;
    }

    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];
      if (docStyleSheet.title) {
        docStyleSheet.disabled = (docStyleSheet.title != title);
      } else if (docStyleSheet.disabled) {
        docStyleSheet.disabled = false;
      }
    }
  },

  _stylesheetInFrame(frame, title) {
    return Array.some(frame.document.styleSheets, (styleSheet) => styleSheet.title == title);
  },

  _filterStyleSheets(styleSheets, content) {
    let result = [];

    for (let currentStyleSheet of styleSheets) {
      if (!currentStyleSheet.title)
        continue;

      // Skip any stylesheets that don't match the screen media type.
      if (currentStyleSheet.media.length > 0) {
        let mediaQueryList = currentStyleSheet.media.mediaText;
        if (!content.matchMedia(mediaQueryList).matches) {
          continue;
        }
      }

      let URI;
      try {
        if (!currentStyleSheet.ownerNode ||
            // special-case style nodes, which have no href
            currentStyleSheet.ownerNode.nodeName.toLowerCase() != "style") {
          URI = Services.io.newURI(currentStyleSheet.href);
        }
      } catch (e) {
        if (e.result != Cr.NS_ERROR_MALFORMED_URI) {
          throw e;
        }
        continue;
      }

      // We won't send data URIs all of the way up to the parent, as these
      // can be arbitrarily large.
      let sentURI = (!URI || URI.scheme == "data") ? null : URI.spec;

      result.push({
        title: currentStyleSheet.title,
        disabled: currentStyleSheet.disabled,
        href: sentURI,
      });
    }

    return result;
  },
};