text: /*
   * Copyright (C) 2013 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.
   */
  /**
   * @implements {SDK.OverlayModel.Highlighter}
   * @unrestricted
   */
  Screencast.ScreencastView = class extends UI.VBox {
    /**
     * @param {!SDK.ScreenCaptureModel} screenCaptureModel
     */
    constructor(screenCaptureModel) {
      super();
      this._screenCaptureModel = screenCaptureModel;
      this._domModel = screenCaptureModel.target().model(SDK.DOMModel);
      this._overlayModel = screenCaptureModel.target().model(SDK.OverlayModel);
      this._resourceTreeModel = screenCaptureModel.target().model(SDK.ResourceTreeModel);
      this._networkManager = screenCaptureModel.target().model(SDK.NetworkManager);
      this._inputModel = screenCaptureModel.target().model(Screencast.InputModel);
  
      this.setMinimumSize(150, 150);
      this.registerRequiredCSS('screencast/screencastView.css');
    }
  
    initialize() {
      this.element.classList.add('screencast');
  
      this._createNavigationBar();
  
      this._viewportElement = this.element.createChild('div', 'screencast-viewport hidden');
      this._canvasContainerElement = this._viewportElement.createChild('div', 'screencast-canvas-container');
      this._glassPaneElement = this._canvasContainerElement.createChild('div', 'screencast-glasspane fill hidden');
  
      this._canvasElement = this._canvasContainerElement.createChild('canvas');
      this._canvasElement.tabIndex = 1;
      this._canvasElement.addEventListener('mousedown', this._handleMouseEvent.bind(this), false);
      this._canvasElement.addEventListener('mouseup', this._handleMouseEvent.bind(this), false);
      this._canvasElement.addEventListener('mousemove', this._handleMouseEvent.bind(this), false);
      this._canvasElement.addEventListener('mousewheel', this._handleMouseEvent.bind(this), false);
      this._canvasElement.addEventListener('click', this._handleMouseEvent.bind(this), false);
      this._canvasElement.addEventListener('contextmenu', this._handleContextMenuEvent.bind(this), false);
      this._canvasElement.addEventListener('keydown', this._handleKeyEvent.bind(this), false);
      this._canvasElement.addEventListener('keyup', this._handleKeyEvent.bind(this), false);
      this._canvasElement.addEventListener('keypress', this._handleKeyEvent.bind(this), false);
      this._canvasElement.addEventListener('blur', this._handleBlurEvent.bind(this), false);
  
      this._titleElement = this._canvasContainerElement.createChild('div', 'screencast-element-title monospace hidden');
      this._tagNameElement = this._titleElement.createChild('span', 'screencast-tag-name');
      this._nodeIdElement = this._titleElement.createChild('span', 'screencast-node-id');
      this._classNameElement = this._titleElement.createChild('span', 'screencast-class-name');
      this._titleElement.createTextChild(' ');
      this._nodeWidthElement = this._titleElement.createChild('span');
      this._titleElement.createChild('span', 'screencast-px').textContent = 'px';
      this._titleElement.createTextChild(' \u00D7 ');
      this._nodeHeightElement = this._titleElement.createChild('span');
      this._titleElement.createChild('span', 'screencast-px').textContent = 'px';
      this._titleElement.style.top = '0';
      this._titleElement.style.left = '0';
  
      this._imageElement = new Image();
      this._isCasting = false;
      this._context = this._canvasElement.getContext('2d');
      this._checkerboardPattern = this._createCheckerboardPattern(this._context);
  
      this._shortcuts = /** !Object.<number, function(Event=):boolean> */ ({});
      this._shortcuts[UI.KeyboardShortcut.makeKey('l', UI.KeyboardShortcut.Modifiers.Ctrl)] =
          this._focusNavigationBar.bind(this);
  
      SDK.targetManager.addEventListener(SDK.TargetManager.Events.SuspendStateChanged, this._onSuspendStateChange, this);
      this._updateGlasspane();
    }
  
    /**
     * @override
     */
    wasShown() {
      this._startCasting();
    }
  
    /**
     * @override
     */
    willHide() {
      this._stopCasting();
    }
  
    _startCasting() {
      if (SDK.targetManager.allTargetsSuspended())
        return;
      if (this._isCasting)
        return;
      this._isCasting = true;
  
      const maxImageDimension = 2048;
      var dimensions = this._viewportDimensions();
      if (dimensions.width < 0 || dimensions.height < 0) {
        this._isCasting = false;
        return;
      }
      dimensions.width *= window.devicePixelRatio;
      dimensions.height *= window.devicePixelRatio;
      // Note: startScreencast width and height are expected to be integers so must be floored.
      this._screenCaptureModel.startScreencast(
          'jpeg', 80, Math.floor(Math.min(maxImageDimension, dimensions.width)),
          Math.floor(Math.min(maxImageDimension, dimensions.height)), undefined, this._screencastFrame.bind(this),
          this._screencastVisibilityChanged.bind(this));
      for (var emulationModel of SDK.targetManager.models(SDK.EmulationModel))
        emulationModel.overrideEmulateTouch(true);
      if (this._overlayModel)
        this._overlayModel.setHighlighter(this);
    }
  
    _stopCasting() {
      if (!this._isCasting)
        return;
      this._isCasting = false;
      this._screenCaptureModel.stopScreencast();
      for (var emulationModel of SDK.targetManager.models(SDK.EmulationModel))
        emulationModel.overrideEmulateTouch(false);
      if (this._overlayModel)
        this._overlayModel.setHighlighter(null);
    }
  
    /**
     * @param {string} base64Data
     * @param {!Protocol.Page.ScreencastFrameMetadata} metadata
     */
    _screencastFrame(base64Data, metadata) {
      this._imageElement.onload = () => {
        this._pageScaleFactor = metadata.pageScaleFactor;
        this._screenOffsetTop = metadata.offsetTop;
        this._scrollOffsetX = metadata.scrollOffsetX;
        this._scrollOffsetY = metadata.scrollOffsetY;
  
        var deviceSizeRatio = metadata.deviceHeight / metadata.deviceWidth;
        var dimensionsCSS = this._viewportDimensions();
  
        this._imageZoom = Math.min(
            dimensionsCSS.width / this._imageElement.naturalWidth,
            dimensionsCSS.height / (this._imageElement.naturalWidth * deviceSizeRatio));
        this._viewportElement.classList.remove('hidden');
        var bordersSize = Screencast.ScreencastView._bordersSize;
        if (this._imageZoom < 1.01 / window.devicePixelRatio)
          this._imageZoom = 1 / window.devicePixelRatio;
        this._screenZoom = this._imageElement.naturalWidth * this._imageZoom / metadata.deviceWidth;
        this._viewportElement.style.width = metadata.deviceWidth * this._screenZoom + bordersSize + 'px';
        this._viewportElement.style.height = metadata.deviceHeight * this._screenZoom + bordersSize + 'px';
  
        this.highlightDOMNode(this._highlightNode, this._highlightConfig);
      };
      this._imageElement.src = 'data:image/jpg;base64,' + base64Data;
    }
  
    _isGlassPaneActive() {
      return !this._glassPaneElement.classList.contains('hidden');
    }
  
    /**
     * @param {boolean} visible
     */
    _screencastVisibilityChanged(visible) {
      this._targetInactive = !visible;
      this._updateGlasspane();
    }
  
    /**
     * @param {!Common.Event} event
     */
    _onSuspendStateChange(event) {
      if (SDK.targetManager.allTargetsSuspended())
        this._stopCasting();
      else
        this._startCasting();
      this._updateGlasspane();
    }
  
    _updateGlasspane() {
      if (this._targetInactive) {
        this._glassPaneElement.textContent = Common.UIString('The tab is inactive');
        this._glassPaneElement.classList.remove('hidden');
      } else if (SDK.targetManager.allTargetsSuspended()) {
        this._glassPaneElement.textContent = Common.UIString('Profiling in progress');
        this._glassPaneElement.classList.remove('hidden');
      } else {
        this._glassPaneElement.classList.add('hidden');
      }
    }
  
    /**
     * @param {!Event} event
     */
    async _handleMouseEvent(event) {
      if (this._isGlassPaneActive()) {
        event.consume();
        return;
      }
  
      if (!this._pageScaleFactor || !this._domModel)
        return;
  
      if (!this._inspectModeConfig || event.type === 'mousewheel') {
        if (this._inputModel)
          this._inputModel.emitTouchFromMouseEvent(event, this._screenOffsetTop, this._screenZoom);
        event.preventDefault();
        if (event.type === 'mousedown')
          this._canvasElement.focus();
        return;
      }
  
      var position = this._convertIntoScreenSpace(event);
  
      var node = await this._domModel.nodeForLocation(
          Math.floor(position.x / this._pageScaleFactor + this._scrollOffsetX),
          Math.floor(position.y / this._pageScaleFactor + this._scrollOffsetY),
          Common.moduleSetting('showUAShadowDOM').get());
  
      if (!node)
        return;
      if (event.type === 'mousemove') {
        this.highlightDOMNode(node, this._inspectModeConfig);
        this._domModel.overlayModel().nodeHighlightRequested(node.id);
      } else if (event.type === 'click') {
        Common.Revealer.reveal(node);
      }
    }
  
    /**
     * @param {!Event} event
     */
    _handleKeyEvent(event) {
      if (this._isGlassPaneActive()) {
        event.consume();
        return;
      }
  
      var shortcutKey = UI.KeyboardShortcut.makeKeyFromEvent(/** @type {!KeyboardEvent} */ (event));
      var handler = this._shortcuts[shortcutKey];
      if (handler && handler(event)) {
        event.consume();
        return;
      }
  
      if (this._inputModel)
        this._inputModel.emitKeyEvent(event);
      event.consume();
      this._canvasElement.focus();
    }
  
    /**
     * @param {!Event} event
     */
    _handleContextMenuEvent(event) {
      event.consume(true);
    }
  
    /**
     * @param {!Event} event
     */
    _handleBlurEvent(event) {
      if (this._inputModel)
        this._inputModel.cancelTouch();
    }
  
    /**
     * @param {!Event} event
     * @return {!{x: number, y: number}}
     */
    _convertIntoScreenSpace(event) {
      var position = {};
      position.x = Math.round(event.offsetX / this._screenZoom);
      position.y = Math.round(event.offsetY / this._screenZoom - this._screenOffsetTop);
      return position;
    }
  
    /**
     * @override
     */
    onResize() {
      if (this._deferredCasting) {
        clearTimeout(this._deferredCasting);
        delete this._deferredCasting;
      }
  
      this._stopCasting();
      this._deferredCasting = setTimeout(this._startCasting.bind(this), 100);
    }
  
    /**
     * @override
     * @param {?SDK.DOMNode} node
     * @param {?Protocol.Overlay.HighlightConfig} config
     * @param {!Protocol.DOM.BackendNodeId=} backendNodeId
     * @param {!Protocol.Runtime.RemoteObjectId=} objectId
     */
    highlightDOMNode(node, config, backendNodeId, objectId) {
      this._highlightNode = node;
      this._highlightConfig = config;
      if (!node) {
        this._model = null;
        this._config = null;
        this._node = null;
        this._titleElement.classList.add('hidden');
        this._repaint();
        return;
      }
  
      this._node = node;
      node.boxModel().then(model => {
        if (!model || !this._pageScaleFactor) {
          this._repaint();
          return;
        }
        this._model = this._scaleModel(model);
        this._config = config;
        this._repaint();
      });
    }
  
    /**
     * @param {!Protocol.DOM.BoxModel} model
     * @return {!Protocol.DOM.BoxModel}
     */
    _scaleModel(model) {
      /**
       * @param {!Protocol.DOM.Quad} quad
       * @this {Screencast.ScreencastView}
       */
      function scaleQuad(quad) {
        for (var i = 0; i < quad.length; i += 2) {
          quad[i] = quad[i] * this._pageScaleFactor * this._screenZoom;
          quad[i + 1] = (quad[i + 1] * this._pageScaleFactor + this._screenOffsetTop) * this._screenZoom;
        }
      }
  
      scaleQuad.call(this, model.content);
      scaleQuad.call(this, model.padding);
      scaleQuad.call(this, model.border);
      scaleQuad.call(this, model.margin);
      return model;
    }
  
    _repaint() {
      var model = this._model;
      var config = this._config;
  
      var canvasWidth = this._canvasElement.getBoundingClientRect().width;
      var canvasHeight = this._canvasElement.getBoundingClientRect().height;
      this._canvasElement.width = window.devicePixelRatio * canvasWidth;
      this._canvasElement.height = window.devicePixelRatio * canvasHeight;
  
      this._context.save();
      this._context.scale(window.devicePixelRatio, window.devicePixelRatio);
  
      // Paint top and bottom gutter.
      this._context.save();
      this._context.fillStyle = this._checkerboardPattern;
      this._context.fillRect(0, 0, canvasWidth, this._screenOffsetTop * this._screenZoom);
      this._context.fillRect(
          0, this._screenOffsetTop * this._screenZoom + this._imageElement.naturalHeight * this._imageZoom, canvasWidth,
          canvasHeight);
      this._context.restore();
  
      if (model && config) {
        this._context.save();
        const transparentColor = 'rgba(0, 0, 0, 0)';
        var quads = [];
        if (model.content && config.contentColor !== transparentColor)
          quads.push({quad: model.content, color: config.contentColor});
        if (model.padding && config.paddingColor !== transparentColor)
          quads.push({quad: model.padding, color: config.paddingColor});
        if (model.border && config.borderColor !== transparentColor)
          quads.push({quad: model.border, color: config.borderColor});
        if (model.margin && config.marginColor !== transparentColor)
          quads.push({quad: model.margin, color: config.marginColor});
  
        for (var i = quads.length - 1; i > 0; --i)
          this._drawOutlinedQuadWithClip(quads[i].quad, quads[i - 1].quad, quads[i].color);
        if (quads.length > 0)
          this._drawOutlinedQuad(quads[0].quad, quads[0].color);
        this._context.restore();
  
        this._drawElementTitle();
  
        this._context.globalCompositeOperation = 'destination-over';
      }
  
      this._context.drawImage(
          this._imageElement, 0, this._screenOffsetTop * this._screenZoom,
          this._imageElement.naturalWidth * this._imageZoom, this._imageElement.naturalHeight * this._imageZoom);
      this._context.restore();
    }
  
    /**
     * @param {!Protocol.DOM.RGBA} color
     * @return {string}
     */
    _cssColor(color) {
      if (!color)
        return 'transparent';
      return Common.Color.fromRGBA([color.r, color.g, color.b, color.a]).asString(Common.Color.Format.RGBA) || '';
    }
  
    /**
     * @param {!Protocol.DOM.Quad} quad
     * @return {!CanvasRenderingContext2D}
     */
    _quadToPath(quad) {
      this._context.beginPath();
      this._context.moveTo(quad[0], quad[1]);
      this._context.lineTo(quad[2], quad[3]);
      this._context.lineTo(quad[4], quad[5]);
      this._context.lineTo(quad[6], quad[7]);
      this._context.closePath();
      return this._context;
    }
  
    /**
     * @param {!Protocol.DOM.Quad} quad
     * @param {!Protocol.DOM.RGBA} fillColor
     */
    _drawOutlinedQuad(quad, fillColor) {
      this._context.save();
      this._context.lineWidth = 2;
      this._quadToPath(quad).clip();
      this._context.fillStyle = this._cssColor(fillColor);
      this._context.fill();
      this._context.restore();
    }
  
    /**
     * @param {!Protocol.DOM.Quad} quad
     * @param {!Protocol.DOM.Quad} clipQuad
     * @param {!Protocol.DOM.RGBA} fillColor
     */
    _drawOutlinedQuadWithClip(quad, clipQuad, fillColor) {
      this._context.fillStyle = this._cssColor(fillColor);
      this._context.save();
      this._context.lineWidth = 0;
      this._quadToPath(quad).fill();
      this._context.globalCompositeOperation = 'destination-out';
      this._context.fillStyle = 'red';
      this._quadToPath(clipQuad).fill();
      this._context.restore();
    }
  
    _drawElementTitle() {
      if (!this._node)
        return;
  
      var canvasWidth = this._canvasElement.getBoundingClientRect().width;
      var canvasHeight = this._canvasElement.getBoundingClientRect().height;
  
      var lowerCaseName = this._node.localName() || this._node.nodeName().toLowerCase();
      this._tagNameElement.textContent = lowerCaseName;
      this._nodeIdElement.textContent = this._node.getAttribute('id') ? '#' + this._node.getAttribute('id') : '';
      this._nodeIdElement.textContent = this._node.getAttribute('id') ? '#' + this._node.getAttribute('id') : '';
      var className = this._node.getAttribute('class');
      if (className && className.length > 50)
        className = className.substring(0, 50) + '\u2026';
      this._classNameElement.textContent = className || '';
      this._nodeWidthElement.textContent = this._model.width;
      this._nodeHeightElement.textContent = this._model.height;
  
      this._titleElement.classList.remove('hidden');
      var titleWidth = this._titleElement.offsetWidth + 6;
      var titleHeight = this._titleElement.offsetHeight + 4;
  
      var anchorTop = this._model.margin[1];
      var anchorBottom = this._model.margin[7];
  
      const arrowHeight = 7;
      var renderArrowUp = false;
      var renderArrowDown = false;
  
      var boxX = Math.max(2, this._model.margin[0]);
      if (boxX + titleWidth > canvasWidth)
        boxX = canvasWidth - titleWidth - 2;
  
      var boxY;
      if (anchorTop > canvasHeight) {
        boxY = canvasHeight - titleHeight - arrowHeight;
        renderArrowDown = true;
      } else if (anchorBottom < 0) {
        boxY = arrowHeight;
        renderArrowUp = true;
      } else if (anchorBottom + titleHeight + arrowHeight < canvasHeight) {
        boxY = anchorBottom + arrowHeight - 4;
        renderArrowUp = true;
      } else if (anchorTop - titleHeight - arrowHeight > 0) {
        boxY = anchorTop - titleHeight - arrowHeight + 3;
        renderArrowDown = true;
      } else {
        boxY = arrowHeight;
      }
  
      this._context.save();
      this._context.translate(0.5, 0.5);
      this._context.beginPath();
      this._context.moveTo(boxX, boxY);
      if (renderArrowUp) {
        this._context.lineTo(boxX + 2 * arrowHeight, boxY);
        this._context.lineTo(boxX + 3 * arrowHeight, boxY - arrowHeight);
        this._context.lineTo(boxX + 4 * arrowHeight, boxY);
      }
      this._context.lineTo(boxX + titleWidth, boxY);
      this._context.lineTo(boxX + titleWidth, boxY + titleHeight);
      if (renderArrowDown) {
        this._context.lineTo(boxX + 4 * arrowHeight, boxY + titleHeight);
        this._context.lineTo(boxX + 3 * arrowHeight, boxY + titleHeight + arrowHeight);
        this._context.lineTo(boxX + 2 * arrowHeight, boxY + titleHeight);
      }
      this._context.lineTo(boxX, boxY + titleHeight);
      this._context.closePath();
      this._context.fillStyle = 'rgb(255, 255, 194)';
      this._context.fill();
      this._context.strokeStyle = 'rgb(128, 128, 128)';
      this._context.stroke();
  
      this._context.restore();
  
      this._titleElement.style.top = (boxY + 3) + 'px';
      this._titleElement.style.left = (boxX + 3) + 'px';
    }
  
    /**
     * @return {!{width: number, height: number}}
     */
    _viewportDimensions() {
      const gutterSize = 30;
      const bordersSize = Screencast.ScreencastView._bordersSize;
      var width = this.element.offsetWidth - bordersSize - gutterSize;
      var height = this.element.offsetHeight - bordersSize - gutterSize - Screencast.ScreencastView._navBarHeight;
      return {width: width, height: height};
    }
  
    /**
     * @override
     * @param {!Protocol.Overlay.InspectMode} mode
     * @param {!Protocol.Overlay.HighlightConfig} config
     * @return {!Promise}
     */
    setInspectMode(mode, config) {
      this._inspectModeConfig = mode !== Protocol.Overlay.InspectMode.None ? config : null;
      return Promise.resolve();
    }
  
    /**
     * @override
     * @param {!Protocol.Page.FrameId} frameId
     */
    highlightFrame(frameId) {
    }
  
    /**
     * @param {!CanvasRenderingContext2D} context
     */
    _createCheckerboardPattern(context) {
      var pattern = /** @type {!HTMLCanvasElement} */ (createElement('canvas'));
      const size = 32;
      pattern.width = size * 2;
      pattern.height = size * 2;
      var pctx = pattern.getContext('2d');
  
      pctx.fillStyle = 'rgb(195, 195, 195)';
      pctx.fillRect(0, 0, size * 2, size * 2);
  
      pctx.fillStyle = 'rgb(225, 225, 225)';
      pctx.fillRect(0, 0, size, size);
      pctx.fillRect(size, size, size, size);
      return context.createPattern(pattern, 'repeat');
    }
  
    _createNavigationBar() {
      this._navigationBar = this.element.createChild('div', 'screencast-navigation');
      this._navigationBack = this._navigationBar.createChild('button', 'back');
      this._navigationBack.disabled = true;
      this._navigationForward = this._navigationBar.createChild('button', 'forward');
      this._navigationForward.disabled = true;
      this._navigationReload = this._navigationBar.createChild('button', 'reload');
      this._navigationUrl = UI.createInput();
      this._navigationBar.appendChild(this._navigationUrl);
      this._navigationUrl.type = 'text';
      this._navigationProgressBar = new Screencast.ScreencastView.ProgressTracker(
          this._resourceTreeModel, this._networkManager, this._navigationBar.createChild('div', 'progress'));
  
      if (this._resourceTreeModel) {
        this._navigationBack.addEventListener('click', this._navigateToHistoryEntry.bind(this, -1), false);
        this._navigationForward.addEventListener('click', this._navigateToHistoryEntry.bind(this, 1), false);
        this._navigationReload.addEventListener('click', this._navigateReload.bind(this), false);
        this._navigationUrl.addEventListener('keyup', this._navigationUrlKeyUp.bind(this), true);
        this._requestNavigationHistory();
        this._resourceTreeModel.addEventListener(
            SDK.ResourceTreeModel.Events.MainFrameNavigated, this._requestNavigationHistory, this);
        this._resourceTreeModel.addEventListener(
            SDK.ResourceTreeModel.Events.CachedResourcesLoaded, this._requestNavigationHistory, this);
      }
    }
  
    /**
     * @param {number} offset
     */
    _navigateToHistoryEntry(offset) {
      var newIndex = this._historyIndex + offset;
      if (newIndex < 0 || newIndex >= this._historyEntries.length)
        return;
      this._resourceTreeModel.navigateToHistoryEntry(this._historyEntries[newIndex]);
      this._requestNavigationHistory();
    }
  
    _navigateReload() {
      this._resourceTreeModel.reloadPage();
    }
  
    /**
     * @param {!Event} event
     */
    _navigationUrlKeyUp(event) {
      if (event.key !== 'Enter')
        return;
      var url = this._navigationUrl.value;
      if (!url)
        return;
      if (!url.match(Screencast.ScreencastView._SchemeRegex))
        url = 'http://' + url;
      this._resourceTreeModel.navigate(url);
      this._canvasElement.focus();
    }
  
    async _requestNavigationHistory() {
      var history = await this._resourceTreeModel.navigationHistory();
      if (!history)
        return;
  
      this._historyIndex = history.currentIndex;
      this._historyEntries = history.entries;
  
      this._navigationBack.disabled = this._historyIndex === 0;
      this._navigationForward.disabled = this._historyIndex === (this._historyEntries.length - 1);
  
      var url = this._historyEntries[this._historyIndex].url;
      var match = url.match(Screencast.ScreencastView._HttpRegex);
      if (match)
        url = match[1];
      InspectorFrontendHost.inspectedURLChanged(url);
      this._navigationUrl.value = url;
    }
  
    _focusNavigationBar() {
      this._navigationUrl.focus();
      this._navigationUrl.select();
      return true;
    }
  };
  
  Screencast.ScreencastView._bordersSize = 44;
  
  Screencast.ScreencastView._navBarHeight = 29;
  
  Screencast.ScreencastView._HttpRegex = /^http:\/\/(.+)/;
  
  Screencast.ScreencastView._SchemeRegex = /^(https?|about|chrome):/;
  
  /**
   * @unrestricted
   */
  Screencast.ScreencastView.ProgressTracker = class {
    /**
     * @param {?SDK.ResourceTreeModel} resourceTreeModel
     * @param {?SDK.NetworkManager} networkManager
     * @param {!Element} element
     */
    constructor(resourceTreeModel, networkManager, element) {
      this._element = element;
      if (resourceTreeModel) {
        resourceTreeModel.addEventListener(
            SDK.ResourceTreeModel.Events.MainFrameNavigated, this._onMainFrameNavigated, this);
        resourceTreeModel.addEventListener(SDK.ResourceTreeModel.Events.Load, this._onLoad, this);
      }
      if (networkManager) {
        networkManager.addEventListener(SDK.NetworkManager.Events.RequestStarted, this._onRequestStarted, this);
        networkManager.addEventListener(SDK.NetworkManager.Events.RequestFinished, this._onRequestFinished, this);
      }
    }
  
    _onMainFrameNavigated() {
      this._requestIds = {};
      this._startedRequests = 0;
      this._finishedRequests = 0;
      this._maxDisplayedProgress = 0;
      this._updateProgress(0.1);  // Display first 10% on navigation start.
    }
  
    _onLoad() {
      delete this._requestIds;
      this._updateProgress(1);  // Display 100% progress on load, hide it in 0.5s.
      setTimeout(function() {
        if (!this._navigationProgressVisible())
          this._displayProgress(0);
      }.bind(this), 500);
    }
  
    _navigationProgressVisible() {
      return !!this._requestIds;
    }
  
    _onRequestStarted(event) {
      if (!this._navigationProgressVisible())
        return;
      var request = /** @type {!SDK.NetworkRequest} */ (event.data);
      // Ignore long-living WebSockets for the sake of progress indicator, as we won't be waiting them anyway.
      if (request.type === Common.resourceTypes.WebSocket)
        return;
      this._requestIds[request.requestId()] = request;
      ++this._startedRequests;
    }
  
    _onRequestFinished(event) {
      if (!this._navigationProgressVisible())
        return;
      var request = /** @type {!SDK.NetworkRequest} */ (event.data);
      if (!(request.requestId() in this._requestIds))
        return;
      ++this._finishedRequests;
      setTimeout(function() {
        this._updateProgress(
            this._finishedRequests / this._startedRequests * 0.9);  // Finished requests drive the progress up to 90%.
      }.bind(this), 500);  // Delay to give the new requests time to start. This makes the progress smoother.
    }
  
    _updateProgress(progress) {
      if (!this._navigationProgressVisible())
        return;
      if (this._maxDisplayedProgress >= progress)
        return;
      this._maxDisplayedProgress = progress;
      this._displayProgress(progress);
    }
  
    _displayProgress(progress) {
      this._element.style.width = (100 * progress) + '%';
    }
  };
  