text: /*
   * Copyright (C) 2011 Google Inc. All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions are
   * met:
   *
   *     * Redistributions of source code must retain the above copyright
   * notice, this list of conditions and the following disclaimer.
   *     * Redistributions in binary form must reproduce the above
   * copyright notice, this list of conditions and the following disclaimer
   * in the documentation and/or other materials provided with the
   * distribution.
   *     * Neither the name of Google Inc. nor the names of its
   * contributors may be used to endorse or promote products derived from
   * this software without specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
   * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
   * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
   * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   */
  
  import * as Bindings from '../bindings/bindings.js';
  import * as Common from '../common/common.js';
  import * as Host from '../host/host.js';
  import * as ObjectUI from '../object_ui/object_ui.js';
  import * as Root from '../root/root.js';
  import * as SDK from '../sdk/sdk.js';
  import * as SourceFrame from '../source_frame/source_frame.js';
  import * as TextEditor from '../text_editor/text_editor.js';
  import * as TextUtils from '../text_utils/text_utils.js';
  import * as UI from '../ui/ui.js';
  import * as Workspace from '../workspace/workspace.js';
  
  import {AddSourceMapURLDialog} from './AddSourceMapURLDialog.js';
  import {BreakpointEditDialog, LogpointPrefix} from './BreakpointEditDialog.js';
  import {Plugin} from './Plugin.js';
  import {ScriptFormatterEditorAction} from './ScriptFormatterEditorAction.js';
  import {resolveExpression, resolveScopeInObject} from './SourceMapNamesResolver.js';
  import {SourcesPanel} from './SourcesPanel.js';
  import {EditorAction} from './SourcesView.js';
  
  // eslint-disable-next-line no-unused-vars
  class DecoratorWidget extends HTMLDivElement {
    constructor() {
      super();
      /** @type {!Map<string,!HTMLElement>} */
      this.__nameToToken;
    }
  }
  
  export class DebuggerPlugin extends Plugin {
    /**
     * @param {!SourceFrame.SourcesTextEditor.SourcesTextEditor} textEditor
     * @param {!Workspace.UISourceCode.UISourceCode} uiSourceCode
     * @param {!SourceFrame.SourceFrame.Transformer} transformer
     */
    constructor(textEditor, uiSourceCode, transformer) {
      super();
      this._textEditor = textEditor;
      this._uiSourceCode = uiSourceCode;
      this._transformer = transformer;
  
      /** @type {?Workspace.UISourceCode.UILocation} */
      this._executionLocation = null;
      this._controlDown = false;
      /** @type {?number} */
      this._asyncStepInHoveredLine = 0;
      this._asyncStepInHovered = false;
      /** @type {?number} */
      this._clearValueWidgetsTimer = null;
      /** @type {?UI.Infobar.Infobar} */
      this._sourceMapInfobar = null;
      /** @type {?number} */
      this._controlTimeout = null;
  
      this._scriptsPanel = SourcesPanel.instance();
      this._breakpointManager = Bindings.BreakpointManager.BreakpointManager.instance();
      if (uiSourceCode.project().type() === Workspace.Workspace.projectTypes.Debugger) {
        this._textEditor.element.classList.add('source-frame-debugger-script');
      }
  
      this._popoverHelper =
          new UI.PopoverHelper.PopoverHelper(this._scriptsPanel.element, this._getPopoverRequest.bind(this));
      this._popoverHelper.setDisableOnClick(true);
      this._popoverHelper.setTimeout(250, 250);
      this._popoverHelper.setHasPadding(true);
      this._boundPopoverHelperHide = this._popoverHelper.hidePopover.bind(this._popoverHelper);
      this._scriptsPanel.element.addEventListener('scroll', this._boundPopoverHelperHide, true);
  
      const shortcutHandlers = {
        'debugger.toggle-breakpoint': async () => {
          const selection = this._textEditor.selection();
          if (!selection) {
            return false;
          }
          await this._toggleBreakpoint(selection.startLine, false);
          return true;
        },
        'debugger.toggle-breakpoint-enabled': async () => {
          const selection = this._textEditor.selection();
          if (!selection) {
            return false;
          }
          await this._toggleBreakpoint(selection.startLine, true);
          return true;
        },
        'debugger.breakpoint-input-window': async () => {
          const selection = this._textEditor.selection();
          if (!selection) {
            return false;
          }
          const breakpoints = this._lineBreakpointDecorations(selection.startLine)
                                  .map(decoration => decoration.breakpoint)
                                  .filter(breakpoint => !!breakpoint);
          let breakpoint = null;
          if (breakpoints.length) {
            breakpoint = breakpoints[0];
          }
          const isLogpoint = breakpoint ? breakpoint.condition().includes(LogpointPrefix) : false;
          this._editBreakpointCondition(selection.startLine, breakpoint, null, isLogpoint);
          return true;
        }
      };
      UI.ShortcutRegistry.ShortcutRegistry.instance().addShortcutListener(this._textEditor.element, shortcutHandlers);
      this._boundKeyDown = /** @type {function(!Event): void} */ (this._onKeyDown.bind(this));
      this._textEditor.element.addEventListener('keydown', this._boundKeyDown, true);
      this._boundKeyUp = /** @type {function(!Event): void} */ (this._onKeyUp.bind(this));
      this._textEditor.element.addEventListener('keyup', this._boundKeyUp, true);
      this._boundMouseMove = /** @type {function(!Event): void} */ (this._onMouseMove.bind(this));
      this._textEditor.element.addEventListener('mousemove', this._boundMouseMove, false);
      this._boundMouseDown = /** @type {function(!Event): void} */ (this._onMouseDown.bind(this));
      this._textEditor.element.addEventListener('mousedown', this._boundMouseDown, true);
      this._boundBlur = /** @type {function(!Event): void} */ (this._onBlur.bind(this));
      this._textEditor.element.addEventListener('focusout', this._boundBlur, false);
      this._boundWheel = /** @type {function(!Event): void} */ (this._onWheel.bind(this));
      this._textEditor.element.addEventListener('wheel', this._boundWheel, true);
  
      this._boundGutterClick =
          /** @type {function(!Common.EventTarget.EventTargetEvent): void} */ (e => {
            this._handleGutterClick(e);
          });
      this._textEditor.addEventListener(SourceFrame.SourcesTextEditor.Events.GutterClick, this._boundGutterClick, this);
  
      this._breakpointManager.addEventListener(
          Bindings.BreakpointManager.Events.BreakpointAdded, this._breakpointAdded, this);
      this._breakpointManager.addEventListener(
          Bindings.BreakpointManager.Events.BreakpointRemoved, this._breakpointRemoved, this);
  
      this._uiSourceCode.addEventListener(
          Workspace.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this);
      this._uiSourceCode.addEventListener(
          Workspace.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this);
  
      /** @type {!Set<!BreakpointDecoration>} */
      this._breakpointDecorations = new Set();
      /** @type {!Map<!Bindings.BreakpointManager.Breakpoint, !BreakpointDecoration>} */
      this._decorationByBreakpoint = new Map();
      /** @type {!Set<number>} */
      this._possibleBreakpointsRequested = new Set();
  
      /** @type {!Map.<!SDK.DebuggerModel.DebuggerModel, !Bindings.ResourceScriptMapping.ResourceScriptFile>}*/
      this._scriptFileForDebuggerModel = new Map();
  
      Common.Settings.Settings.instance()
          .moduleSetting('skipStackFramesPattern')
          .addChangeListener(this._showBlackboxInfobarIfNeeded, this);
      Common.Settings.Settings.instance()
          .moduleSetting('skipContentScripts')
          .addChangeListener(this._showBlackboxInfobarIfNeeded, this);
  
      /** @type {!Map.<number, !DecoratorWidget>} */
      this._valueWidgets = new Map();
      /** @type {?Map<!CodeMirror.TextMarker, !Function>} */
      this._continueToLocationDecorations = null;
  
      UI.Context.Context.instance().addFlavorChangeListener(SDK.DebuggerModel.CallFrame, this._callFrameChanged, this);
      this._liveLocationPool = new Bindings.LiveLocation.LiveLocationPool();
      this._callFrameChanged();
  
      this._updateScriptFiles();
  
      if (this._uiSourceCode.isDirty()) {
        this._muted = true;
        this._mutedFromStart = true;
      } else {
        this._muted = false;
        this._mutedFromStart = false;
        this._initializeBreakpoints();
      }
  
      /** @type {?UI.Infobar.Infobar} */
      this._blackboxInfobar = null;
      this._showBlackboxInfobarIfNeeded();
  
      for (const scriptFile of this._scriptFileForDebuggerModel.values()) {
        scriptFile.checkMapping();
      }
  
      this._hasLineWithoutMapping = false;
      this._updateLinesWithoutMappingHighlight();
      if (!Root.Runtime.experiments.isEnabled('sourcesPrettyPrint')) {
        /** @type {?UI.Infobar.Infobar} */
        this._prettyPrintInfobar = null;
        this._detectMinified();
      }
    }
  
    /**
     * @override
     * @param {!Workspace.UISourceCode.UISourceCode} uiSourceCode
     * @return {boolean}
     */
    static accepts(uiSourceCode) {
      return uiSourceCode.contentType().hasScripts();
    }
  
    _showBlackboxInfobarIfNeeded() {
      const uiSourceCode = this._uiSourceCode;
      if (!uiSourceCode.contentType().hasScripts()) {
        return;
      }
      const projectType = uiSourceCode.project().type();
      if (!Bindings.BlackboxManager.BlackboxManager.instance().isBlackboxedUISourceCode(uiSourceCode)) {
        this._hideBlackboxInfobar();
        return;
      }
  
      if (this._blackboxInfobar) {
        this._blackboxInfobar.dispose();
      }
  
      function unblackbox() {
        Bindings.BlackboxManager.BlackboxManager.instance().unblackboxUISourceCode(uiSourceCode);
        if (projectType === Workspace.Workspace.projectTypes.ContentScripts) {
          Bindings.BlackboxManager.BlackboxManager.instance().unblackboxContentScripts();
        }
      }
  
      const infobar = new UI.Infobar.Infobar(
          UI.Infobar.Type.Warning, Common.UIString.UIString('This script is blackboxed in the debugger'), [
            {text: ls`Unblackbox`, highlight: false, delegate: unblackbox, dismiss: true}, {
              text: ls`Configure`,
              highlight: false,
              delegate:
                  UI.ViewManager.ViewManager.instance().showView.bind(UI.ViewManager.ViewManager.instance(), 'blackbox'),
              dismiss: false
            }
          ]);
      this._blackboxInfobar = infobar;
  
      infobar.createDetailsRowMessage(Common.UIString.UIString(
          'The debugger will skip stepping through this script, and will not stop on exceptions.'));
  
      const scriptFile =
          this._scriptFileForDebuggerModel.size ? this._scriptFileForDebuggerModel.values().next().value : null;
      if (scriptFile && scriptFile.hasSourceMapURL()) {
        infobar.createDetailsRowMessage(Common.UIString.UIString('Source map found, but ignored for blackboxed file.'));
      }
      this._textEditor.attachInfobar(this._blackboxInfobar);
    }
  
    _hideBlackboxInfobar() {
      if (!this._blackboxInfobar) {
        return;
      }
      this._blackboxInfobar.dispose();
      this._blackboxInfobar = null;
    }
  
    /**
     * @override
     */
    wasShown() {
      if (this._executionLocation) {
        // We need SourcesTextEditor to be initialized prior to this call. @see crbug.com/499889
        setImmediate(() => {
          this._generateValuesInSource();
        });
      }
    }
  
    /**
     * @override
     */
    willHide() {
      this._popoverHelper.hidePopover();
    }
  
    /**
     * @override
     * @param {!UI.ContextMenu.ContextMenu} contextMenu
     * @param {number} editorLineNumber
     * @return {!Promise<void>}
     */
    async populateLineGutterContextMenu(contextMenu, editorLineNumber) {
      const uiLocation = new Workspace.UISourceCode.UILocation(this._uiSourceCode, editorLineNumber, 0);
      this._scriptsPanel.appendUILocationItems(contextMenu, uiLocation);
      const breakpoints =
          /** @type {!Array<!Bindings.BreakpointManager.Breakpoint>} */ (this._lineBreakpointDecorations(editorLineNumber)
                                                                             .map(decoration => decoration.breakpoint)
                                                                             .filter(breakpoint => !!breakpoint));
      const hasOnlyJavaScript = Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance()
                                    .scriptsForUISourceCode(this._uiSourceCode)
                                    .every(script => script.isJavaScript());
      if (!breakpoints.length) {
        contextMenu.debugSection().appendItem(
            Common.UIString.UIString('Add breakpoint'), this._createNewBreakpoint.bind(this, editorLineNumber, '', true));
        // Conditional breakpoints, logpoints and 'Never pause here'
        // are currently only available for JavaScript debugging.
        if (hasOnlyJavaScript) {
          contextMenu.debugSection().appendItem(
              Common.UIString.UIString('Add conditional breakpoint…'),
              this._editBreakpointCondition.bind(this, editorLineNumber, null, null, false /* preferLogpoint */));
          contextMenu.debugSection().appendItem(
              ls`Add logpoint…`,
              this._editBreakpointCondition.bind(this, editorLineNumber, null, null, true /* preferLogpoint */));
          contextMenu.debugSection().appendItem(
              Common.UIString.UIString('Never pause here'),
              this._createNewBreakpoint.bind(this, editorLineNumber, 'false', true));
        }
      } else {
        const hasOneBreakpoint = breakpoints.length === 1;
        const removeTitle = hasOneBreakpoint ? Common.UIString.UIString('Remove breakpoint') :
                                               Common.UIString.UIString('Remove all breakpoints in line');
        contextMenu.debugSection().appendItem(removeTitle, () => breakpoints.map(breakpoint => breakpoint.remove(false)));
        if (hasOneBreakpoint && hasOnlyJavaScript) {
          // Editing breakpoints only make sense for conditional breakpoints
          // and logpoints and both are currently only available for JavaScript
          // debugging.
          contextMenu.debugSection().appendItem(
              Common.UIString.UIString('Edit breakpoint…'),
              this._editBreakpointCondition.bind(
                  this, editorLineNumber, breakpoints[0], null, false /* preferLogpoint */));
        }
        const hasEnabled = breakpoints.some(breakpoint => breakpoint.enabled());
        if (hasEnabled) {
          const title = hasOneBreakpoint ? Common.UIString.UIString('Disable breakpoint') :
                                           Common.UIString.UIString('Disable all breakpoints in line');
          contextMenu.debugSection().appendItem(title, () => breakpoints.map(breakpoint => breakpoint.setEnabled(false)));
        }
        const hasDisabled = breakpoints.some(breakpoint => !breakpoint.enabled());
        if (hasDisabled) {
          const title = hasOneBreakpoint ? Common.UIString.UIString('Enable breakpoint') :
                                           Common.UIString.UIString('Enable all breakpoints in line');
          contextMenu.debugSection().appendItem(title, () => breakpoints.map(breakpoint => breakpoint.setEnabled(true)));
        }
      }
    }
  
    /**
     * @override
     * @param {!UI.ContextMenu.ContextMenu} contextMenu
     * @param {number} editorLineNumber
     * @param {number} editorColumnNumber
     * @return {!Promise<void>}
     */
    populateTextAreaContextMenu(contextMenu, editorLineNumber, editorColumnNumber) {
      /**
       * @param {!Bindings.ResourceScriptMapping.ResourceScriptFile} scriptFile
       */
      function addSourceMapURL(scriptFile) {
        const dialog = new AddSourceMapURLDialog(addSourceMapURLDialogCallback.bind(null, scriptFile));
        dialog.show();
      }
  
      /**
       * @param {!Bindings.ResourceScriptMapping.ResourceScriptFile} scriptFile
       * @param {string} url
       */
      function addSourceMapURLDialogCallback(scriptFile, url) {
        if (!url) {
          return;
        }
        scriptFile.addSourceMapURL(url);
      }
  
      /**
       * @this {DebuggerPlugin}
       */
      function populateSourceMapMembers() {
        if (this._uiSourceCode.project().type() === Workspace.Workspace.projectTypes.Network &&
            Common.Settings.Settings.instance().moduleSetting('jsSourceMapsEnabled').get() &&
            !Bindings.BlackboxManager.BlackboxManager.instance().isBlackboxedUISourceCode(this._uiSourceCode)) {
          if (this._scriptFileForDebuggerModel.size) {
            const scriptFile = this._scriptFileForDebuggerModel.values().next().value;
            const addSourceMapURLLabel = Common.UIString.UIString('Add source map…');
            contextMenu.debugSection().appendItem(addSourceMapURLLabel, addSourceMapURL.bind(null, scriptFile));
          }
        }
      }
  
      return super.populateTextAreaContextMenu(contextMenu, editorLineNumber, editorColumnNumber)
          .then(populateSourceMapMembers.bind(this));
    }
  
    _workingCopyChanged() {
      if (this._scriptFileForDebuggerModel.size) {
        return;
      }
  
      if (this._uiSourceCode.isDirty()) {
        this._muteBreakpointsWhileEditing();
      } else {
        this._restoreBreakpointsAfterEditing();
      }
    }
  
    /**
     * @param {!Common.EventTarget.EventTargetEvent} event
     */
    _workingCopyCommitted(event) {
      this._scriptsPanel.updateLastModificationTime();
      if (!this._scriptFileForDebuggerModel.size) {
        this._restoreBreakpointsAfterEditing();
      }
    }
  
    _didMergeToVM() {
      this._restoreBreakpointsIfConsistentScripts();
    }
  
    _didDivergeFromVM() {
      this._muteBreakpointsWhileEditing();
    }
  
    _muteBreakpointsWhileEditing() {
      if (this._muted) {
        return;
      }
      for (const decoration of this._breakpointDecorations) {
        this._updateBreakpointDecoration(decoration);
      }
      this._muted = true;
    }
  
    async _restoreBreakpointsIfConsistentScripts() {
      for (const scriptFile of this._scriptFileForDebuggerModel.values()) {
        if (scriptFile.hasDivergedFromVM() || scriptFile.isMergingToVM()) {
          return;
        }
      }
  
      await this._restoreBreakpointsAfterEditing();
    }
  
    async _restoreBreakpointsAfterEditing() {
      this._muted = false;
      if (this._mutedFromStart) {
        this._mutedFromStart = false;
        this._initializeBreakpoints();
        return;
      }
      const decorations = Array.from(this._breakpointDecorations);
      this._breakpointDecorations.clear();
      this._textEditor.operation(() => decorations.map(decoration => decoration.hide()));
      for (const decoration of decorations) {
        if (!decoration.breakpoint) {
          continue;
        }
        const enabled = decoration.enabled;
        decoration.breakpoint.remove(false);
        const location = decoration.handle.resolve();
        if (location) {
          await this._setBreakpoint(location.lineNumber, location.columnNumber, decoration.condition, enabled);
        }
      }
    }
  
    /**
     * @param {string}  tokenType
     * @return {boolean}
     */
    _isIdentifier(tokenType) {
      return tokenType.startsWith('js-variable') || tokenType.startsWith('js-property') || tokenType === 'js-def' ||
          tokenType === 'variable';
    }
  
    /**
     * @param {!MouseEvent} event
     * @return {?UI.PopoverHelper.PopoverRequest}
     */
    _getPopoverRequest(event) {
      if (UI.KeyboardShortcut.KeyboardShortcut.eventHasCtrlOrMeta(event)) {
        return null;
      }
      const target = UI.Context.Context.instance().flavor(SDK.SDKModel.Target);
      const debuggerModel = target ? target.model(SDK.DebuggerModel.DebuggerModel) : null;
      if (!debuggerModel || !debuggerModel.isPaused()) {
        return null;
      }
  
      const textPosition = this._textEditor.coordinatesToCursorPosition(event.x, event.y);
      if (!textPosition) {
        return null;
      }
  
      const mouseLine = textPosition.startLine;
      const mouseColumn = textPosition.startColumn;
      const textSelection = this._textEditor.selection().normalize();
      let anchorBox;
      let editorLineNumber = -1;
      let startHighlight = -1;
      let endHighlight = -1;
  
      const selectedCallFrame =
          /** @type {!SDK.DebuggerModel.CallFrame} */ (UI.Context.Context.instance().flavor(SDK.DebuggerModel.CallFrame));
      if (!selectedCallFrame) {
        return null;
      }
  
  
      if (textSelection && !textSelection.isEmpty()) {
        if (textSelection.startLine !== textSelection.endLine || textSelection.startLine !== mouseLine ||
            mouseColumn < textSelection.startColumn || mouseColumn > textSelection.endColumn) {
          return null;
        }
  
        const leftCorner =
            this._textEditor.cursorPositionToCoordinates(textSelection.startLine, textSelection.startColumn);
        const rightCorner = this._textEditor.cursorPositionToCoordinates(textSelection.endLine, textSelection.endColumn);
        if (!leftCorner) {
          throw new Error('Expected leftCorner to not be null.');
        }
        if (!rightCorner) {
          throw new Error('Expected rightCorner to not be null.');
        }
        anchorBox = new AnchorBox(leftCorner.x, leftCorner.y, rightCorner.x - leftCorner.x, leftCorner.height);
        editorLineNumber = textSelection.startLine;
        startHighlight = textSelection.startColumn;
        endHighlight = textSelection.endColumn - 1;
      } else if (this._uiSourceCode.mimeType() === 'application/wasm') {
        const token = this._textEditor.tokenAtTextPosition(textPosition.startLine, textPosition.startColumn);
        if (!token || token.type !== 'variable-2') {
          return null;
        }
        const leftCorner = this._textEditor.cursorPositionToCoordinates(textPosition.startLine, token.startColumn);
        const rightCorner = this._textEditor.cursorPositionToCoordinates(textPosition.startLine, token.endColumn - 1);
        if (!leftCorner) {
          throw new Error('Expected leftCorner to not be null.');
        }
        if (!rightCorner) {
          throw new Error('Expected rightCorner to not be null.');
        }
        anchorBox = new AnchorBox(leftCorner.x, leftCorner.y, rightCorner.x - leftCorner.x, leftCorner.height);
        editorLineNumber = textPosition.startLine;
        startHighlight = token.startColumn;
        endHighlight = token.endColumn - 1;
      } else {
        const token = this._textEditor.tokenAtTextPosition(textPosition.startLine, textPosition.startColumn);
        if (!token || !token.type) {
          return null;
        }
        editorLineNumber = textPosition.startLine;
        const line = this._textEditor.line(editorLineNumber);
        const tokenContent = line.substring(token.startColumn, token.endColumn);
  
        const isIdentifier = this._isIdentifier(token.type);
        if (!isIdentifier && (token.type !== 'js-keyword' || tokenContent !== 'this')) {
          return null;
        }
  
        const leftCorner = this._textEditor.cursorPositionToCoordinates(editorLineNumber, token.startColumn);
        const rightCorner = this._textEditor.cursorPositionToCoordinates(editorLineNumber, token.endColumn - 1);
        if (!leftCorner) {
          throw new Error('Expected leftCorner to not be null.');
        }
        if (!rightCorner) {
          throw new Error('Expected rightCorner to not be null.');
        }
        anchorBox = new AnchorBox(leftCorner.x, leftCorner.y, rightCorner.x - leftCorner.x, leftCorner.height);
  
        startHighlight = token.startColumn;
        endHighlight = token.endColumn - 1;
        while (startHighlight > 1 && line.charAt(startHighlight - 1) === '.') {
          const tokenBefore = this._textEditor.tokenAtTextPosition(editorLineNumber, startHighlight - 2);
          if (!tokenBefore || !tokenBefore.type) {
            return null;
          }
          if (tokenBefore.type === 'js-meta') {
            break;
          }
          if (tokenBefore.type === 'js-string-2') {
            // If we hit a template literal, find the opening ` in this line.
            // TODO(bmeurer): We should eventually replace this tokenization
            // approach with a proper soluation based on parsing, maybe reusing
            // the Parser and AST inside V8 for this (or potentially relying on
            // acorn to do the job).
            if (tokenBefore.endColumn < 2) {
              return null;
            }
            startHighlight = line.lastIndexOf('`', tokenBefore.endColumn - 2);
            if (startHighlight < 0) {
              return null;
            }
            break;
          }
          startHighlight = tokenBefore.startColumn;
        }
      }
  
      /** @type {?ObjectUI.ObjectPopoverHelper.ObjectPopoverHelper} */
      let objectPopoverHelper;
      /** @type {?CodeMirror.TextMarker} */
      let highlightDescriptor;
  
      /**
       * @param {!Workspace.UISourceCode.UISourceCode} uiSourceCode
       * @param {string} evaluationText
       * @return {!Promise<?SDK.RuntimeModel.EvaluationResult>}
       */
      async function evaluate(uiSourceCode, evaluationText) {
        const resolvedText = await resolveExpression(
            selectedCallFrame, evaluationText, uiSourceCode, editorLineNumber, startHighlight, endHighlight);
        return await selectedCallFrame.evaluate({
          expression: resolvedText || evaluationText,
          objectGroup: 'popover',
          includeCommandLineAPI: false,
          silent: true,
          returnByValue: false,
          generatePreview: false,
          throwOnSideEffect: undefined,
          timeout: undefined,
          disableBreaks: undefined,
          replMode: undefined,
          allowUnsafeEvalBlockedByCSP: undefined
        });
      }
  
      return {
        box: anchorBox,
        show: async popover => {
          const evaluationText = this._textEditor.line(editorLineNumber).substring(startHighlight, endHighlight + 1);
          const result = await evaluate(this._uiSourceCode, evaluationText);
  
          if (!result || 'error' in result || !result.object ||
              (result.object.type === 'object' && result.object.subtype === 'error')) {
            return false;
          }
          objectPopoverHelper =
              await ObjectUI.ObjectPopoverHelper.ObjectPopoverHelper.buildObjectPopover(result.object, popover);
          const potentiallyUpdatedCallFrame = UI.Context.Context.instance().flavor(SDK.DebuggerModel.CallFrame);
          if (!objectPopoverHelper || selectedCallFrame !== potentiallyUpdatedCallFrame) {
            debuggerModel.runtimeModel().releaseObjectGroup('popover');
            if (objectPopoverHelper) {
              objectPopoverHelper.dispose();
            }
            return false;
          }
          const highlightRange =
              new TextUtils.TextRange.TextRange(editorLineNumber, startHighlight, editorLineNumber, endHighlight);
          highlightDescriptor = this._textEditor.highlightRange(highlightRange, 'source-frame-eval-expression');
          return true;
        },
        hide: () => {
          if (objectPopoverHelper) {
            objectPopoverHelper.dispose();
          }
          debuggerModel.runtimeModel().releaseObjectGroup('popover');
          if (highlightDescriptor) {
            this._textEditor.removeHighlight(highlightDescriptor);
          }
        }
      };
    }
  
    /**
     * @param {!WheelEvent} event
     */
    _onWheel(event) {
      if (this._executionLocation && UI.KeyboardShortcut.KeyboardShortcut.eventHasCtrlOrMeta(event)) {
        event.preventDefault();
      }
    }
  
    /**
     * @param {!KeyboardEvent} event
     */
    _onKeyDown(event) {
      if (!event.ctrlKey || (!event.metaKey && Host.Platform.isMac())) {
        this._clearControlDown();
      }
  
      if (event.key === 'Escape') {
        if (this._popoverHelper.isPopoverVisible()) {
          this._popoverHelper.hidePopover();
          event.consume();
        }
        return;
      }
  
      if (UI.KeyboardShortcut.KeyboardShortcut.eventHasCtrlOrMeta(event) && this._executionLocation) {
        this._controlDown = true;
        if (event.key === (Host.Platform.isMac() ? 'Meta' : 'Control')) {
          this._controlTimeout = window.setTimeout(() => {
            if (this._executionLocation && this._controlDown) {
              this._showContinueToLocations();
            }
          }, 150);
        }
      }
    }
  
    /**
     * @param {!MouseEvent} event
     */
    _onMouseMove(event) {
      if (this._executionLocation && this._controlDown &&
          UI.KeyboardShortcut.KeyboardShortcut.eventHasCtrlOrMeta(event)) {
        if (!this._continueToLocationDecorations) {
          this._showContinueToLocations();
        }
      }
      if (this._continueToLocationDecorations) {
        const target = /** @type {!Element} */ (event.target);
        const textPosition = this._textEditor.coordinatesToCursorPosition(event.x, event.y);
        const hovering = !!target.enclosingNodeOrSelfWithClass('source-frame-async-step-in');
        this._setAsyncStepInHoveredLine(textPosition ? textPosition.startLine : null, hovering);
      }
    }
  
    /**
     * @param {?number} editorLineNumber
     * @param {boolean} hovered
     */
    _setAsyncStepInHoveredLine(editorLineNumber, hovered) {
      if (this._asyncStepInHoveredLine === editorLineNumber && this._asyncStepInHovered === hovered) {
        return;
      }
      if (this._asyncStepInHovered && this._asyncStepInHoveredLine) {
        this._textEditor.toggleLineClass(this._asyncStepInHoveredLine, 'source-frame-async-step-in-hovered', false);
      }
      this._asyncStepInHoveredLine = editorLineNumber;
      this._asyncStepInHovered = hovered;
      if (this._asyncStepInHovered && this._asyncStepInHoveredLine) {
        this._textEditor.toggleLineClass(this._asyncStepInHoveredLine, 'source-frame-async-step-in-hovered', true);
      }
    }
  
    /**
     * @param {!MouseEvent} event
     */
    _onMouseDown(event) {
      if (!this._executionLocation || !UI.KeyboardShortcut.KeyboardShortcut.eventHasCtrlOrMeta(event)) {
        return;
      }
      if (!this._continueToLocationDecorations) {
        return;
      }
      event.consume();
      const textPosition = this._textEditor.coordinatesToCursorPosition(event.x, event.y);
      if (!textPosition) {
        return;
      }
      for (const decoration of this._continueToLocationDecorations.keys()) {
        const range = decoration.find();
        if (range.from.line !== textPosition.startLine || range.to.line !== textPosition.startLine) {
          continue;
        }
        if (range.from.ch <= textPosition.startColumn && textPosition.startColumn <= range.to.ch) {
          const callback = this._continueToLocationDecorations.get(decoration);
          if (!callback) {
            throw new Error('Expected a function');
          }
          callback();
          break;
        }
      }
    }
  
    /**
     * @param {!Event} event
     */
    _onBlur(event) {
      this._clearControlDown();
    }
  
    /**
     * @param {!KeyboardEvent} event
     */
    _onKeyUp(event) {
      this._clearControlDown();
    }
  
    _clearControlDown() {
      this._controlDown = false;
      this._clearContinueToLocations();
      if (this._controlTimeout) {
        clearTimeout(this._controlTimeout);
      }
    }
  
    /**
     * @param {number} editorLineNumber
     * @param {?Bindings.BreakpointManager.Breakpoint} breakpoint
     * @param {?{lineNumber: number, columnNumber: number}} location
     * @param {boolean=} preferLogpoint
     */
    async _editBreakpointCondition(editorLineNumber, breakpoint, location, preferLogpoint) {
      const oldCondition = breakpoint ? breakpoint.condition() : '';
      const decorationElement = document.createElement('div');
      const dialog = new BreakpointEditDialog(editorLineNumber, oldCondition, !!preferLogpoint, async result => {
        dialog.detach();
        this._textEditor.removeDecoration(decorationElement, editorLineNumber);
        if (!result.committed) {
          return;
        }
        if (breakpoint) {
          breakpoint.setCondition(result.condition);
        } else if (location) {
          await this._setBreakpoint(location.lineNumber, location.columnNumber, result.condition, true);
        } else {
          await this._createNewBreakpoint(editorLineNumber, result.condition, true);
        }
      });
      this._textEditor.addDecoration(decorationElement, editorLineNumber);
      dialog.markAsExternallyManaged();
      dialog.show(decorationElement);
    }
  
    /**
     * @param {!Bindings.LiveLocation.LiveLocation} liveLocation
     */
    async _executionLineChanged(liveLocation) {
      this._clearExecutionLine();
      const uiLocation = await liveLocation.uiLocation();
      if (!uiLocation || uiLocation.uiSourceCode.url() !== this._uiSourceCode.url()) {
        this._executionLocation = null;
        return;
      }
  
      this._executionLocation = uiLocation;
      const editorLocation = this._transformer.uiLocationToEditorLocation(uiLocation.lineNumber, uiLocation.columnNumber);
      this._textEditor.setExecutionLocation(editorLocation.lineNumber, editorLocation.columnNumber);
      if (this._textEditor.isShowing()) {
        // We need SourcesTextEditor to be initialized prior to this call. @see crbug.com/506566
        setImmediate(() => {
          if (this._controlDown) {
            this._showContinueToLocations();
          } else {
            this._generateValuesInSource();
          }
        });
      }
    }
  
    _generateValuesInSource() {
      if (!Common.Settings.Settings.instance().moduleSetting('inlineVariableValues').get()) {
        return;
      }
      const executionContext = UI.Context.Context.instance().flavor(SDK.RuntimeModel.ExecutionContext);
      if (!executionContext) {
        return;
      }
      const callFrame = UI.Context.Context.instance().flavor(SDK.DebuggerModel.CallFrame);
      if (!callFrame) {
        return;
      }
  
      const localScope = callFrame.localScope();
      const functionLocation = callFrame.functionLocation();
      if (localScope && functionLocation) {
        resolveScopeInObject(localScope)
            .getAllProperties(false, false)
            .then(this._prepareScopeVariables.bind(this, callFrame));
      }
    }
  
    _showContinueToLocations() {
      this._popoverHelper.hidePopover();
      const executionContext = UI.Context.Context.instance().flavor(SDK.RuntimeModel.ExecutionContext);
      if (!executionContext) {
        return;
      }
      const callFrame = UI.Context.Context.instance().flavor(SDK.DebuggerModel.CallFrame);
      if (!callFrame) {
        return;
      }
      const start = callFrame.functionLocation() || callFrame.location();
      const debuggerModel = callFrame.debuggerModel;
      debuggerModel.getPossibleBreakpoints(start, null, true)
          .then(locations => this._textEditor.operation(renderLocations.bind(this, locations)));
  
      /**
       * @param {!Array<!SDK.DebuggerModel.BreakLocation>} locations
       * @this {DebuggerPlugin}
       */
      function renderLocations(locations) {
        this._clearContinueToLocationsNoRestore();
        this._textEditor.hideExecutionLineBackground();
        this._continueToLocationDecorations = new Map();
        locations = locations.reverse();
        let previousCallLine = -1;
        for (const location of locations) {
          const editorLocation = this._transformer.uiLocationToEditorLocation(location.lineNumber, location.columnNumber);
          const tokenThatIsPossiblyNull =
              this._textEditor.tokenAtTextPosition(editorLocation.lineNumber, editorLocation.columnNumber);
          if (!tokenThatIsPossiblyNull) {
            continue;
          }
          let token = /** @type {!TextEditor.CodeMirrorTextEditor.Token} */ (tokenThatIsPossiblyNull);
  
          const line = this._textEditor.line(editorLocation.lineNumber);
          let tokenContent = line.substring(token.startColumn, token.endColumn);
          if (!token.type && tokenContent === '.') {
            const nextToken = this._textEditor.tokenAtTextPosition(editorLocation.lineNumber, token.endColumn + 1);
            if (!nextToken) {
              throw new Error('nextToken should not be null.');
            }
            token = nextToken;
            tokenContent = line.substring(token.startColumn, token.endColumn);
          }
          if (!token.type) {
            continue;
          }
          const validKeyword = token.type === 'js-keyword' &&
              (tokenContent === 'this' || tokenContent === 'return' || tokenContent === 'new' ||
               tokenContent === 'continue' || tokenContent === 'break');
          if (!validKeyword && !this._isIdentifier(token.type)) {
            continue;
          }
          if (previousCallLine === editorLocation.lineNumber &&
              location.type !== Protocol.Debugger.BreakLocationType.Call) {
            continue;
          }
  
          let highlightRange = new TextUtils.TextRange.TextRange(
              editorLocation.lineNumber, token.startColumn, editorLocation.lineNumber, token.endColumn - 1);
          let decoration = this._textEditor.highlightRange(highlightRange, 'source-frame-continue-to-location');
          this._continueToLocationDecorations.set(decoration, location.continueToLocation.bind(location));
          if (location.type === Protocol.Debugger.BreakLocationType.Call) {
            previousCallLine = editorLocation.lineNumber;
          }
  
          let isAsyncCall = (line[token.startColumn - 1] === '.' && tokenContent === 'then') ||
              tokenContent === 'setTimeout' || tokenContent === 'setInterval' || tokenContent === 'postMessage';
          if (tokenContent === 'new') {
            const nextToken = this._textEditor.tokenAtTextPosition(editorLocation.lineNumber, token.endColumn + 1);
            if (!nextToken) {
              throw new Error('nextToken should not be null.');
            }
            token = nextToken;
            tokenContent = line.substring(token.startColumn, token.endColumn);
            isAsyncCall = tokenContent === 'Worker';
          }
          const isCurrentPosition = this._executionLocation &&
              location.lineNumber === this._executionLocation.lineNumber &&
              location.columnNumber === this._executionLocation.columnNumber;
          if (location.type === Protocol.Debugger.BreakLocationType.Call && isAsyncCall) {
            const asyncStepInRange =
                this._findAsyncStepInRange(this._textEditor, editorLocation.lineNumber, line, token.endColumn);
            if (asyncStepInRange) {
              highlightRange = new TextUtils.TextRange.TextRange(
                  editorLocation.lineNumber, asyncStepInRange.from, editorLocation.lineNumber, asyncStepInRange.to - 1);
              decoration = this._textEditor.highlightRange(highlightRange, 'source-frame-async-step-in');
              this._continueToLocationDecorations.set(
                  decoration, this._asyncStepIn.bind(this, location, !!isCurrentPosition));
            }
          }
        }
  
        this._continueToLocationRenderedForTest();
      }
    }
  
    _continueToLocationRenderedForTest() {
    }
  
    /**
     * @param {!SourceFrame.SourcesTextEditor.SourcesTextEditor} textEditor
     * @param {number} editorLineNumber
     * @param {string} line
     * @param {number} column
     * @return {?{from: number, to: number}}
     */
    _findAsyncStepInRange(textEditor, editorLineNumber, line, column) {
      /** @type {?TextEditor.CodeMirrorTextEditor.Token} */
      let token = null;
      let tokenText;
      let from = column;
      let to = line.length;
  
      let position = line.indexOf('(', column);
      const argumentsStart = position;
      if (position === -1) {
        return null;
      }
      position++;
  
      skipWhitespace();
      if (position >= line.length) {
        return null;
      }
  
      token = nextToken();
      if (!token) {
        return null;
      }
      from = token.startColumn;
  
      if (token.type === 'js-keyword' && tokenText === 'async') {
        skipWhitespace();
        if (position >= line.length) {
          return {from: from, to: to};
        }
        token = nextToken();
        if (!token) {
          return {from: from, to: to};
        }
      }
  
      if (token.type === 'js-keyword' && tokenText === 'function') {
        return {from: from, to: to};
      }
  
      if (token.type === 'js-string') {
        return {from: argumentsStart, to: to};
      }
  
      if (token.type && this._isIdentifier(token.type)) {
        return {from: from, to: to};
      }
  
      if (tokenText !== '(') {
        return null;
      }
      const closeParen = line.indexOf(')', position);
      if (closeParen === -1 || line.substring(position, closeParen).indexOf('(') !== -1) {
        return {from: from, to: to};
      }
      return {from: from, to: closeParen + 1};
  
      function nextToken() {
        token = textEditor.tokenAtTextPosition(editorLineNumber, position);
        if (token) {
          position = token.endColumn;
          to = token.endColumn;
          tokenText = line.substring(token.startColumn, token.endColumn);
        }
  
        return token;
      }
  
      function skipWhitespace() {
        while (position < line.length) {
          if (line[position] === ' ') {
            position++;
            continue;
          }
          const token = textEditor.tokenAtTextPosition(editorLineNumber, position);
          if (!token) {
            throw new Error('expected token to not be null');
          }
          if (token.type === 'js-comment') {
            position = token.endColumn;
            continue;
          }
          break;
        }
      }
    }
  
    /**
     * @param {!SDK.DebuggerModel.BreakLocation} location
     * @param {boolean} isCurrentPosition
     */
    _asyncStepIn(location, isCurrentPosition) {
      if (!isCurrentPosition) {
        location.continueToLocation(asyncStepIn);
      } else {
        asyncStepIn();
      }
  
      function asyncStepIn() {
        location.debuggerModel.scheduleStepIntoAsync();
      }
    }
  
    /**
     * @param {!SDK.DebuggerModel.CallFrame} callFrame
     * @param {!SDK.RemoteObject.GetPropertiesResult} allProperties
     */
    async _prepareScopeVariables(callFrame, allProperties) {
      const properties = allProperties.properties;
      this._clearValueWidgets();
      if (!properties || !properties.length || properties.length > 500 || !this._textEditor.isShowing()) {
        return;
      }
  
      const functionUILocationPromise =
          Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance().rawLocationToUILocation(
              /** @type {!SDK.DebuggerModel.Location} */ (callFrame.functionLocation()));
      const executionUILocationPromise =
          Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance().rawLocationToUILocation(
              callFrame.location());
      const [functionUILocation, executionUILocation] =
          await Promise.all([functionUILocationPromise, executionUILocationPromise]);
      if (!functionUILocation || !executionUILocation ||
          functionUILocation.uiSourceCode.url() !== this._uiSourceCode.url() ||
          executionUILocation.uiSourceCode.url() !== this._uiSourceCode.url()) {
        return;
      }
  
      const functionEditorLocation =
          this._transformer.uiLocationToEditorLocation(functionUILocation.lineNumber, functionUILocation.columnNumber);
      const executionEditorLocation =
          this._transformer.uiLocationToEditorLocation(executionUILocation.lineNumber, executionUILocation.columnNumber);
      const fromLine = functionEditorLocation.lineNumber;
      const fromColumn = functionEditorLocation.columnNumber;
      const toLine = executionEditorLocation.lineNumber;
      if (fromLine >= toLine || toLine - fromLine > 500 || fromLine < 0 || toLine >= this._textEditor.linesCount) {
        return;
      }
  
      const valuesMap = new Map();
      for (const property of properties) {
        valuesMap.set(property.name, property.value);
      }
  
      /** @type {!Map.<number, !Set<string>>} */
      const namesPerLine = new Map();
      let skipObjectProperty = false;
      const tokenizer = new TextEditor.CodeMirrorUtils.TokenizerFactory().createTokenizer('text/javascript');
      tokenizer(this._textEditor.line(fromLine).substring(fromColumn), processToken.bind(this, fromLine));
      for (let i = fromLine + 1; i < toLine; ++i) {
        tokenizer(this._textEditor.line(i), processToken.bind(this, i));
      }
  
      /**
       * @param {number} editorLineNumber
       * @param {string} tokenValue
       * @param {?string} tokenType
       * @param {number} column
       * @param {number} newColumn
       * @this {DebuggerPlugin}
       */
      function processToken(editorLineNumber, tokenValue, tokenType, column, newColumn) {
        if (!skipObjectProperty && tokenType && this._isIdentifier(tokenType) && valuesMap.get(tokenValue)) {
          let names = namesPerLine.get(editorLineNumber);
          if (!names) {
            names = new Set();
            namesPerLine.set(editorLineNumber, names);
          }
          names.add(tokenValue);
        }
        skipObjectProperty = tokenValue === '.';
      }
      this._textEditor.operation(this._renderDecorations.bind(this, valuesMap, namesPerLine, fromLine, toLine));
    }
  
    /**
     * @param {!Map.<string,!SDK.RemoteObject.RemoteObject>} valuesMap
     * @param {!Map.<number, !Set<string>>} namesPerLine
     * @param {number} fromLine
     * @param {number} toLine
     */
    _renderDecorations(valuesMap, namesPerLine, fromLine, toLine) {
      const formatter = new ObjectUI.RemoteObjectPreviewFormatter.RemoteObjectPreviewFormatter();
      for (let i = fromLine; i < toLine; ++i) {
        const names = namesPerLine.get(i);
        const oldWidget = this._valueWidgets.get(i);
        if (!names) {
          if (oldWidget) {
            this._valueWidgets.delete(i);
            this._textEditor.removeDecoration(oldWidget, i);
          }
          continue;
        }
  
        const widget = /** @type {!DecoratorWidget} */ (document.createElement('div'));
        widget.classList.add('text-editor-value-decoration');
        const base = this._textEditor.cursorPositionToCoordinates(i, 0);
        if (!base) {
          throw new Error('base is expected to not be null');
        }
        const offset = this._textEditor.cursorPositionToCoordinates(i, this._textEditor.line(i).length);
        if (!offset) {
          throw new Error('offset is expected to not be null');
        }
        const codeMirrorLinesLeftPadding = 4;
        const left = offset.x - base.x + codeMirrorLinesLeftPadding;
        widget.style.left = left + 'px';
        widget.__nameToToken = new Map();
  
        let renderedNameCount = 0;
        for (const name of names) {
          if (renderedNameCount > 10) {
            break;
          }
          const names = namesPerLine.get(i - 1);
          if (names && names.has(name)) {
            continue;
          }  // Only render name once in the given continuous block.
          if (renderedNameCount) {
            UI.UIUtils.createTextChild(widget, ', ');
          }
          const nameValuePair = /** @type {!HTMLElement} */ (widget.createChild('span'));
          widget.__nameToToken.set(name, nameValuePair);
          UI.UIUtils.createTextChild(nameValuePair, name + ' = ');
          const value = valuesMap.get(name);
          if (!value) {
            throw new Error('value is expected to be null');
          }
          const propertyCount = value.preview ? value.preview.properties.length : 0;
          const entryCount = value.preview && value.preview.entries ? value.preview.entries.length : 0;
          const diracAngel = Common.getDiracAngel();
          if (diracAngel.toggles.hasInlineCFs && value.customPreview()) {
            const customValueEl = (new ObjectUI.CustomPreviewComponent.CustomPreviewComponent(value)).element;
            nameValuePair.appendChild(customValueEl);
          } else if (value.preview && propertyCount + entryCount < 10) {
            formatter.appendObjectPreview(nameValuePair, value.preview, false /* isEntry */);
          } else {
            const propertyValue = ObjectUI.ObjectPropertiesSection.ObjectPropertiesSection.createPropertyValue(
                value, /* wasThrown */ false, /* showPreview */ false);
            nameValuePair.appendChild(propertyValue.element);
          }
          ++renderedNameCount;
        }
  
        let widgetChanged = true;
        if (oldWidget) {
          widgetChanged = false;
          for (const name of widget.__nameToToken.keys()) {
            const oldTextElement = oldWidget.__nameToToken.get(name);
            const newTextElement = widget.__nameToToken.get(name);
            const oldText = oldTextElement ? oldTextElement.textContent : '';
            const newText = newTextElement ? newTextElement.textContent : '';
            if (newText !== oldText) {
              widgetChanged = true;
              // value has changed, update it.
              UI.UIUtils.runCSSAnimationOnce(
                  /** @type {!Element} */ (widget.__nameToToken.get(name)), 'source-frame-value-update-highlight');
            }
          }
          if (widgetChanged) {
            this._valueWidgets.delete(i);
            this._textEditor.removeDecoration(oldWidget, i);
          }
        }
        if (widgetChanged) {
          this._valueWidgets.set(i, widget);
          this._textEditor.addDecoration(widget, i);
        }
      }
    }
  
    _clearExecutionLine() {
      this._textEditor.operation(() => {
        if (this._executionLocation) {
          this._textEditor.clearExecutionLine();
        }
        this._executionLocation = null;
        if (this._clearValueWidgetsTimer) {
          clearTimeout(this._clearValueWidgetsTimer);
          this._clearValueWidgetsTimer = null;
        }
        this._clearValueWidgetsTimer = window.setTimeout(this._clearValueWidgets.bind(this), 1000);
        this._clearContinueToLocationsNoRestore();
      });
    }
  
    _clearValueWidgets() {
      if (this._clearValueWidgetsTimer) {
        clearTimeout(this._clearValueWidgetsTimer);
      }
      this._clearValueWidgetsTimer = null;
      this._textEditor.operation(() => {
        for (const line of this._valueWidgets.keys()) {
          const valueWidget = this._valueWidgets.get(line);
          if (valueWidget) {
            this._textEditor.removeDecoration(valueWidget, line);
          }
        }
        this._valueWidgets.clear();
      });
    }
  
    _clearContinueToLocationsNoRestore() {
      const continueToLocationDecorations = this._continueToLocationDecorations;
      if (!continueToLocationDecorations) {
        return;
      }
      this._textEditor.operation(() => {
        for (const decoration of continueToLocationDecorations.keys()) {
          this._textEditor.removeHighlight(decoration);
        }
        this._continueToLocationDecorations = null;
        this._setAsyncStepInHoveredLine(null, false);
      });
    }
  
    _clearContinueToLocations() {
      if (!this._continueToLocationDecorations) {
        return;
      }
      this._textEditor.operation(() => {
        this._textEditor.showExecutionLineBackground();
        this._generateValuesInSource();
        this._clearContinueToLocationsNoRestore();
      });
    }
  
    /**
     * @param {number} lineNumber
     * @return {!Array<!BreakpointDecoration>}
     */
    _lineBreakpointDecorations(lineNumber) {
      return Array.from(this._breakpointDecorations)
          .filter(decoration => (decoration.handle.resolve() || {}).lineNumber === lineNumber);
    }
  
    /**
     * @param {number} editorLineNumber
     * @param {number} editorColumnNumber
     * @return {?BreakpointDecoration}
     */
    _breakpointDecoration(editorLineNumber, editorColumnNumber) {
      for (const decoration of this._breakpointDecorations) {
        const location = decoration.handle.resolve();
        if (!location) {
          continue;
        }
        if (location.lineNumber === editorLineNumber && location.columnNumber === editorColumnNumber) {
          return decoration;
        }
      }
      return null;
    }
  
    /**
     * @param {!BreakpointDecoration} decoration
     */
    _updateBreakpointDecoration(decoration) {
      if (!this._scheduledBreakpointDecorationUpdates) {
        /** @type {?Set<!BreakpointDecoration>} */
        this._scheduledBreakpointDecorationUpdates = new Set();
        setImmediate(() => this._textEditor.operation(update.bind(this)));
      }
      this._scheduledBreakpointDecorationUpdates.add(decoration);
  
      /**
       * @this {DebuggerPlugin}
       */
      function update() {
        if (!this._scheduledBreakpointDecorationUpdates) {
          return;
        }
        const editorLineNumbers = new Set();
        for (const decoration of this._scheduledBreakpointDecorationUpdates) {
          const location = decoration.handle.resolve();
          if (!location) {
            continue;
          }
          editorLineNumbers.add(location.lineNumber);
        }
        this._scheduledBreakpointDecorationUpdates = null;
        let waitingForInlineDecorations = false;
        for (const lineNumber of editorLineNumbers) {
          const decorations = this._lineBreakpointDecorations(lineNumber);
          updateGutter.call(this, lineNumber, decorations);
          if (this._possibleBreakpointsRequested.has(lineNumber)) {
            waitingForInlineDecorations = true;
            continue;
          }
          updateInlineDecorations.call(this, lineNumber, decorations);
        }
        if (!waitingForInlineDecorations) {
          this._breakpointDecorationsUpdatedForTest();
        }
      }
  
      /**
       * @param {number} editorLineNumber
       * @param {!Array<!BreakpointDecoration>} decorations
       * @this {DebuggerPlugin}
       */
      function updateGutter(editorLineNumber, decorations) {
        this._textEditor.toggleLineClass(editorLineNumber, 'cm-breakpoint', false);
        this._textEditor.toggleLineClass(editorLineNumber, 'cm-breakpoint-disabled', false);
        this._textEditor.toggleLineClass(editorLineNumber, 'cm-breakpoint-unbound', false);
        this._textEditor.toggleLineClass(editorLineNumber, 'cm-breakpoint-conditional', false);
        this._textEditor.toggleLineClass(editorLineNumber, 'cm-breakpoint-logpoint', false);
  
        if (decorations.length) {
          decorations.sort(BreakpointDecoration.mostSpecificFirst);
          const isDisabled = !decorations[0].enabled || this._muted;
          const isLogpoint = decorations[0].condition.includes(LogpointPrefix);
          const isUnbound = !decorations[0].bound;
          const isConditionalBreakpoint = !!decorations[0].condition && !isLogpoint;
  
          this._textEditor.toggleLineClass(editorLineNumber, 'cm-breakpoint', true);
          this._textEditor.toggleLineClass(editorLineNumber, 'cm-breakpoint-disabled', isDisabled);
          this._textEditor.toggleLineClass(editorLineNumber, 'cm-breakpoint-unbound', isUnbound && !isDisabled);
          this._textEditor.toggleLineClass(editorLineNumber, 'cm-breakpoint-logpoint', isLogpoint);
          this._textEditor.toggleLineClass(editorLineNumber, 'cm-breakpoint-conditional', isConditionalBreakpoint);
        }
      }
  
      /**
       * @param {number} editorLineNumber
       * @param {!Array<!BreakpointDecoration>} decorations
       * @this {DebuggerPlugin}
       */
      function updateInlineDecorations(editorLineNumber, decorations) {
        const actualBookmarks =
            new Set(decorations.map(decoration => decoration.bookmark).filter(bookmark => !!bookmark));
        const lineEnd = this._textEditor.line(editorLineNumber).length;
        const bookmarks = this._textEditor.bookmarks(
            new TextUtils.TextRange.TextRange(editorLineNumber, 0, editorLineNumber, lineEnd),
            BreakpointDecoration.bookmarkSymbol);
        for (const bookmark of bookmarks) {
          if (!actualBookmarks.has(bookmark)) {
            bookmark.clear();
          }
        }
        if (!decorations.length) {
          return;
        }
        if (decorations.length > 1) {
          for (const decoration of decorations) {
            decoration.update();
            if (!this._muted) {
              decoration.show();
            } else {
              decoration.hide();
            }
          }
        } else {
          decorations[0].update();
          decorations[0].hide();
        }
      }
    }
  
    _breakpointDecorationsUpdatedForTest() {
    }
  
    /**
     * @param {!BreakpointDecoration} decoration
     * @param {!MouseEvent} event
     */
    async _inlineBreakpointClick(decoration, event) {
      event.consume(true);
      if (decoration.breakpoint) {
        if (event.shiftKey) {
          decoration.breakpoint.setEnabled(!decoration.breakpoint.enabled());
        } else {
          decoration.breakpoint.remove(false);
        }
      } else {
        const editorLocation = decoration.handle.resolve();
        if (!editorLocation) {
          return;
        }
        const location =
            this._transformer.editorLocationToUILocation(editorLocation.lineNumber, editorLocation.columnNumber);
        await this._setBreakpoint(location.lineNumber, location.columnNumber, decoration.condition, true);
      }
    }
  
    /**
     * @param {!BreakpointDecoration} decoration
     * @param {!Event} event
     */
    _inlineBreakpointContextMenu(decoration, event) {
      event.consume(true);
      const editorLocation = decoration.handle.resolve();
      if (!editorLocation) {
        return;
      }
      if (this._textEditor.hasLineClass(editorLocation.lineNumber, 'cm-non-breakable-line')) {
        return;
      }
      if (!Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance()
               .scriptsForUISourceCode(this._uiSourceCode)
               .every(script => script.isJavaScript())) {
        // Editing breakpoints only make sense for conditional breakpoints
        // and logpoints and both are currently only available for JavaScript
        // debugging.
        return;
      }
      const location =
          this._transformer.editorLocationToUILocation(editorLocation.lineNumber, editorLocation.columnNumber);
      const contextMenu = new UI.ContextMenu.ContextMenu(event);
      if (decoration.breakpoint) {
        contextMenu.debugSection().appendItem(
            Common.UIString.UIString('Edit breakpoint…'),
            this._editBreakpointCondition.bind(
                this, editorLocation.lineNumber, decoration.breakpoint, null, false /* preferLogpoint */));
      } else {
        contextMenu.debugSection().appendItem(
            Common.UIString.UIString('Add conditional breakpoint…'),
            this._editBreakpointCondition.bind(
                this, editorLocation.lineNumber, null, editorLocation, false /* preferLogpoint */));
        contextMenu.debugSection().appendItem(
            ls`Add logpoint…`,
            this._editBreakpointCondition.bind(
                this, editorLocation.lineNumber, null, editorLocation, true /* preferLogpoint */));
        contextMenu.debugSection().appendItem(
            Common.UIString.UIString('Never pause here'),
            this._setBreakpoint.bind(this, location.lineNumber, location.columnNumber, 'false', true));
      }
      contextMenu.show();
    }
  
    /**
     * @param {!Common.EventTarget.EventTargetEvent} event
     * @return {boolean}
     */
    _shouldIgnoreExternalBreakpointEvents(event) {
      const uiLocation = /** @type {!Workspace.UISourceCode.UILocation} */ (event.data.uiLocation);
      if (uiLocation.uiSourceCode !== this._uiSourceCode) {
        return true;
      }
      if (this._muted) {
        return true;
      }
      for (const scriptFile of this._scriptFileForDebuggerModel.values()) {
        if (scriptFile.isDivergingFromVM() || scriptFile.isMergingToVM()) {
          return true;
        }
      }
      return false;
    }
  
    /**
     * @param {!Common.EventTarget.EventTargetEvent} event
     */
    _breakpointAdded(event) {
      if (this._shouldIgnoreExternalBreakpointEvents(event)) {
        return;
      }
      const uiLocation = /** @type {!Workspace.UISourceCode.UILocation} */ (event.data.uiLocation);
      const breakpoint = /** @type {!Bindings.BreakpointManager.Breakpoint} */ (event.data.breakpoint);
      this._addBreakpoint(uiLocation, breakpoint);
    }
  
    /**
     * @param {!Workspace.UISourceCode.UILocation} uiLocation
     * @param {!Bindings.BreakpointManager.Breakpoint} breakpoint
     */
    _addBreakpoint(uiLocation, breakpoint) {
      const editorLocation = this._transformer.uiLocationToEditorLocation(uiLocation.lineNumber, uiLocation.columnNumber);
      const lineDecorations = this._lineBreakpointDecorations(uiLocation.lineNumber);
      let decoration = this._breakpointDecoration(editorLocation.lineNumber, editorLocation.columnNumber);
      if (decoration) {
        decoration.breakpoint = breakpoint;
        decoration.condition = breakpoint.condition();
        decoration.enabled = breakpoint.enabled();
      } else {
        const handle = this._textEditor.textEditorPositionHandle(editorLocation.lineNumber, editorLocation.columnNumber);
        decoration = new BreakpointDecoration(
            this._textEditor, handle, breakpoint.condition(), breakpoint.enabled(),
            breakpoint.bound() || !breakpoint.hasBoundScript(), breakpoint);
        decoration.element.addEventListener('click', this._inlineBreakpointClick.bind(this, decoration), true);
        decoration.element.addEventListener(
            'contextmenu', this._inlineBreakpointContextMenu.bind(this, decoration), true);
        this._breakpointDecorations.add(decoration);
      }
      this._decorationByBreakpoint.set(breakpoint, decoration);
      this._updateBreakpointDecoration(decoration);
      if (breakpoint.enabled() && !lineDecorations.length) {
        this._possibleBreakpointsRequested.add(editorLocation.lineNumber);
        const start = this._transformer.editorLocationToUILocation(editorLocation.lineNumber, 0);
        const end = this._transformer.editorLocationToUILocation(editorLocation.lineNumber + 1, 0);
        this._breakpointManager
            .possibleBreakpoints(
                this._uiSourceCode,
                new TextUtils.TextRange.TextRange(start.lineNumber, start.columnNumber, end.lineNumber, end.columnNumber))
            .then(addInlineDecorations.bind(this, editorLocation.lineNumber));
      }
  
      /**
       * @this {DebuggerPlugin}
       * @param {number} editorLineNumber
       * @param {!Array<!Workspace.UISourceCode.UILocation>} possibleLocations
       */
      function addInlineDecorations(editorLineNumber, possibleLocations) {
        this._possibleBreakpointsRequested.delete(editorLineNumber);
        const decorations = this._lineBreakpointDecorations(editorLineNumber);
        for (const decoration of decorations) {
          this._updateBreakpointDecoration(decoration);
        }
        if (!decorations.some(decoration => !!decoration.breakpoint)) {
          return;
        }
        /** @type {!Set<number>} */
        const columns = new Set();
        for (const decoration of decorations) {
          const editorLocation = decoration.handle.resolve();
          if (!editorLocation) {
            continue;
          }
          columns.add(editorLocation.columnNumber);
        }
        // Only consider the first 100 inline breakpoints, as DevTools might appear to hang while CodeMirror is updating
        // the inline breakpoints. See crbug.com/1060105.
        for (const location of possibleLocations.slice(0, 100)) {
          const editorLocation = this._transformer.uiLocationToEditorLocation(location.lineNumber, location.columnNumber);
          if (editorLocation.lineNumber !== editorLineNumber) {
            continue;
          }
          if (columns.has(editorLocation.columnNumber)) {
            continue;
          }
          const handle =
              this._textEditor.textEditorPositionHandle(editorLocation.lineNumber, editorLocation.columnNumber);
          const decoration = new BreakpointDecoration(
              this._textEditor, handle, '', /** enabled */ false, /** bound */ false, /** breakpoint */ null);
          decoration.element.addEventListener('click', this._inlineBreakpointClick.bind(this, decoration), true);
          decoration.element.addEventListener(
              'contextmenu', this._inlineBreakpointContextMenu.bind(this, decoration), true);
          this._breakpointDecorations.add(decoration);
          this._updateBreakpointDecoration(decoration);
        }
      }
    }
  
    /**
     * @param {!Common.EventTarget.EventTargetEvent} event
     */
    _breakpointRemoved(event) {
      if (this._shouldIgnoreExternalBreakpointEvents(event)) {
        return;
      }
      const uiLocation = /** @type {!Workspace.UISourceCode.UILocation} */ (event.data.uiLocation);
      const breakpoint = /** @type {!Bindings.BreakpointManager.Breakpoint} */ (event.data.breakpoint);
      const decoration = this._decorationByBreakpoint.get(breakpoint);
      if (!decoration) {
        return;
      }
      this._decorationByBreakpoint.delete(breakpoint);
  
      const editorLocation = this._transformer.uiLocationToEditorLocation(uiLocation.lineNumber, uiLocation.columnNumber);
      decoration.breakpoint = null;
      decoration.enabled = false;
  
      const lineDecorations = this._lineBreakpointDecorations(editorLocation.lineNumber);
      if (!lineDecorations.some(decoration => !!decoration.breakpoint)) {
        for (const lineDecoration of lineDecorations) {
          this._breakpointDecorations.delete(lineDecoration);
          this._updateBreakpointDecoration(lineDecoration);
        }
      } else {
        this._updateBreakpointDecoration(decoration);
      }
    }
  
    _initializeBreakpoints() {
      const breakpointLocations = this._breakpointManager.breakpointLocationsForUISourceCode(this._uiSourceCode);
      for (const breakpointLocation of breakpointLocations) {
        this._addBreakpoint(breakpointLocation.uiLocation, breakpointLocation.breakpoint);
      }
    }
  
    _updateLinesWithoutMappingHighlight() {
      const isSourceMapSource =
          !!Bindings.CompilerScriptMapping.CompilerScriptMapping.uiSourceCodeOrigin(this._uiSourceCode);
      if (isSourceMapSource) {
        const linesCount = this._textEditor.linesCount;
        for (let i = 0; i < linesCount; ++i) {
          const lineHasMapping =
              Bindings.CompilerScriptMapping.CompilerScriptMapping.uiLineHasMapping(this._uiSourceCode, i);
          if (!lineHasMapping) {
            this._hasLineWithoutMapping = true;
          }
          if (this._hasLineWithoutMapping) {
            this._textEditor.toggleLineClass(i, 'cm-non-breakable-line', !lineHasMapping);
          }
        }
        return;
      }
    }
  
    _updateScriptFiles() {
      for (const debuggerModel of SDK.SDKModel.TargetManager.instance().models(SDK.DebuggerModel.DebuggerModel)) {
        const scriptFile = Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance().scriptFile(
            this._uiSourceCode, debuggerModel);
        if (scriptFile) {
          this._updateScriptFile(debuggerModel);
        }
      }
    }
  
    /**
     * @param {!SDK.DebuggerModel.DebuggerModel} debuggerModel
     */
    _updateScriptFile(debuggerModel) {
      const oldScriptFile = this._scriptFileForDebuggerModel.get(debuggerModel);
      const newScriptFile = Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance().scriptFile(
          this._uiSourceCode, debuggerModel);
      this._scriptFileForDebuggerModel.delete(debuggerModel);
      if (oldScriptFile) {
        oldScriptFile.removeEventListener(
            Bindings.ResourceScriptMapping.ResourceScriptFile.Events.DidMergeToVM, this._didMergeToVM, this);
        oldScriptFile.removeEventListener(
            Bindings.ResourceScriptMapping.ResourceScriptFile.Events.DidDivergeFromVM, this._didDivergeFromVM, this);
        if (this._muted && !this._uiSourceCode.isDirty()) {
          this._restoreBreakpointsIfConsistentScripts();
        }
      }
      if (!newScriptFile) {
        return;
      }
      this._scriptFileForDebuggerModel.set(debuggerModel, newScriptFile);
      newScriptFile.addEventListener(
          Bindings.ResourceScriptMapping.ResourceScriptFile.Events.DidMergeToVM, this._didMergeToVM, this);
      newScriptFile.addEventListener(
          Bindings.ResourceScriptMapping.ResourceScriptFile.Events.DidDivergeFromVM, this._didDivergeFromVM, this);
      newScriptFile.checkMapping();
      if (newScriptFile.hasSourceMapURL()) {
        this._showSourceMapInfobar();
      }
    }
  
    _showSourceMapInfobar() {
      if (this._sourceMapInfobar) {
        return;
      }
      this._sourceMapInfobar = UI.Infobar.Infobar.create(
          UI.Infobar.Type.Info, Common.UIString.UIString('Source Map detected.'), [],
          Common.Settings.Settings.instance().createSetting('sourceMapInfobarDisabled', false));
      if (!this._sourceMapInfobar) {
        return;
      }
      this._sourceMapInfobar.createDetailsRowMessage(Common.UIString.UIString(
          'Associated files should be added to the file tree. You can debug these resolved source files as regular JavaScript files.'));
      this._sourceMapInfobar.createDetailsRowMessage(Common.UIString.UIString(
          'Associated files are available via file tree or %s.',
          UI.ShortcutRegistry.ShortcutRegistry.instance().shortcutTitleForAction('quickOpen.show')));
      this._sourceMapInfobar.setCloseCallback(() => {
        this._sourceMapInfobar = null;
      });
      this._textEditor.attachInfobar(this._sourceMapInfobar);
    }
  
    async _detectMinified() {
      const content = this._uiSourceCode.content();
      if (!content || !TextUtils.TextUtils.isMinified(content)) {
        return;
      }
  
      const editorActions = await Root.Runtime.Runtime.instance().allInstances(EditorAction);
      /** @type {(function(): void) | null} */
      let formatterCallback = null;
      for (const editorAction of editorActions) {
        if (editorAction instanceof ScriptFormatterEditorAction) {
          // Check if the source code is formattable the same way the pretty print button does
          if (!editorAction.isCurrentUISourceCodeFormatable()) {
            return;
          }
          formatterCallback = () => editorAction.toggleFormatScriptSource.bind(editorAction);
          break;
        }
      }
  
      this._prettyPrintInfobar = UI.Infobar.Infobar.create(
          UI.Infobar.Type.Info, Common.UIString.UIString('Pretty-print this minified file?'),
          [{text: ls`Pretty-print`, delegate: formatterCallback, highlight: true, dismiss: true}],
          Common.Settings.Settings.instance().createSetting('prettyPrintInfobarDisabled', false));
      if (!this._prettyPrintInfobar) {
        return;
      }
  
      this._prettyPrintInfobar.setCloseCallback(() => {
        this._prettyPrintInfobar = null;
      });
      const toolbar = new UI.Toolbar.Toolbar('');
      const button = new UI.Toolbar.ToolbarButton('', 'largeicon-pretty-print');
      toolbar.appendToolbarItem(button);
      toolbar.element.style.display = 'inline-block';
      toolbar.element.style.verticalAlign = 'middle';
      toolbar.element.style.marginBottom = '3px';
      toolbar.element.style.pointerEvents = 'none';
      toolbar.element.tabIndex = -1;
      const element = this._prettyPrintInfobar.createDetailsRowMessage();
      element.appendChild(UI.UIUtils.formatLocalized(
          'Pretty-printing will format this file in a new tab where you can continue debugging. You can also pretty-print this file by clicking the %s button on the bottom status bar.',
          [toolbar.element]));
      this._textEditor.attachInfobar(this._prettyPrintInfobar);
    }
  
    /**
     * @param {!Common.EventTarget.EventTargetEvent} event
     */
    async _handleGutterClick(event) {
      if (this._muted) {
        return;
      }
  
      const eventData = /** @type {!SourceFrame.SourcesTextEditor.GutterClickEventData} */ (event.data);
      if (eventData.gutterType !== SourceFrame.SourcesTextEditor.lineNumbersGutterType) {
        return;
      }
      const editorLineNumber = eventData.lineNumber;
      const eventObject = eventData.event;
  
      if (eventObject.button !== 0 || eventObject.altKey || eventObject.ctrlKey || eventObject.metaKey) {
        return;
      }
  
      await this._toggleBreakpoint(editorLineNumber, eventObject.shiftKey);
      eventObject.consume(true);
    }
  
    /**
     * @param {number} editorLineNumber
     * @param {boolean} onlyDisable
     */
    async _toggleBreakpoint(editorLineNumber, onlyDisable) {
      const decorations = this._lineBreakpointDecorations(editorLineNumber);
      if (!decorations.length) {
        await this._createNewBreakpoint(editorLineNumber, '', true);
        return;
      }
      const hasDisabled = this._textEditor.hasLineClass(editorLineNumber, 'cm-breakpoint-disabled');
      const breakpoints = /** @type {!Array<!Bindings.BreakpointManager.Breakpoint>} */ (
          decorations.map(decoration => decoration.breakpoint).filter(breakpoint => !!breakpoint));
      for (const breakpoint of breakpoints) {
        if (onlyDisable) {
          breakpoint.setEnabled(hasDisabled);
        } else {
          breakpoint.remove(false);
        }
      }
    }
  
    /**
     * @param {number} editorLineNumber
     * @param {string} condition
     * @param {boolean} enabled
     */
    async _createNewBreakpoint(editorLineNumber, condition, enabled) {
      if (this._textEditor.hasLineClass(editorLineNumber, 'cm-non-breakable-line')) {
        return;
      }
      Host.userMetrics.actionTaken(Host.UserMetrics.Action.ScriptsBreakpointSet);
      const origin = this._transformer.editorLocationToUILocation(editorLineNumber, 0);
      await this._setBreakpoint(origin.lineNumber, origin.columnNumber, condition, enabled);
    }
  
    /**
     * @param {number} lineNumber
     * @param {number} columnNumber
     * @param {string} condition
     * @param {boolean} enabled
     */
    async _setBreakpoint(lineNumber, columnNumber, condition, enabled) {
      Common.Settings.Settings.instance().moduleSetting('breakpointsActive').set(true);
      await this._breakpointManager.setBreakpoint(this._uiSourceCode, lineNumber, columnNumber, condition, enabled);
      this._breakpointWasSetForTest(lineNumber, columnNumber, condition, enabled);
    }
  
    /**
     * @param {number} lineNumber
     * @param {number} columnNumber
     * @param {string} condition
     * @param {boolean} enabled
     */
    _breakpointWasSetForTest(lineNumber, columnNumber, condition, enabled) {
    }
  
  
    async _callFrameChanged() {
      this._liveLocationPool.disposeAll();
      const callFrame = UI.Context.Context.instance().flavor(SDK.DebuggerModel.CallFrame);
      if (!callFrame) {
        this._clearExecutionLine();
        return;
      }
      await Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance().createCallFrameLiveLocation(
          callFrame.location(), this._executionLineChanged.bind(this), this._liveLocationPool);
    }
  
    /**
     * @override
     */
    dispose() {
      for (const decoration of this._breakpointDecorations) {
        decoration.dispose();
      }
      this._breakpointDecorations.clear();
      if (this._scheduledBreakpointDecorationUpdates) {
        for (const decoration of this._scheduledBreakpointDecorationUpdates) {
          decoration.dispose();
        }
        this._scheduledBreakpointDecorationUpdates.clear();
      }
  
      this._hideBlackboxInfobar();
      if (this._sourceMapInfobar) {
        this._sourceMapInfobar.dispose();
      }
      if (this._prettyPrintInfobar) {
        this._prettyPrintInfobar.dispose();
      }
      this._scriptsPanel.element.removeEventListener('scroll', this._boundPopoverHelperHide, true);
      for (const script of this._scriptFileForDebuggerModel.values()) {
        script.removeEventListener(
            Bindings.ResourceScriptMapping.ResourceScriptFile.Events.DidMergeToVM, this._didMergeToVM, this);
        script.removeEventListener(
            Bindings.ResourceScriptMapping.ResourceScriptFile.Events.DidDivergeFromVM, this._didDivergeFromVM, this);
      }
      this._scriptFileForDebuggerModel.clear();
  
  
      this._textEditor.element.removeEventListener('keydown', this._boundKeyDown, true);
      this._textEditor.element.removeEventListener('keyup', this._boundKeyUp, true);
      this._textEditor.element.removeEventListener('mousemove', this._boundMouseMove, false);
      this._textEditor.element.removeEventListener('mousedown', this._boundMouseDown, true);
      this._textEditor.element.removeEventListener('focusout', this._boundBlur, false);
      this._textEditor.element.removeEventListener('wheel', this._boundWheel, true);
  
      this._textEditor.removeEventListener(
          SourceFrame.SourcesTextEditor.Events.GutterClick, this._boundGutterClick, this);
      this._popoverHelper.hidePopover();
      this._popoverHelper.dispose();
  
      this._breakpointManager.removeEventListener(
          Bindings.BreakpointManager.Events.BreakpointAdded, this._breakpointAdded, this);
      this._breakpointManager.removeEventListener(
          Bindings.BreakpointManager.Events.BreakpointRemoved, this._breakpointRemoved, this);
      this._uiSourceCode.removeEventListener(
          Workspace.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this);
      this._uiSourceCode.removeEventListener(
          Workspace.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this);
  
      Common.Settings.Settings.instance()
          .moduleSetting('skipStackFramesPattern')
          .removeChangeListener(this._showBlackboxInfobarIfNeeded, this);
      Common.Settings.Settings.instance()
          .moduleSetting('skipContentScripts')
          .removeChangeListener(this._showBlackboxInfobarIfNeeded, this);
      super.dispose();
  
      this._clearExecutionLine();
      UI.Context.Context.instance().removeFlavorChangeListener(SDK.DebuggerModel.CallFrame, this._callFrameChanged, this);
      this._liveLocationPool.disposeAll();
    }
  }
  
  /**
   * @unrestricted
   */
  export class BreakpointDecoration {
    /**
     * @param {!SourceFrame.SourcesTextEditor.SourcesTextEditor} textEditor
     * @param {!TextEditor.CodeMirrorTextEditor.TextEditorPositionHandle} handle
     * @param {string} condition
     * @param {boolean} enabled
     * @param {boolean} bound
     * @param {?Bindings.BreakpointManager.Breakpoint} breakpoint
     */
    constructor(textEditor, handle, condition, enabled, bound, breakpoint) {
      this._textEditor = textEditor;
      this.handle = handle;
      this.condition = condition;
      this.enabled = enabled;
      this.bound = bound;
      this.breakpoint = breakpoint;
      this.element = document.createElement('span');
      this.element.classList.toggle('cm-inline-breakpoint', true);
  
      /** @type {?TextEditor.CodeMirrorTextEditor.TextEditorBookMark} */
      this.bookmark = null;
    }
  
    /**
     * @param {!BreakpointDecoration} decoration1
     * @param {!BreakpointDecoration} decoration2
     * @return {number}
     */
    static mostSpecificFirst(decoration1, decoration2) {
      if (decoration1.enabled !== decoration2.enabled) {
        return decoration1.enabled ? -1 : 1;
      }
      if (decoration1.bound !== decoration2.bound) {
        return decoration1.bound ? -1 : 1;
      }
      if (!!decoration1.condition !== !!decoration2.condition) {
        return !!decoration1.condition ? -1 : 1;
      }
      return 0;
    }
  
    update() {
      const isLogpoint = !!this.condition && this.condition.includes(LogpointPrefix);
      const isConditionalBreakpoint = !!this.condition && !isLogpoint;
      this.element.classList.toggle('cm-inline-logpoint', isLogpoint);
      this.element.classList.toggle('cm-inline-breakpoint-conditional', isConditionalBreakpoint);
      this.element.classList.toggle('cm-inline-disabled', !this.enabled);
    }
  
    show() {
      if (this.bookmark) {
        return;
      }
      const editorLocation = this.handle.resolve();
      if (!editorLocation) {
        return;
      }
      this.bookmark = this._textEditor.addBookmark(
          editorLocation.lineNumber, editorLocation.columnNumber, this.element, BreakpointDecoration.bookmarkSymbol);
      // @ts-ignore Only used for layout tests
      this.bookmark[BreakpointDecoration._elementSymbolForTest] = this.element;
    }
  
    hide() {
      if (!this.bookmark) {
        return;
      }
      this.bookmark.clear();
      this.bookmark = null;
    }
  
    dispose() {
      const location = this.handle.resolve();
      if (location) {
        this._textEditor.toggleLineClass(location.lineNumber, 'cm-breakpoint', false);
        this._textEditor.toggleLineClass(location.lineNumber, 'cm-breakpoint-disabled', false);
        this._textEditor.toggleLineClass(location.lineNumber, 'cm-breakpoint-unbound', false);
        this._textEditor.toggleLineClass(location.lineNumber, 'cm-breakpoint-conditional', false);
        this._textEditor.toggleLineClass(location.lineNumber, 'cm-breakpoint-logpoint', false);
      }
      this.hide();
    }
  }
  
  BreakpointDecoration.bookmarkSymbol = Symbol('bookmark');
  BreakpointDecoration._elementSymbolForTest = Symbol('element');
  
  export const continueToLocationDecorationSymbol = Symbol('bookmark');
  