tor-browser

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

Popup.js (8593B)


      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 import React, { Component } from "devtools/client/shared/vendor/react";
      6 import { div } from "devtools/client/shared/vendor/react-dom-factories";
      7 import PropTypes from "devtools/client/shared/vendor/react-prop-types";
      8 import { connect } from "devtools/client/shared/vendor/react-redux";
      9 
     10 const Reps = ChromeUtils.importESModule(
     11  "resource://devtools/client/shared/components/reps/index.mjs"
     12 );
     13 const {
     14  REPS: { Grip },
     15  MODE,
     16 } = Reps;
     17 import * as objectInspector from "resource://devtools/client/shared/components/object-inspector/index.js";
     18 
     19 const { ObjectInspector, utils } = objectInspector;
     20 
     21 const {
     22  node: { nodeIsPrimitive },
     23 } = utils;
     24 
     25 import ExceptionPopup from "./ExceptionPopup";
     26 
     27 import actions from "../../../actions/index";
     28 import Popover from "../../shared/Popover";
     29 
     30 export class Popup extends Component {
     31  constructor(props) {
     32    super(props);
     33  }
     34 
     35  static get propTypes() {
     36    return {
     37      clearPreview: PropTypes.func.isRequired,
     38      editorRef: PropTypes.object.isRequired,
     39      highlightDomElement: PropTypes.func.isRequired,
     40      openElementInInspector: PropTypes.func.isRequired,
     41      openLink: PropTypes.func.isRequired,
     42      preview: PropTypes.object.isRequired,
     43      selectSourceURL: PropTypes.func.isRequired,
     44      unHighlightDomElement: PropTypes.func.isRequired,
     45    };
     46  }
     47 
     48  componentDidMount() {
     49    this.addHighlightToToken(this.props.preview.target);
     50  }
     51 
     52  componentWillUnmount() {
     53    this.removeHighlightFromToken(this.props.preview.target);
     54  }
     55 
     56  componentDidUpdate(prevProps) {
     57    const { target } = this.props.preview;
     58    if (prevProps.target == target) {
     59      return;
     60    }
     61 
     62    this.removeHighlightFromToken(prevProps.preview.target);
     63    this.addHighlightToToken(target);
     64  }
     65 
     66  addHighlightToToken(target) {
     67    if (!target) {
     68      return;
     69    }
     70 
     71    target.classList.add("preview-token");
     72    addHighlightToTargetSiblings(target, this.props);
     73  }
     74 
     75  removeHighlightFromToken(target) {
     76    if (!target) {
     77      return;
     78    }
     79 
     80    target.classList.remove("preview-token");
     81    removeHighlightForTargetSiblings(target);
     82  }
     83 
     84  calculateMaxHeight = () => {
     85    const { editorRef } = this.props;
     86    if (!editorRef) {
     87      return "auto";
     88    }
     89 
     90    const { height, top } = editorRef.getBoundingClientRect();
     91    const maxHeight = height + top;
     92    if (maxHeight < 250) {
     93      return maxHeight;
     94    }
     95 
     96    return 250;
     97  };
     98 
     99  createElement(element) {
    100    return document.createElement(element);
    101  }
    102 
    103  renderExceptionPreview(exception) {
    104    return React.createElement(ExceptionPopup, {
    105      exception,
    106      mouseout: this.props.clearPreview,
    107    });
    108  }
    109 
    110  renderPreview() {
    111    const {
    112      preview: { root, exception, previewType },
    113    } = this.props;
    114 
    115    const usesCustomFormatter =
    116      root?.contents?.value?.useCustomFormatter ?? false;
    117 
    118    if (exception) {
    119      return this.renderExceptionPreview(exception);
    120    }
    121 
    122    return div(
    123      {
    124        className: `preview-popup preview-type-${previewType}`,
    125        style: {
    126          maxHeight: this.calculateMaxHeight(),
    127        },
    128      },
    129      // Bug 1915610 - JS Tracer isn't localized yet
    130      previewType == "tracer"
    131        ? div({ className: "preview-tracer-header" }, "Tracer preview")
    132        : null,
    133      previewType == "tracer" && !nodeIsPrimitive(root)
    134        ? div(
    135            { className: "preview-tracer-warning" },
    136            "Attribute previews on traced objects are showing the current values and not the value at execution of the selected frame."
    137          )
    138        : null,
    139      React.createElement(ObjectInspector, {
    140        roots: [root],
    141        autoExpandDepth: 1,
    142        autoReleaseObjectActors: false,
    143        mode: usesCustomFormatter ? MODE.LONG : MODE.SHORT,
    144        disableWrap: true,
    145        displayRootNodeAsHeader: true,
    146        focusable: false,
    147        openLink: this.props.openLink,
    148        defaultRep: Grip,
    149        createElement: this.createElement,
    150        onDOMNodeClick: grip => this.props.openElementInInspector(grip),
    151        onInspectIconClick: grip => this.props.openElementInInspector(grip),
    152        onDOMNodeMouseOver: grip => this.props.highlightDomElement(grip),
    153        onDOMNodeMouseOut: grip => this.props.unHighlightDomElement(grip),
    154        mayUseCustomFormatter: true,
    155        onViewSourceInDebugger: ({ url, line, column }) =>
    156          this.props.selectSourceURL(url, { line, column }),
    157      })
    158    );
    159  }
    160 
    161  getPreviewType() {
    162    const {
    163      preview: { root, exception },
    164    } = this.props;
    165    if (exception || nodeIsPrimitive(root)) {
    166      return "tooltip";
    167    }
    168 
    169    return "popover";
    170  }
    171 
    172  render() {
    173    const {
    174      preview: { cursorPos, resultGrip, exception },
    175      editorRef,
    176    } = this.props;
    177 
    178    if (
    179      !exception &&
    180      (typeof resultGrip == "undefined" || resultGrip?.optimizedOut)
    181    ) {
    182      return null;
    183    }
    184 
    185    const type = this.getPreviewType();
    186    return React.createElement(
    187      Popover,
    188      {
    189        targetPosition: cursorPos,
    190        type,
    191        editorRef,
    192        target: this.props.preview.target,
    193        mouseout: this.props.clearPreview,
    194      },
    195      this.renderPreview()
    196    );
    197  }
    198 }
    199 
    200 export function addHighlightToTargetSiblings(target, props) {
    201  // This function searches for related tokens that should also be highlighted when previewed.
    202  // Here is the process:
    203  // It conducts a search on the target's next siblings and then another search for the previous siblings.
    204  // If a sibling is not an element node (nodeType === 1), the highlight is not added and the search is short-circuited.
    205  // If the element sibling is the same token type as the target, and is also found in the preview expression, the highlight class is added.
    206 
    207  const tokenType = target.classList.item(0);
    208  const previewExpression = props.preview.expression;
    209 
    210  if (
    211    tokenType &&
    212    previewExpression &&
    213    target.innerHTML !== previewExpression
    214  ) {
    215    let nextSibling = target.nextSibling;
    216    let nextElementSibling = target.nextElementSibling;
    217 
    218    // Note: Declaring previous/next ELEMENT siblings as well because
    219    // properties like innerHTML can't be checked on nextSibling
    220    // without creating a flow error even if the node is an element type.
    221    while (
    222      nextSibling &&
    223      nextElementSibling &&
    224      nextSibling.nodeType === 1 &&
    225      nextElementSibling.className.includes(tokenType) &&
    226      previewExpression.includes(nextElementSibling.innerHTML)
    227    ) {
    228      // All checks passed, add highlight and continue the search.
    229      nextElementSibling.classList.add("preview-token");
    230 
    231      nextSibling = nextSibling.nextSibling;
    232      nextElementSibling = nextElementSibling.nextElementSibling;
    233    }
    234 
    235    let previousSibling = target.previousSibling;
    236    let previousElementSibling = target.previousElementSibling;
    237 
    238    while (
    239      previousSibling &&
    240      previousElementSibling &&
    241      previousSibling.nodeType === 1 &&
    242      previousElementSibling.className.includes(tokenType) &&
    243      previewExpression.includes(previousElementSibling.innerHTML)
    244    ) {
    245      // All checks passed, add highlight and continue the search.
    246      previousElementSibling.classList.add("preview-token");
    247 
    248      previousSibling = previousSibling.previousSibling;
    249      previousElementSibling = previousElementSibling.previousElementSibling;
    250    }
    251  }
    252 }
    253 
    254 export function removeHighlightForTargetSiblings(target) {
    255  // Look at target's previous and next token siblings.
    256  // If they also have the highlight class 'preview-token',
    257  // remove that class.
    258  let nextSibling = target.nextElementSibling;
    259  while (nextSibling && nextSibling.className.includes("preview-token")) {
    260    nextSibling.classList.remove("preview-token");
    261    nextSibling = nextSibling.nextElementSibling;
    262  }
    263  let previousSibling = target.previousElementSibling;
    264  while (
    265    previousSibling &&
    266    previousSibling.className.includes("preview-token")
    267  ) {
    268    previousSibling.classList.remove("preview-token");
    269    previousSibling = previousSibling.previousElementSibling;
    270  }
    271 }
    272 
    273 const mapDispatchToProps = {
    274  addExpression: actions.addExpression,
    275  selectSourceURL: actions.selectSourceURL,
    276  openLink: actions.openLink,
    277  openElementInInspector: actions.openElementInInspectorCommand,
    278  highlightDomElement: actions.highlightDomElement,
    279  unHighlightDomElement: actions.unHighlightDomElement,
    280 };
    281 
    282 export default connect(null, mapDispatchToProps)(Popup);