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.
   */
  
  /**
   * @constructor
   * @extends {WebInspector.VBox}
   * @param {string} prefix
   */
  WebInspector.TimelineOverviewPane = function(prefix)
  {
      WebInspector.VBox.call(this);
      this.element.id = prefix + "-overview-pane";
  
      this._overviewCalculator = new WebInspector.TimelineOverviewCalculator();
      this._overviewGrid = new WebInspector.OverviewGrid(prefix);
      this.element.appendChild(this._overviewGrid.element);
      this._cursorArea = this._overviewGrid.element.createChild("div", "overview-grid-cursor-area");
      this._cursorElement = this._overviewGrid.element.createChild("div", "overview-grid-cursor-position");
      this._cursorArea.addEventListener("mousemove", this._onMouseMove.bind(this), true);
      this._cursorArea.addEventListener("mouseleave", this._hideCursor.bind(this), true);
  
      this._overviewGrid.setResizeEnabled(false);
      this._overviewGrid.addEventListener(WebInspector.OverviewGrid.Events.WindowChanged, this._onWindowChanged, this);
      this._overviewGrid.addEventListener(WebInspector.OverviewGrid.Events.Click, this._onClick, this);
      this._overviewControls = [];
      this._markers = new Map();
  
      this._popoverHelper = new WebInspector.PopoverHelper(this._cursorArea, this._getPopoverAnchor.bind(this), this._showPopover.bind(this), this._onHidePopover.bind(this));
      this._popoverHelper.setTimeout(0);
  
      this._updateThrottler = new WebInspector.Throttler(100);
  
      this._cursorEnabled = false;
      this._cursorPosition = 0;
      this._lastWidth = 0;
  }
  
  WebInspector.TimelineOverviewPane.Events = {
      WindowChanged: "WindowChanged"
  };
  
  WebInspector.TimelineOverviewPane.prototype = {
      /**
       * @param {!Element} element
       * @param {!Event} event
       * @return {!Element|!AnchorBox|undefined}
       */
      _getPopoverAnchor: function(element, event)
      {
          return this._cursorArea;
      },
  
      /**
       * @param {!Element} anchor
       * @param {!WebInspector.Popover} popover
       */
      _showPopover: function(anchor, popover)
      {
          this._buildPopoverContents().then(maybeShowPopover.bind(this));
          /**
           * @this {WebInspector.TimelineOverviewPane}
           * @param {!DocumentFragment} fragment
           */
          function maybeShowPopover(fragment)
          {
              if (!fragment.firstChild)
                  return;
              var content = new WebInspector.TimelineOverviewPane.PopoverContents();
              this._popoverContents = content.contentElement.createChild("div");
              this._popoverContents.appendChild(fragment);
              this._popover = popover;
              popover.showView(content, this._cursorElement);
          }
      },
  
      _onHidePopover: function()
      {
          this._popover = null;
          this._popoverContents = null;
      },
  
      /**
       * @param {!Event} event
       */
      _onMouseMove: function(event)
      {
          if (!this._cursorEnabled)
              return;
          this._cursorPosition = event.offsetX + event.target.offsetLeft;
          this._cursorElement.style.left = this._cursorPosition + "px";
          this._cursorElement.style.visibility = "visible";
          if (!this._popover)
              return;
          this._buildPopoverContents().then(updatePopover.bind(this));
          this._popover.positionElement(this._cursorElement);
  
          /**
           * @param {!DocumentFragment} fragment
           * @this {WebInspector.TimelineOverviewPane}
           */
          function updatePopover(fragment)
          {
              if (!this._popoverContents)
                  return;
              this._popoverContents.removeChildren();
              this._popoverContents.appendChild(fragment);
          }
      },
  
      /**
       * @return {!Promise<!DocumentFragment>}
       */
      _buildPopoverContents: function()
      {
          var document = this.element.ownerDocument;
          var x = this._cursorPosition;
          var promises = this._overviewControls.map(control => control.popoverElementPromise(x));
          return Promise.all(promises).then(buildFragment);
  
          /**
           * @param {!Array<?Element>} elements
           * @return {!DocumentFragment}
           */
          function buildFragment(elements)
          {
              var fragment = document.createDocumentFragment();
              elements.remove(null);
              fragment.appendChildren.apply(fragment, elements);
              return fragment;
          }
      },
  
      _hideCursor: function()
      {
          this._cursorElement.style.visibility = "hidden";
      },
  
      /**
       * @override
       */
      wasShown: function()
      {
          this._update();
      },
  
      /**
       * @override
       */
      willHide: function()
      {
          this._popoverHelper.hidePopover();
      },
  
      /**
       * @override
       */
      onResize: function()
      {
          var width = this.element.offsetWidth;
          if (width === this._lastWidth)
              return;
          this._lastWidth = width;
          this.scheduleUpdate();
      },
  
      /**
       * @param {!Array.<!WebInspector.TimelineOverview>} overviewControls
       */
      setOverviewControls: function(overviewControls)
      {
          for (var i = 0; i < this._overviewControls.length; ++i)
              this._overviewControls[i].dispose();
  
          for (var i = 0; i < overviewControls.length; ++i) {
              overviewControls[i].setCalculator(this._overviewCalculator);
              overviewControls[i].show(this._overviewGrid.element);
          }
          this._overviewControls = overviewControls;
          this._update();
      },
  
      /**
       * @param {number} minimumBoundary
       * @param {number} maximumBoundary
       */
      setBounds: function(minimumBoundary, maximumBoundary)
      {
          this._overviewCalculator.setBounds(minimumBoundary, maximumBoundary);
          this._overviewGrid.setResizeEnabled(true);
          this._cursorEnabled = true;
      },
  
      scheduleUpdate: function()
      {
          this._updateThrottler.schedule(process.bind(this));
          /**
           * @this {WebInspector.TimelineOverviewPane}
           * @return {!Promise.<undefined>}
           */
          function process()
          {
              this._update();
              return Promise.resolve();
          }
      },
  
      _update: function()
      {
          if (!this.isShowing())
              return;
          this._overviewCalculator.setDisplayWindow(this._overviewGrid.clientWidth());
          for (var i = 0; i < this._overviewControls.length; ++i)
              this._overviewControls[i].update();
          this._overviewGrid.updateDividers(this._overviewCalculator);
          this._updateMarkers();
          this._updateWindow();
      },
  
      /**
       * @param {!Map<number, !Element>} markers
       */
      setMarkers: function(markers)
      {
          this._markers = markers;
          this._updateMarkers();
      },
  
      _updateMarkers: function()
      {
          var filteredMarkers = new Map();
          for (var time of this._markers.keys()) {
              var marker = this._markers.get(time);
              var position = Math.round(this._overviewCalculator.computePosition(time));
              // Limit the number of markers to one per pixel.
              if (filteredMarkers.has(position))
                  continue;
              filteredMarkers.set(position, marker);
              marker.style.left = position + "px";
          }
          this._overviewGrid.removeEventDividers();
          this._overviewGrid.addEventDividers(filteredMarkers.valuesArray());
      },
  
      reset: function()
      {
          this._windowStartTime = 0;
          this._windowEndTime = Infinity;
          this._overviewCalculator.reset();
          this._overviewGrid.reset();
          this._overviewGrid.setResizeEnabled(false);
          this._overviewGrid.updateDividers(this._overviewCalculator);
          this._cursorEnabled = false;
          this._hideCursor();
          this._markers = new Map();
          for (var i = 0; i < this._overviewControls.length; ++i)
              this._overviewControls[i].reset();
          this._popoverHelper.hidePopover();
          this._update();
      },
  
      /**
       * @param {!WebInspector.Event} event
       */
      _onClick: function(event)
      {
          var domEvent = /** @type {!Event} */ (event.data);
          for (var overviewControl of this._overviewControls) {
              if (overviewControl.onClick(domEvent)) {
                  event.preventDefault();
                  return;
              }
          }
      },
  
      /**
       * @param {!WebInspector.Event} event
       */
      _onWindowChanged: function(event)
      {
          if (this._muteOnWindowChanged)
              return;
          // Always use first control as a time converter.
          if (!this._overviewControls.length)
              return;
          var windowTimes = this._overviewControls[0].windowTimes(this._overviewGrid.windowLeft(), this._overviewGrid.windowRight());
          this._windowStartTime = windowTimes.startTime;
          this._windowEndTime = windowTimes.endTime;
          this.dispatchEventToListeners(WebInspector.TimelineOverviewPane.Events.WindowChanged, windowTimes);
      },
  
      /**
       * @param {number} startTime
       * @param {number} endTime
       */
      requestWindowTimes: function(startTime, endTime)
      {
          if (startTime === this._windowStartTime && endTime === this._windowEndTime)
              return;
          this._windowStartTime = startTime;
          this._windowEndTime = endTime;
          this._updateWindow();
          this.dispatchEventToListeners(WebInspector.TimelineOverviewPane.Events.WindowChanged, { startTime: startTime, endTime: endTime });
      },
  
      _updateWindow: function()
      {
          if (!this._overviewControls.length)
              return;
          var windowBoundaries = this._overviewControls[0].windowBoundaries(this._windowStartTime, this._windowEndTime);
          this._muteOnWindowChanged = true;
          this._overviewGrid.setWindow(windowBoundaries.left, windowBoundaries.right);
          this._muteOnWindowChanged = false;
      },
  
      __proto__: WebInspector.VBox.prototype
  }
  
  /**
   * @constructor
   * @extends {WebInspector.VBox}
   */
  WebInspector.TimelineOverviewPane.PopoverContents = function()
  {
      WebInspector.VBox.call(this, true);
      this.contentElement.classList.add("timeline-overview-popover");
  }
  
  WebInspector.TimelineOverviewPane.PopoverContents.prototype = {
      __proto__: WebInspector.VBox.prototype
  }
  
  /**
   * @constructor
   * @implements {WebInspector.TimelineGrid.Calculator}
   */
  WebInspector.TimelineOverviewCalculator = function()
  {
      this.reset();
  }
  
  WebInspector.TimelineOverviewCalculator.prototype = {
      /**
       * @override
       * @return {number}
       */
      paddingLeft: function()
      {
          return this._paddingLeft;
      },
  
      /**
       * @override
       * @param {number} time
       * @return {number}
       */
      computePosition: function(time)
      {
          return (time - this._minimumBoundary) / this.boundarySpan() * this._workingArea + this._paddingLeft;
      },
  
      /**
       * @param {number} position
       * @return {number}
       */
      positionToTime: function(position)
      {
          return (position - this._paddingLeft) / this._workingArea * this.boundarySpan() + this._minimumBoundary;
      },
  
      /**
       * @param {number} minimumBoundary
       * @param {number} maximumBoundary
       */
      setBounds: function(minimumBoundary, maximumBoundary)
      {
          this._minimumBoundary = minimumBoundary;
          this._maximumBoundary = maximumBoundary;
      },
  
      /**
       * @param {number} clientWidth
       * @param {number=} paddingLeft
       */
      setDisplayWindow: function(clientWidth, paddingLeft)
      {
          this._paddingLeft = paddingLeft || 0;
          this._workingArea = clientWidth - this._paddingLeft;
      },
  
      reset: function()
      {
          this.setBounds(0, 1000);
      },
  
      /**
       * @override
       * @param {number} value
       * @param {number=} precision
       * @return {string}
       */
      formatValue: function(value, precision)
      {
          return Number.preciseMillisToString(value - this.zeroTime(), precision);
      },
  
      /**
       * @override
       * @return {number}
       */
      maximumBoundary: function()
      {
          return this._maximumBoundary;
      },
  
      /**
       * @override
       * @return {number}
       */
      minimumBoundary: function()
      {
          return this._minimumBoundary;
      },
  
      /**
       * @override
       * @return {number}
       */
      zeroTime: function()
      {
          return this._minimumBoundary;
      },
  
      /**
       * @override
       * @return {number}
       */
      boundarySpan: function()
      {
          return this._maximumBoundary - this._minimumBoundary;
      }
  }
  
  /**
   * @interface
   */
  WebInspector.TimelineOverview = function()
  {
  }
  
  WebInspector.TimelineOverview.prototype = {
      /**
       * @param {?Element} parentElement
       * @param {!Element=} insertBefore
       */
      show: function(parentElement, insertBefore) { },
  
      update: function() { },
  
      dispose: function() { },
  
      reset: function() { },
  
      /**
       * @param {number} x
       * @return {!Promise<?Element>}
       */
      popoverElementPromise: function(x) { },
  
      /**
       * @param {!Event} event
       * @return {boolean}
       */
      onClick: function(event) { },
  
      /**
       * @param {number} windowLeft
       * @param {number} windowRight
       * @return {!{startTime: number, endTime: number}}
       */
      windowTimes: function(windowLeft, windowRight) { },
  
      /**
       * @param {number} startTime
       * @param {number} endTime
       * @return {!{left: number, right: number}}
       */
      windowBoundaries: function(startTime, endTime) { },
  
      timelineStarted: function() { },
  
      timelineStopped: function() { },
  }
  
  /**
   * @constructor
   * @extends {WebInspector.VBox}
   * @implements {WebInspector.TimelineOverview}
   */
  WebInspector.TimelineOverviewBase = function()
  {
      WebInspector.VBox.call(this);
      /** @type {?WebInspector.TimelineOverviewCalculator} */
      this._calculator = null;
      this._canvas = this.element.createChild("canvas", "fill");
      this._context = this._canvas.getContext("2d");
  }
  
  WebInspector.TimelineOverviewBase.prototype = {
      /**
       * @override
       */
      update: function()
      {
          this.resetCanvas();
      },
  
      /**
       * @override
       */
      dispose: function()
      {
          this.detach();
      },
  
      /**
       * @override
       */
      reset: function()
      {
      },
  
      /**
       * @override
       * @param {number} x
       * @return {!Promise<?Element>}
       */
      popoverElementPromise: function(x)
      {
          return Promise.resolve(/** @type {?Element} */ (null));
      },
  
      /**
       * @override
       */
      timelineStarted: function()
      {
      },
  
      /**
       * @override
       */
      timelineStopped: function()
      {
      },
  
      /**
       * @param {!WebInspector.TimelineOverviewCalculator} calculator
       */
      setCalculator: function(calculator)
      {
          this._calculator = calculator;
      },
  
      /**
       * @override
       * @param {!Event} event
       * @return {boolean}
       */
      onClick: function(event)
      {
          return false;
      },
  
      /**
       * @override
       * @param {number} windowLeft
       * @param {number} windowRight
       * @return {!{startTime: number, endTime: number}}
       */
      windowTimes: function(windowLeft, windowRight)
      {
          var absoluteMin = this._calculator.minimumBoundary();
          var timeSpan = this._calculator.maximumBoundary() - absoluteMin;
          return {
              startTime: absoluteMin + timeSpan * windowLeft,
              endTime: absoluteMin + timeSpan * windowRight
          };
      },
  
      /**
       * @override
       * @param {number} startTime
       * @param {number} endTime
       * @return {!{left: number, right: number}}
       */
      windowBoundaries: function(startTime, endTime)
      {
          var absoluteMin = this._calculator.minimumBoundary();
          var timeSpan = this._calculator.maximumBoundary() - absoluteMin;
          var haveRecords = absoluteMin > 0;
          return {
              left: haveRecords && startTime ? Math.min((startTime - absoluteMin) / timeSpan, 1) : 0,
              right: haveRecords && endTime < Infinity ? (endTime - absoluteMin) / timeSpan : 1
          };
      },
  
      resetCanvas: function()
      {
          this._canvas.width = this.element.clientWidth * window.devicePixelRatio;
          this._canvas.height = this.element.clientHeight * window.devicePixelRatio;
      },
  
      __proto__: WebInspector.VBox.prototype
  }
  