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

"use strict";

loader.lazyRequireGetter(
  this,
  "loadSheet",
  "resource://devtools/shared/layout/utils.js",
  true
);
loader.lazyRequireGetter(
  this,
  "removeSheet",
  "resource://devtools/shared/layout/utils.js",
  true
);

// How many text runs are we highlighting at a time. There may be many text runs, and we
// want to prevent performance problems.
const MAX_TEXT_RANGES = 100;

// This stylesheet is inserted into the page to customize the color of the selected text
// runs.
// Note that this color is defined as --highlighter-content-color in the highlighters.css
// file, and corresponds to the box-model content color. We want to give it an opacity of
// 0.6 here.
const STYLESHEET_URI =
  "data:text/css," +
  encodeURIComponent(
    "::selection{background-color:hsl(197,71%,73%,.6)!important;}"
  );

/**
 * This highlighter highlights runs of text in the page that have been rendered given a
 * certain font. The highlighting is done with window selection ranges, so no extra
 * markup is being inserted into the content page.
 */
class FontsHighlighter {
  constructor(highlighterEnv) {
    this.env = highlighterEnv;
  }

  destroy() {
    this.hide();
    this.env = this.currentNode = null;
  }

  get currentNodeDocument() {
    if (!this.currentNode) {
      return this.env.document;
    }

    if (this.currentNode.nodeType === this.currentNode.DOCUMENT_NODE) {
      return this.currentNode;
    }

    return this.currentNode.ownerDocument;
  }

  /**
   * Show the highlighter for a given node.
   *
   * @param {DOMNode} node The node in which we want to search for text runs.
   * @param {object} options A bunch of options that can be set:
   * - {String} name The actual font name to look for in the node.
   * - {String} CSSFamilyName The CSS font-family name given to this font.
   */
  show(node, options) {
    this.currentNode = node;
    const doc = this.currentNodeDocument;

    // Get all of the fonts used to render content inside the node.
    const searchRange = doc.createRange();
    searchRange.selectNodeContents(node);

    const fonts = InspectorUtils.getUsedFontFaces(searchRange, MAX_TEXT_RANGES);

    // Find the ones we want, based on the provided option.
    const matchingFonts = fonts.filter(
      f => f.CSSFamilyName === options.CSSFamilyName && f.name === options.name
    );
    if (!matchingFonts.length) {
      return;
    }

    // Load the stylesheet that will customize the color of the highlighter (using a
    // ::selection rule).
    loadSheet(this.env.window, STYLESHEET_URI);

    // Create a multi-selection in the page to highlight the text runs.
    const selection = doc.defaultView.getSelection();
    selection.removeAllRanges();

    for (const matchingFont of matchingFonts) {
      for (const range of matchingFont.ranges) {
        selection.addRange(range);
      }
    }
  }

  hide() {
    // No node was highlighted before, don't need to continue any further.
    if (!this.currentNode) {
      return;
    }

    try {
      removeSheet(this.env.window, STYLESHEET_URI);
    } catch (e) {
      // Silently fail here as we might not have inserted the stylesheet at all.
    }

    // Simply remove all current ranges in the seletion.
    const doc = this.currentNodeDocument;
    const selection = doc.defaultView.getSelection();
    selection.removeAllRanges();
  }
}

exports.FontsHighlighter = FontsHighlighter;
