tor-browser

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

css-compatibility-tooltip-helper.js (9197B)


      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 "use strict";
      6 
      7 const { BrowserLoader } = ChromeUtils.importESModule(
      8  "resource://devtools/shared/loader/browser-loader.sys.mjs"
      9 );
     10 
     11 loader.lazyRequireGetter(
     12  this,
     13  "openDocLink",
     14  "resource://devtools/client/shared/link.js",
     15  true
     16 );
     17 
     18 class CssCompatibilityTooltipHelper {
     19  constructor() {
     20    this.addTab = this.addTab.bind(this);
     21  }
     22 
     23  #currentTooltip = null;
     24  #currentUrl = null;
     25 
     26  #createElement(doc, tag, classList = [], attributeList = {}) {
     27    const XHTML_NS = "http://www.w3.org/1999/xhtml";
     28    const newElement = doc.createElementNS(XHTML_NS, tag);
     29    for (const elementClass of classList) {
     30      newElement.classList.add(elementClass);
     31    }
     32 
     33    for (const key in attributeList) {
     34      newElement.setAttribute(key, attributeList[key]);
     35    }
     36 
     37    return newElement;
     38  }
     39 
     40  /*
     41   * Attach the UnsupportedBrowserList component to the
     42   * ".compatibility-browser-list-wrapper" div to render the
     43   * unsupported browser list
     44   */
     45  #renderUnsupportedBrowserList(container, unsupportedBrowsers) {
     46    // Mount the ReactDOM only if the unsupported browser
     47    // list is not empty. Else "compatibility-browser-list-wrapper"
     48    // is not defined. For example, for property clip,
     49    // unsupportedBrowsers is an empty array
     50    if (!unsupportedBrowsers.length) {
     51      return;
     52    }
     53 
     54    const { require } = BrowserLoader({
     55      baseURI: "resource://devtools/client/shared/widgets/tooltip/",
     56      window: this.#currentTooltip.doc.defaultView,
     57    });
     58    const {
     59      createFactory,
     60      createElement,
     61    } = require("resource://devtools/client/shared/vendor/react.mjs");
     62    const ReactDOM = require("resource://devtools/client/shared/vendor/react-dom.mjs");
     63    const UnsupportedBrowserList = createFactory(
     64      require("resource://devtools/client/inspector/compatibility/components/UnsupportedBrowserList.js")
     65    );
     66 
     67    const unsupportedBrowserList = createElement(UnsupportedBrowserList, {
     68      browsers: unsupportedBrowsers,
     69    });
     70    ReactDOM.render(
     71      unsupportedBrowserList,
     72      container.querySelector(".compatibility-browser-list-wrapper")
     73    );
     74  }
     75 
     76  /*
     77   * Get the first paragraph for the compatibility tooltip
     78   * Return a subtree similar to:
     79   *   <p data-l10n-id="css-compatibility-default-message"
     80   *      data-l10n-args="{&quot;property&quot;:&quot;user-select&quot;}">
     81   *   </p>
     82   */
     83  #getCompatibilityMessage(doc, data) {
     84    const { msgId, property } = data;
     85    return this.#createElement(doc, "p", [], {
     86      "data-l10n-id": msgId,
     87      "data-l10n-args": JSON.stringify({ property }),
     88    });
     89  }
     90 
     91  /**
     92   * Gets the paragraph elements related to the browserList.
     93   * This returns an array with following subtree:
     94   *   [
     95   *     <p data-l10n-id="css-compatibility-browser-list-message"></p>,
     96   *     <p>
     97   *      <ul class="compatibility-unsupported-browser-list">
     98   *        <list-element />
     99   *      </ul>
    100   *     </p>
    101   *   ]
    102   * The first element is the message and the second element is the
    103   * unsupported browserList itself.
    104   * If the unsupportedBrowser is an empty array, we return an empty
    105   * array back.
    106   */
    107  #getBrowserListContainer(doc, unsupportedBrowsers) {
    108    if (!unsupportedBrowsers.length) {
    109      return null;
    110    }
    111 
    112    const browserList = this.#createElement(doc, "p");
    113    const browserListWrapper = this.#createElement(doc, "div", [
    114      "compatibility-browser-list-wrapper",
    115    ]);
    116    browserList.appendChild(browserListWrapper);
    117 
    118    return browserList;
    119  }
    120 
    121  /*
    122   * This is the learn more message element linking to the MDN documentation
    123   * for the particular incompatible CSS declaration.
    124   * The element returned is:
    125   *   <p data-l10n-id="css-compatibility-learn-more-message"
    126   *       data-l10n-args="{&quot;property&quot;:&quot;user-select&quot;}">
    127   *     <span data-l10n-name="link" class="link"></span>
    128   *   </p>
    129   */
    130  #getLearnMoreMessage(doc, { rootProperty }) {
    131    const learnMoreMessage = this.#createElement(doc, "p", [], {
    132      "data-l10n-id": "css-compatibility-learn-more-message",
    133      "data-l10n-args": JSON.stringify({ rootProperty }),
    134    });
    135    learnMoreMessage.appendChild(
    136      this.#createElement(doc, "span", ["link"], {
    137        "data-l10n-name": "link",
    138      })
    139    );
    140 
    141    return learnMoreMessage;
    142  }
    143 
    144  /**
    145   * Fill the tooltip with inactive CSS information.
    146   *
    147   * @param {object} data
    148   *        An object in the following format: {
    149   *          // Type of compatibility issue
    150   *          type: <string>,
    151   *          // The CSS declaration that has compatibility issues
    152   *          // The raw CSS declaration name that has compatibility issues
    153   *          declaration: <string>,
    154   *          property: <string>,
    155   *          // Alias to the given CSS property
    156   *          alias: <Array>,
    157   *          // Link to MDN documentation for the particular CSS rule
    158   *          url: <string>,
    159   *          deprecated: <boolean>,
    160   *          experimental: <boolean>,
    161   *          // An array of all the browsers that don't support the given CSS rule
    162   *          unsupportedBrowsers: <Array>,
    163   *        }
    164   * @param {HTMLTooltip} tooltip
    165   *        The tooltip we are targetting.
    166   */
    167  async setContent(data, tooltip) {
    168    const fragment = this.getTemplate(data, tooltip);
    169 
    170    tooltip.panel.addEventListener("click", this.addTab);
    171    tooltip.once("hidden", () => {
    172      tooltip.panel.removeEventListener("click", this.addTab);
    173    });
    174 
    175    await tooltip.setLocalizedFragment(fragment, { width: 267 });
    176  }
    177 
    178  /**
    179   * Get the template that the Fluent string will be merged with. This template
    180   * looks like this:
    181   *
    182   * <div class="devtools-tooltip-css-compatibility">
    183   *   <p data-l10n-id="css-compatibility-default-message"
    184   *      data-l10n-args="{&quot;property&quot;:&quot;user-select&quot;}">
    185   *     <strong></strong>
    186   *   </p>
    187   *   <browser-list />
    188   *   <p data-l10n-id="css-compatibility-learn-more-message"
    189   *       data-l10n-args="{&quot;property&quot;:&quot;user-select&quot;}">
    190   *     <span data-l10n-name="link" class="link"></span>
    191   *     <strong></strong>
    192   *   </p>
    193   * </div>
    194   *
    195   * @param {object} data
    196   *        An object in the following format: {
    197   *          // Type of compatibility issue
    198   *          type: <string>,
    199   *          // The CSS declaration that has compatibility issues
    200   *          // The raw CSS declaration name that has compatibility issues
    201   *          declaration: <string>,
    202   *          property: <string>,
    203   *          // Alias to the given CSS property
    204   *          alias: <Array>,
    205   *          // Link to MDN documentation for the particular CSS rule
    206   *          url: <string>,
    207   *          // Link to the spec for the particular CSS rule
    208   *          specUrl: <string>,
    209   *          deprecated: <boolean>,
    210   *          experimental: <boolean>,
    211   *          // An array of all the browsers that don't support the given CSS rule
    212   *          unsupportedBrowsers: <Array>,
    213   *        }
    214   * @param {HTMLTooltip} tooltip
    215   *        The tooltip we are targetting.
    216   */
    217  getTemplate(data, tooltip) {
    218    const { doc } = tooltip;
    219    const { specUrl, url, unsupportedBrowsers } = data;
    220 
    221    this.#currentTooltip = tooltip;
    222    this.#currentUrl = url
    223      ? `${url}?utm_source=devtools&utm_medium=inspector-css-compatibility&utm_campaign=default`
    224      : specUrl;
    225    const templateNode = this.#createElement(doc, "template");
    226 
    227    const tooltipContainer = this.#createElement(doc, "div", [
    228      "devtools-tooltip-css-compatibility",
    229    ]);
    230 
    231    tooltipContainer.appendChild(this.#getCompatibilityMessage(doc, data));
    232    const browserListContainer = this.#getBrowserListContainer(
    233      doc,
    234      unsupportedBrowsers
    235    );
    236    if (browserListContainer) {
    237      tooltipContainer.appendChild(browserListContainer);
    238      this.#renderUnsupportedBrowserList(tooltipContainer, unsupportedBrowsers);
    239    }
    240 
    241    if (this.#currentUrl) {
    242      tooltipContainer.appendChild(this.#getLearnMoreMessage(doc, data));
    243    }
    244 
    245    templateNode.content.appendChild(tooltipContainer);
    246    return doc.importNode(templateNode.content, true);
    247  }
    248 
    249  /**
    250   * Hide the tooltip, open `this.#currentUrl` in a new tab and focus it.
    251   *
    252   * @param {DOMEvent} event
    253   *        The click event originating from the tooltip.
    254   */
    255  addTab(event) {
    256    // The XUL panel swallows click events so handlers can't be added directly
    257    // to the link span. As a workaround we listen to all click events in the
    258    // panel and if a link span is clicked we proceed.
    259    if (event.target.className !== "link") {
    260      return;
    261    }
    262 
    263    const tooltip = this.#currentTooltip;
    264    tooltip.hide();
    265 
    266    const isMacOS = Services.appinfo.OS === "Darwin";
    267    openDocLink(this.#currentUrl, {
    268      relatedToCurrent: true,
    269      inBackground: isMacOS ? event.metaKey : event.ctrlKey,
    270    });
    271  }
    272 
    273  destroy() {
    274    this.#currentTooltip = null;
    275    this.#currentUrl = null;
    276  }
    277 }
    278 
    279 module.exports = CssCompatibilityTooltipHelper;