tor-browser

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

index.js (25454B)


      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 PropTypes from "devtools/client/shared/vendor/react-prop-types";
      6 import React, { PureComponent } from "devtools/client/shared/vendor/react";
      7 import { div } from "devtools/client/shared/vendor/react-dom-factories";
      8 import { bindActionCreators } from "devtools/client/shared/vendor/redux";
      9 import ReactDOM from "devtools/client/shared/vendor/react-dom";
     10 import { connect } from "devtools/client/shared/vendor/react-redux";
     11 
     12 import { getLineText, isLineBlackboxed } from "./../../utils/source";
     13 import { createLocation } from "./../../utils/location";
     14 import { asSettled, isFulfilled, isRejected } from "../../utils/async-value";
     15 
     16 import {
     17  getActiveSearch,
     18  getSelectedLocation,
     19  getSelectedSource,
     20  getSelectedSourceTextContent,
     21  getSelectedBreakableLines,
     22  getConditionalPanelLocation,
     23  getIsCurrentThreadPaused,
     24  getSkipPausing,
     25  getInlinePreview,
     26  getBlackBoxRanges,
     27  isSourceBlackBoxed,
     28  getHighlightedLineRangeForSelectedSource,
     29  isSourceMapIgnoreListEnabled,
     30  isSourceOnSourceMapIgnoreList,
     31  isMapScopesEnabled,
     32  getSelectedTraceIndex,
     33  getShouldScrollToSelectedLocation,
     34  getShouldHighlightSelectedLocation,
     35  getSelectedTraceLocation,
     36 } from "../../selectors/index";
     37 
     38 // Redux actions
     39 import actions from "../../actions/index";
     40 
     41 import SearchInFileBar from "./SearchInFileBar";
     42 import HighlightLines from "./HighlightLines";
     43 import Preview from "./Preview/index";
     44 import Breakpoints from "./Breakpoints";
     45 import ColumnBreakpoints from "./ColumnBreakpoints";
     46 import DebugLine from "./DebugLine";
     47 import HighlightLine from "./HighlightLine";
     48 import ConditionalPanel from "./ConditionalPanel";
     49 import TracePanel from "./TracePanel";
     50 import InlinePreviews from "./InlinePreviews";
     51 import Exceptions from "./Exceptions";
     52 
     53 import {
     54  fromEditorLine,
     55  getEditor,
     56  removeEditor,
     57  toSourceLine,
     58  toEditorPosition,
     59  onMouseOver,
     60 } from "../../utils/editor/index";
     61 
     62 import { updateEditorSizeCssVariables } from "../../utils/ui";
     63 
     64 const { debounce } = require("resource://devtools/shared/debounce.js");
     65 const classnames = require("resource://devtools/client/shared/classnames.js");
     66 
     67 const { appinfo } = Services;
     68 const isMacOS = appinfo.OS === "Darwin";
     69 
     70 function isSecondary(ev) {
     71  return isMacOS && ev.ctrlKey && ev.button === 0;
     72 }
     73 
     74 function isCmd(ev) {
     75  return isMacOS ? ev.metaKey : ev.ctrlKey;
     76 }
     77 
     78 const cssVars = {
     79  searchbarHeight: "var(--editor-searchbar-height)",
     80 };
     81 
     82 class Editor extends PureComponent {
     83  static get propTypes() {
     84    return {
     85      selectedSource: PropTypes.object,
     86      selectedSourceTextContent: PropTypes.object,
     87      selectedSourceIsBlackBoxed: PropTypes.bool,
     88      closeTabForSource: PropTypes.func.isRequired,
     89      toggleBreakpointAtLine: PropTypes.func.isRequired,
     90      conditionalPanelLocation: PropTypes.object,
     91      closeConditionalPanel: PropTypes.func.isRequired,
     92      openConditionalPanel: PropTypes.func.isRequired,
     93      updateViewport: PropTypes.func.isRequired,
     94      isPaused: PropTypes.bool.isRequired,
     95      addBreakpointAtLine: PropTypes.func.isRequired,
     96      continueToHere: PropTypes.func.isRequired,
     97      jumpToMappedLocation: PropTypes.func.isRequired,
     98      selectedLocation: PropTypes.object,
     99      startPanelSize: PropTypes.number.isRequired,
    100      endPanelSize: PropTypes.number.isRequired,
    101      searchInFileEnabled: PropTypes.bool.isRequired,
    102      inlinePreviewEnabled: PropTypes.bool.isRequired,
    103      skipPausing: PropTypes.bool.isRequired,
    104      blackboxedRanges: PropTypes.object.isRequired,
    105      breakableLines: PropTypes.object.isRequired,
    106      highlightedLineRange: PropTypes.object,
    107      isSourceOnIgnoreList: PropTypes.bool,
    108      isOriginalSourceAndMapScopesEnabled: PropTypes.bool,
    109      shouldScrollToSelectedLocation: PropTypes.bool,
    110      setInScopeLines: PropTypes.func,
    111    };
    112  }
    113 
    114  $editorWrapper;
    115  constructor(props) {
    116    super(props);
    117 
    118    this.state = {
    119      editor: null,
    120    };
    121  }
    122 
    123  // FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=1774507
    124  UNSAFE_componentWillReceiveProps(nextProps) {
    125    let { editor } = this.state;
    126    const prevEditor = editor;
    127 
    128    if (!editor) {
    129      // See Bug 1913061
    130      if (!nextProps.selectedSource) {
    131        return;
    132      }
    133      editor = this.setupEditor();
    134    }
    135 
    136    this.setTextContent(nextProps, editor, prevEditor);
    137  }
    138 
    139  async setTextContent(nextProps, editor, prevEditor) {
    140    const shouldUpdateText =
    141      nextProps.selectedSource !== this.props.selectedSource ||
    142      nextProps.selectedSourceTextContent?.value !==
    143        this.props.selectedSourceTextContent?.value ||
    144      // If the selectedSource gets set before the editor get selected, make sure we update the text
    145      prevEditor !== editor;
    146 
    147    const shouldScroll =
    148      nextProps.selectedLocation &&
    149      nextProps.shouldScrollToSelectedLocation &&
    150      this.shouldScrollToLocation(nextProps);
    151 
    152    if (shouldUpdateText) {
    153      await this.setText(nextProps, editor);
    154    }
    155 
    156    if (shouldScroll) {
    157      await this.scrollToLocation(nextProps, editor);
    158    }
    159    // Note: Its important to get the scope lines after
    160    // the scrolling is complete to make sure codemirror
    161    // has loaded the content for the current viewport.
    162    //
    163    // Also if scope mapping is on, the babel parser worker
    164    // will be used instead (for scope mapping) as the preview data relies
    165    // on it for original variable mapping.
    166    if (nextProps.isPaused && !nextProps.isOriginalSourceAndMapScopesEnabled) {
    167      this.props.setInScopeLines(editor);
    168    }
    169  }
    170 
    171  onEditorUpdated = viewUpdate => {
    172    if (viewUpdate.docChanged || viewUpdate.geometryChanged) {
    173      updateEditorSizeCssVariables(viewUpdate.view.dom);
    174      this.props.updateViewport();
    175    } else if (viewUpdate.selectionSet) {
    176      this.onCursorChange();
    177    }
    178  };
    179 
    180  setupEditor() {
    181    const editor = getEditor();
    182 
    183    // disables the default search shortcuts
    184    editor._initShortcuts = () => {};
    185 
    186    const node = ReactDOM.findDOMNode(this);
    187    const mountEl = node.querySelector(".editor-mount");
    188    if (node instanceof HTMLElement) {
    189      editor.appendToLocalElement(mountEl);
    190    }
    191 
    192    editor.setUpdateListener(this.onEditorUpdated);
    193    editor.setGutterEventListeners({
    194      click: (event, cm, line) => {
    195        // Ignore clicks on the code folding button
    196        if (
    197          event.target.className.includes("cm6-dt-foldgutter__toggle-button")
    198        ) {
    199          return;
    200        }
    201        // Clicking any where on the fold gutter (except on a code folding button)
    202        // should toggle the breakpoint for this line, if possible.
    203        if (event.target.className.includes("cm-foldGutter")) {
    204          this.props.toggleBreakpointAtLine(line);
    205          return;
    206        }
    207        this.onGutterClick(cm, line, null, event);
    208      },
    209      contextmenu: (event, cm, line) => this.openMenu(event, line),
    210    });
    211    editor.addEditorDOMEventListeners({
    212      click: (event, cm, line, column) => this.onClick(event, line, column),
    213      contextmenu: (event, cm, line, column) =>
    214        this.openMenu(event, line, column),
    215      mouseover: onMouseOver(editor),
    216    });
    217 
    218    this.setState({ editor });
    219    // Used for tests
    220    Object.defineProperty(window, "codeMirrorSourceEditorTestInstance", {
    221      get() {
    222        return editor;
    223      },
    224    });
    225    return editor;
    226  }
    227 
    228  componentDidMount() {
    229    const { shortcuts } = this.context;
    230 
    231    shortcuts.on(L10N.getStr("toggleBreakpoint.key"), this.onToggleBreakpoint);
    232    shortcuts.on(
    233      L10N.getStr("toggleCondPanel.breakpoint.key"),
    234      this.onToggleConditionalPanel
    235    );
    236    shortcuts.on(
    237      L10N.getStr("toggleCondPanel.logPoint.key"),
    238      this.onToggleLogPanel
    239    );
    240    shortcuts.on(
    241      L10N.getStr("sourceTabs.closeTab.key"),
    242      this.onCloseShortcutPress
    243    );
    244    shortcuts.on("Esc", this.onEscape);
    245  }
    246 
    247  onCloseShortcutPress = e => {
    248    const { selectedSource } = this.props;
    249    if (selectedSource) {
    250      e.preventDefault();
    251      e.stopPropagation();
    252      this.props.closeTabForSource(selectedSource);
    253    }
    254  };
    255 
    256  componentDidUpdate(prevProps, prevState) {
    257    const {
    258      selectedSource,
    259      blackboxedRanges,
    260      isSourceOnIgnoreList,
    261      breakableLines,
    262    } = this.props;
    263    const { editor } = this.state;
    264 
    265    if (!selectedSource || !editor) {
    266      return;
    267    }
    268 
    269    const shouldUpdateBreakableLines =
    270      prevProps.breakableLines.size !== this.props.breakableLines.size ||
    271      prevProps.selectedSource?.id !== selectedSource.id ||
    272      // Make sure we update after the editor has loaded
    273      (!prevState.editor && !!editor);
    274 
    275    if (shouldUpdateBreakableLines) {
    276      editor.setLineGutterMarkers([
    277        {
    278          id: editor.markerTypes.EMPTY_LINE_MARKER,
    279          lineClassName: "empty-line",
    280          condition: line => {
    281            const lineNumber = fromEditorLine(selectedSource, line);
    282            return !breakableLines.has(lineNumber);
    283          },
    284        },
    285      ]);
    286    }
    287 
    288    editor.setLineGutterMarkers([
    289      {
    290        id: editor.markerTypes.BLACKBOX_LINE_GUTTER_MARKER,
    291        lineClassName: "blackboxed-line",
    292        condition: line => {
    293          const lineNumber = fromEditorLine(selectedSource, line);
    294          return isLineBlackboxed(
    295            blackboxedRanges[selectedSource.url],
    296            lineNumber,
    297            isSourceOnIgnoreList
    298          );
    299        },
    300      },
    301    ]);
    302 
    303    if (
    304      prevProps.selectedSource?.id !== selectedSource.id ||
    305      prevProps.blackboxedRanges[selectedSource.url]?.length !==
    306        blackboxedRanges[selectedSource.url]?.length ||
    307      (!prevState.editor && !!editor)
    308    ) {
    309      if (blackboxedRanges[selectedSource.url] == undefined) {
    310        editor.removeLineContentMarker(editor.markerTypes.BLACKBOX_LINE_MARKER);
    311        return;
    312      }
    313 
    314      const lines = [];
    315      for (const range of blackboxedRanges[selectedSource.url]) {
    316        for (let line = range.start.line; line <= range.end.line; line++) {
    317          lines.push({ line });
    318        }
    319      }
    320 
    321      editor.setLineContentMarker({
    322        id: editor.markerTypes.BLACKBOX_LINE_MARKER,
    323        lineClassName: "blackboxed-line",
    324        // If the the whole source is blackboxed, lets just mark all positions.
    325        shouldMarkAllLines: !blackboxedRanges[selectedSource.url].length,
    326        lines,
    327      });
    328    }
    329  }
    330 
    331  componentWillUnmount() {
    332    const { editor } = this.state;
    333    const { shortcuts } = this.context;
    334    shortcuts.off(L10N.getStr("sourceTabs.closeTab.key"));
    335    shortcuts.off(L10N.getStr("toggleBreakpoint.key"));
    336    shortcuts.off(L10N.getStr("toggleCondPanel.breakpoint.key"));
    337    shortcuts.off(L10N.getStr("toggleCondPanel.logPoint.key"));
    338 
    339    if (this.abortController) {
    340      this.abortController.abort();
    341      this.abortController = null;
    342    }
    343 
    344    if (editor) {
    345      editor.destroy();
    346      this.setState({ editor: null });
    347      removeEditor();
    348    }
    349  }
    350 
    351  getCurrentPosition() {
    352    const { editor } = this.state;
    353    const { selectedLocation } = this.props;
    354    if (!selectedLocation) {
    355      return null;
    356    }
    357    let { line, column } = selectedLocation;
    358    // When no specific line has been selected, fallback to the current cursor posiiton
    359    if (line == 0) {
    360      const selectionCursor = editor.getSelectionCursor();
    361      line = toSourceLine(selectedLocation.source, selectionCursor.from.line);
    362      column = selectionCursor.from.ch;
    363    }
    364    return { line, column };
    365  }
    366 
    367  onToggleBreakpoint = e => {
    368    e.preventDefault();
    369    e.stopPropagation();
    370 
    371    const currentPosition = this.getCurrentPosition();
    372    if (!currentPosition || typeof currentPosition.line !== "number") {
    373      return;
    374    }
    375 
    376    this.props.toggleBreakpointAtLine(currentPosition.line);
    377  };
    378 
    379  onToggleLogPanel = e => {
    380    e.stopPropagation();
    381    e.preventDefault();
    382    this.toggleBreakpointPanel(true);
    383  };
    384 
    385  onToggleConditionalPanel = e => {
    386    e.stopPropagation();
    387    e.preventDefault();
    388    this.toggleBreakpointPanel(false);
    389  };
    390 
    391  toggleBreakpointPanel(logPanel) {
    392    const {
    393      conditionalPanelLocation,
    394      closeConditionalPanel,
    395      openConditionalPanel,
    396      selectedSource,
    397    } = this.props;
    398 
    399    const currentPosition = this.getCurrentPosition();
    400 
    401    if (conditionalPanelLocation) {
    402      return closeConditionalPanel();
    403    }
    404 
    405    if (!selectedSource || typeof currentPosition?.line !== "number") {
    406      return null;
    407    }
    408 
    409    return openConditionalPanel(
    410      createLocation({
    411        line: currentPosition.line,
    412        column: currentPosition.column,
    413        source: selectedSource,
    414      }),
    415      logPanel
    416    );
    417  }
    418 
    419  onEditorScroll = debounce(this.props.updateViewport, 75);
    420 
    421  /*
    422   * The default Esc command is overridden in the CodeMirror keymap to allow
    423   * the Esc keypress event to be catched by the toolbox and trigger the
    424   * split console. Restore it here, but preventDefault if and only if there
    425   * is a multiselection.
    426   */
    427  onEscape() {}
    428 
    429  // Note: The line is optional, if not passed it fallsback to lineAtHeight.
    430  openMenu(event, line, ch) {
    431    event.stopPropagation();
    432    event.preventDefault();
    433 
    434    const {
    435      selectedSource,
    436      selectedSourceTextContent,
    437      conditionalPanelLocation,
    438      closeConditionalPanel,
    439    } = this.props;
    440 
    441    const { editor } = this.state;
    442 
    443    if (!selectedSource || !editor) {
    444      return;
    445    }
    446 
    447    // only allow one conditionalPanel location.
    448    if (conditionalPanelLocation) {
    449      closeConditionalPanel();
    450    }
    451 
    452    const target = event.target;
    453    const { id: sourceId } = selectedSource;
    454 
    455    if (typeof line != "number") {
    456      return;
    457    }
    458 
    459    if (
    460      target.classList.contains("cm-gutter") ||
    461      target.classList.contains("cm-gutterElement")
    462    ) {
    463      const location = createLocation({
    464        line,
    465        column: undefined,
    466        source: selectedSource,
    467      });
    468 
    469      const lineText = getLineText(
    470        sourceId,
    471        selectedSourceTextContent,
    472        line
    473      ).trim();
    474 
    475      const lineObject = { from: { line, ch }, to: { line, ch } };
    476 
    477      this.props.showEditorGutterContextMenu(
    478        event,
    479        lineObject,
    480        location,
    481        lineText
    482      );
    483      return;
    484    }
    485 
    486    if (target.getAttribute("id") === "columnmarker") {
    487      return;
    488    }
    489 
    490    const location = createLocation({
    491      source: selectedSource,
    492      line: fromEditorLine(selectedSource, line),
    493      column: editor.isWasm ? 0 : ch,
    494    });
    495 
    496    const lineObject = editor.getSelectionCursor();
    497    this.props.showEditorContextMenu(event, editor, lineObject, location);
    498  }
    499 
    500  /**
    501   * CodeMirror event handler, called whenever the cursor moves
    502   * for user-driven or programatic reasons.
    503   */
    504  onCursorChange = () => {
    505    const { editor } = this.state;
    506    if (!editor || !this.props.selectedSource) {
    507      return;
    508    }
    509    const { selectedLocation } = this.props;
    510    const selectionCursor = editor.getSelectionCursor();
    511    const { line, ch } = selectionCursor.to;
    512 
    513    // Avoid triggering an infinite loop of select location action
    514    // if we moved the cursor to the location already stored in redux
    515    //
    516    // About columns: consider the reducer's column set to undefined as equivalent to selecting column 0 in CodeMirror.
    517    // This also avoids dispatching duplicated `selectLocation` actions when selecting a line without a specific column.
    518    if (
    519      selectedLocation.line == line &&
    520      ((typeof selectedLocation.column != "number" && ch == 0) ||
    521        selectedLocation.column == ch)
    522    ) {
    523      return;
    524    }
    525 
    526    this.props.selectLocation(
    527      createLocation({
    528        source: this.props.selectedSource,
    529        line: toSourceLine(this.props.selectedSource, line),
    530        column: ch,
    531      }),
    532      {
    533        // Reset the context, so that we don't switch to original
    534        // while moving the cursor within a bundle
    535        keepContext: false,
    536 
    537        // Avoid highlighting the selected line
    538        highlight: false,
    539 
    540        // Avoid scrolling to the selected line, it's already visible
    541        scroll: false,
    542      }
    543    );
    544  };
    545 
    546  onGutterClick = (cm, line, gutter, ev) => {
    547    const {
    548      selectedSource,
    549      conditionalPanelLocation,
    550      closeConditionalPanel,
    551      addBreakpointAtLine,
    552      continueToHere,
    553      breakableLines,
    554      blackboxedRanges,
    555      isSourceOnIgnoreList,
    556    } = this.props;
    557 
    558    // ignore right clicks in the gutter
    559    if (isSecondary(ev) || ev.button === 2 || !selectedSource) {
    560      return;
    561    }
    562 
    563    if (conditionalPanelLocation) {
    564      closeConditionalPanel();
    565      return;
    566    }
    567 
    568    if (gutter === "CodeMirror-foldgutter") {
    569      return;
    570    }
    571 
    572    const sourceLine = toSourceLine(selectedSource, line);
    573    if (typeof sourceLine !== "number") {
    574      return;
    575    }
    576 
    577    // ignore clicks on a non-breakable line
    578    if (!breakableLines.has(sourceLine)) {
    579      return;
    580    }
    581 
    582    if (isCmd(ev)) {
    583      continueToHere(
    584        createLocation({
    585          line: sourceLine,
    586          column: undefined,
    587          source: selectedSource,
    588        })
    589      );
    590      return;
    591    }
    592 
    593    addBreakpointAtLine(
    594      sourceLine,
    595      ev.altKey,
    596      ev.shiftKey ||
    597        isLineBlackboxed(
    598          blackboxedRanges[selectedSource.url],
    599          sourceLine,
    600          isSourceOnIgnoreList
    601        )
    602    );
    603  };
    604 
    605  onClick(e, line, ch) {
    606    const { selectedSource, jumpToMappedLocation } = this.props;
    607 
    608    if (!selectedSource) {
    609      return;
    610    }
    611 
    612    const sourceLocation = createLocation({
    613      source: selectedSource,
    614      line: fromEditorLine(selectedSource, line),
    615      column: this.state.editor.isWasm ? 0 : ch,
    616    });
    617 
    618    if (e.metaKey && e.altKey) {
    619      jumpToMappedLocation(sourceLocation);
    620    }
    621  }
    622 
    623  shouldScrollToLocation(nextProps) {
    624    if (
    625      !nextProps.selectedLocation?.line ||
    626      !nextProps.selectedSourceTextContent
    627    ) {
    628      return false;
    629    }
    630 
    631    const { selectedLocation, selectedSourceTextContent } = this.props;
    632    const contentChanged =
    633      !selectedSourceTextContent?.value &&
    634      nextProps.selectedSourceTextContent?.value;
    635    const locationChanged = selectedLocation !== nextProps.selectedLocation;
    636 
    637    return contentChanged || locationChanged;
    638  }
    639 
    640  async scrollToLocation(nextProps, editor) {
    641    const { selectedLocation } = nextProps;
    642    const { line, column } = toEditorPosition(selectedLocation);
    643    await editor.scrollTo(line, column);
    644 
    645    // Prevent focusing codemirror if we don't want to highlight the line
    646    // mostly to avoid changing the focus from where it currently is.
    647    // For example in the Search Bar.
    648    if (this.props.shouldHighlightSelectedLocation) {
    649      editor.focus();
    650    }
    651    // This should also scroll the editor to the specified position
    652    await editor.setCursorAt(line, column);
    653  }
    654 
    655  async setText(props, editor) {
    656    const { selectedSource, selectedSourceTextContent } = props;
    657 
    658    if (!editor) {
    659      return;
    660    }
    661 
    662    // check if we previously had a selected source
    663    if (!selectedSource) {
    664      return;
    665    }
    666 
    667    if (!selectedSourceTextContent?.value) {
    668      this.showLoadingMessage(editor);
    669      return;
    670    }
    671 
    672    if (isRejected(selectedSourceTextContent)) {
    673      let { value } = selectedSourceTextContent;
    674      if (typeof value !== "string") {
    675        value = "Unexpected source error";
    676      }
    677 
    678      this.showErrorMessage(value);
    679      return;
    680    }
    681    await editor.setText(selectedSourceTextContent.value.value, {
    682      documentId: selectedSource.id,
    683    });
    684  }
    685 
    686  showErrorMessage(msg) {
    687    const { editor } = this.state;
    688    if (!editor) {
    689      return;
    690    }
    691 
    692    let error;
    693    if (msg.includes("WebAssembly binary source is not available")) {
    694      error = L10N.getStr("wasmIsNotAvailable");
    695    } else {
    696      error = L10N.getFormatStr("errorLoadingText3", msg);
    697    }
    698    editor.setText(error);
    699  }
    700 
    701  showLoadingMessage(editor) {
    702    editor.setText(L10N.getStr("loadingText"));
    703  }
    704 
    705  getInlineEditorStyles() {
    706    const { searchInFileEnabled } = this.props;
    707 
    708    if (searchInFileEnabled) {
    709      return {
    710        height: `calc(100% - ${cssVars.searchbarHeight})`,
    711      };
    712    }
    713 
    714    return {
    715      height: "100%",
    716    };
    717  }
    718 
    719  // eslint-disable-next-line complexity
    720  renderItems() {
    721    const {
    722      selectedSource,
    723      conditionalPanelLocation,
    724      isPaused,
    725      isTraceSelected,
    726      inlinePreviewEnabled,
    727      highlightedLineRange,
    728      isOriginalSourceAndMapScopesEnabled,
    729      selectedSourceTextContent,
    730      selectedTraceLocation,
    731    } = this.props;
    732    const { editor } = this.state;
    733 
    734    if (!selectedSource || !editor) {
    735      return null;
    736    }
    737 
    738    // Only load the sub components if the content has loaded without issues.
    739    if (selectedSourceTextContent && !isFulfilled(selectedSourceTextContent)) {
    740      return null;
    741    }
    742 
    743    return React.createElement(
    744      React.Fragment,
    745      null,
    746      React.createElement(Breakpoints, { editor }),
    747      selectedTraceLocation
    748        ? React.createElement(TracePanel, {
    749            editor,
    750          })
    751        : null,
    752      (isPaused || isTraceSelected) &&
    753        selectedSource.isOriginal &&
    754        !selectedSource.isPrettyPrinted &&
    755        !isOriginalSourceAndMapScopesEnabled
    756        ? null
    757        : React.createElement(Preview, {
    758            editor,
    759            editorRef: this.$editorWrapper,
    760          }),
    761      React.createElement(DebugLine, { editor, selectedSource }),
    762      React.createElement(HighlightLine, { editor }),
    763      React.createElement(Exceptions, { editor }),
    764      conditionalPanelLocation
    765        ? React.createElement(ConditionalPanel, {
    766            editor,
    767            selectedSource,
    768          })
    769        : null,
    770      (isPaused || isTraceSelected) &&
    771        inlinePreviewEnabled &&
    772        (!selectedSource.isOriginal ||
    773          selectedSource.isPrettyPrinted ||
    774          isOriginalSourceAndMapScopesEnabled)
    775        ? React.createElement(InlinePreviews, {
    776            editor,
    777          })
    778        : null,
    779      highlightedLineRange
    780        ? React.createElement(HighlightLines, {
    781            editor,
    782            range: highlightedLineRange,
    783          })
    784        : null,
    785      React.createElement(ColumnBreakpoints, {
    786        editor,
    787      })
    788    );
    789  }
    790 
    791  renderSearchInFileBar() {
    792    if (!this.props.selectedSource) {
    793      return null;
    794    }
    795    return React.createElement(SearchInFileBar, {
    796      editor: this.state.editor,
    797    });
    798  }
    799 
    800  render() {
    801    const { selectedSourceIsBlackBoxed, skipPausing } = this.props;
    802    return div(
    803      {
    804        className: classnames("editor-wrapper", {
    805          blackboxed: selectedSourceIsBlackBoxed,
    806          "skip-pausing": skipPausing,
    807        }),
    808        ref: c => (this.$editorWrapper = c),
    809      },
    810      div({
    811        className: "editor-mount devtools-monospace",
    812        style: this.getInlineEditorStyles(),
    813      }),
    814      this.renderSearchInFileBar(),
    815      this.renderItems()
    816    );
    817  }
    818 }
    819 
    820 Editor.contextTypes = {
    821  shortcuts: PropTypes.object,
    822 };
    823 
    824 const mapStateToProps = state => {
    825  const selectedSource = getSelectedSource(state);
    826  const selectedLocation = getSelectedLocation(state);
    827 
    828  const selectedSourceTextContent = getSelectedSourceTextContent(state);
    829 
    830  return {
    831    selectedLocation,
    832    selectedSource,
    833    // Settled means the content loaded succesfully (fulfilled) or the there was
    834    // error (rejected)
    835    selectedSourceTextContent: asSettled(selectedSourceTextContent),
    836    selectedSourceIsBlackBoxed: selectedSource
    837      ? isSourceBlackBoxed(state, selectedSource)
    838      : null,
    839    isSourceOnIgnoreList:
    840      isSourceMapIgnoreListEnabled(state) &&
    841      isSourceOnSourceMapIgnoreList(state, selectedSource),
    842    searchInFileEnabled: getActiveSearch(state) === "file",
    843    conditionalPanelLocation: getConditionalPanelLocation(state),
    844    isPaused: getIsCurrentThreadPaused(state),
    845    isTraceSelected: getSelectedTraceIndex(state) != null,
    846    skipPausing: getSkipPausing(state),
    847    inlinePreviewEnabled: getInlinePreview(state),
    848    blackboxedRanges: getBlackBoxRanges(state),
    849    breakableLines: getSelectedBreakableLines(state),
    850    highlightedLineRange: getHighlightedLineRangeForSelectedSource(state),
    851    isOriginalSourceAndMapScopesEnabled:
    852      selectedSource?.isOriginal && isMapScopesEnabled(state),
    853    shouldScrollToSelectedLocation: getShouldScrollToSelectedLocation(state),
    854    shouldHighlightSelectedLocation: getShouldHighlightSelectedLocation(state),
    855    selectedTraceLocation: getSelectedTraceLocation(state),
    856  };
    857 };
    858 
    859 const mapDispatchToProps = dispatch => ({
    860  ...bindActionCreators(
    861    {
    862      openConditionalPanel: actions.openConditionalPanel,
    863      closeConditionalPanel: actions.closeConditionalPanel,
    864      continueToHere: actions.continueToHere,
    865      toggleBreakpointAtLine: actions.toggleBreakpointAtLine,
    866      addBreakpointAtLine: actions.addBreakpointAtLine,
    867      jumpToMappedLocation: actions.jumpToMappedLocation,
    868      updateViewport: actions.updateViewport,
    869      closeTabForSource: actions.closeTabForSource,
    870      showEditorContextMenu: actions.showEditorContextMenu,
    871      showEditorGutterContextMenu: actions.showEditorGutterContextMenu,
    872      selectLocation: actions.selectLocation,
    873      setInScopeLines: actions.setInScopeLines,
    874    },
    875    dispatch
    876  ),
    877 });
    878 
    879 export default connect(mapStateToProps, mapDispatchToProps)(Editor);