<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[203035] trunk/Websites/perf.webkit.org</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/203035">203035</a></dd>
<dt>Author</dt> <dd>rniwa@webkit.org</dd>
<dt>Date</dt> <dd>2016-07-09 15:58:54 -0700 (Sat, 09 Jul 2016)</dd>
</dl>

<h3>Log Message</h3>
<pre>Perf dashboard can consume 50-70% of CPU on MacBook even if user is not interacting at all
https://bugs.webkit.org/show_bug.cgi?id=159597

Reviewed by Chris Dumez.

TimeSeriesChart and InteractiveTimeSeriesChart had been relying on continually polling on requestAnimationFrame
to update itself in response to its canvas resizing. Even though there as an early exit in the case there was
nothing to update, this is still causing a significant power drain when the user is not interacting at all.

Let TimeSeriesChart use the regular top-down render path like other components with exceptions of listening to
window's resize eventas well as when new JSONs are fetched from the server. The render() call to the latter case
will be coerced into a single callback on requestAnimationFrame to avoid DOM-mutation-layout churn.

* public/v3/components/base.js:
(ComponentBase.isElementInViewport): Deleted.
* public/v3/components/chart-pane-base.js:
(ChartPaneBase.prototype.render): Enqueue charts to render.
* public/v3/components/chart-styles.js:
(ChartStyles.dashboardOptions): Removed updateOnRequestAnimationFrame which is no longer an available option.
* public/v3/components/time-series-chart.js:
(TimeSeriesChart): Replaced the code to register itself for rAF by the code to listen to resize events on window.
(TimeSeriesChart._updateOnRAF): Deleted.
(TimeSeriesChart._updateAllCharts): Added.
(TimeSeriesChart.prototype._enqueueToRender): Added.
(TimeSeriesChart._renderEnqueuedCharts): Added.
(TimeSeriesChart.prototype.fetchMeasurementSets): Avoid calling fetchBetween when the range had been fetched.
Without this change, we can incur a significant number of redundant calls to render() when adjusting the domain
in charts page by the slider. When no new JSON is fetched, simply enqueue this chart to render on rAF.
(TimeSeriesChart.prototype._didFetchMeasurementSet): Enqueue this chart to render on rAF.
(TimeSeriesChart.prototype.render): Removed the check for isElementInViewport since we no longer get render() call
when this chart moves into the viewport (as we no longer listen to every rAF or scroll event).
* public/v3/models/measurement-set.js:
(MeasurementSet.prototype.hasFetchedRange): Fixed various bugs revealed by the new use in fetchMeasurementSets.
* public/v3/pages/chart-pane-status-view.js:
(ChartPaneStatusView.prototype._updateRevisionListForNewCurrentRepository): Removed the dead code. It was probably
copied from when this code was in InteractiveTimeSeries chart. There is no this._forceRender in this component.
* public/v3/pages/dashboard-page.js:
(DashboardPage.prototype.render): Enqueue charts to render.
* public/v3/pages/heading.js:
(SummaryPage.prototype.open): Removed the call to isElementInViewport. This wasn't really doing anything useful in
this component.
* unit-tests/measurement-set-tests.js: Added tests for hasFetchedRange.
(.waitForMeasurementSet): Moved to be used in a newly added test case.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkWebsitesperfwebkitorgChangeLog">trunk/Websites/perf.webkit.org/ChangeLog</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3componentsbasejs">trunk/Websites/perf.webkit.org/public/v3/components/base.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3componentschartpanebasejs">trunk/Websites/perf.webkit.org/public/v3/components/chart-pane-base.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3componentschartstylesjs">trunk/Websites/perf.webkit.org/public/v3/components/chart-styles.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3componentstimeserieschartjs">trunk/Websites/perf.webkit.org/public/v3/components/time-series-chart.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3modelsmeasurementsetjs">trunk/Websites/perf.webkit.org/public/v3/models/measurement-set.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3pageschartpanestatusviewjs">trunk/Websites/perf.webkit.org/public/v3/pages/chart-pane-status-view.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3pagesdashboardpagejs">trunk/Websites/perf.webkit.org/public/v3/pages/dashboard-page.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3pagesheadingjs">trunk/Websites/perf.webkit.org/public/v3/pages/heading.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgunittestsmeasurementsettestsjs">trunk/Websites/perf.webkit.org/unit-tests/measurement-set-tests.js</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkWebsitesperfwebkitorgChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/ChangeLog (203034 => 203035)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/ChangeLog        2016-07-09 21:07:45 UTC (rev 203034)
+++ trunk/Websites/perf.webkit.org/ChangeLog        2016-07-09 22:58:54 UTC (rev 203035)
</span><span class="lines">@@ -1,5 +1,51 @@
</span><span class="cx"> 2016-07-09  Ryosuke Niwa  &lt;rniwa@webkit.org&gt;
</span><span class="cx"> 
</span><ins>+        Perf dashboard can consume 50-70% of CPU on MacBook even if user is not interacting at all
+        https://bugs.webkit.org/show_bug.cgi?id=159597
+
+        Reviewed by Chris Dumez.
+
+        TimeSeriesChart and InteractiveTimeSeriesChart had been relying on continually polling on requestAnimationFrame
+        to update itself in response to its canvas resizing. Even though there as an early exit in the case there was
+        nothing to update, this is still causing a significant power drain when the user is not interacting at all.
+
+        Let TimeSeriesChart use the regular top-down render path like other components with exceptions of listening to
+        window's resize eventas well as when new JSONs are fetched from the server. The render() call to the latter case
+        will be coerced into a single callback on requestAnimationFrame to avoid DOM-mutation-layout churn.
+
+        * public/v3/components/base.js:
+        (ComponentBase.isElementInViewport): Deleted.
+        * public/v3/components/chart-pane-base.js:
+        (ChartPaneBase.prototype.render): Enqueue charts to render.
+        * public/v3/components/chart-styles.js:
+        (ChartStyles.dashboardOptions): Removed updateOnRequestAnimationFrame which is no longer an available option.
+        * public/v3/components/time-series-chart.js:
+        (TimeSeriesChart): Replaced the code to register itself for rAF by the code to listen to resize events on window.
+        (TimeSeriesChart._updateOnRAF): Deleted.
+        (TimeSeriesChart._updateAllCharts): Added.
+        (TimeSeriesChart.prototype._enqueueToRender): Added.
+        (TimeSeriesChart._renderEnqueuedCharts): Added.
+        (TimeSeriesChart.prototype.fetchMeasurementSets): Avoid calling fetchBetween when the range had been fetched.
+        Without this change, we can incur a significant number of redundant calls to render() when adjusting the domain
+        in charts page by the slider. When no new JSON is fetched, simply enqueue this chart to render on rAF.
+        (TimeSeriesChart.prototype._didFetchMeasurementSet): Enqueue this chart to render on rAF.
+        (TimeSeriesChart.prototype.render): Removed the check for isElementInViewport since we no longer get render() call
+        when this chart moves into the viewport (as we no longer listen to every rAF or scroll event).
+        * public/v3/models/measurement-set.js:
+        (MeasurementSet.prototype.hasFetchedRange): Fixed various bugs revealed by the new use in fetchMeasurementSets.
+        * public/v3/pages/chart-pane-status-view.js:
+        (ChartPaneStatusView.prototype._updateRevisionListForNewCurrentRepository): Removed the dead code. It was probably
+        copied from when this code was in InteractiveTimeSeries chart. There is no this._forceRender in this component.
+        * public/v3/pages/dashboard-page.js:
+        (DashboardPage.prototype.render): Enqueue charts to render. 
+        * public/v3/pages/heading.js:
+        (SummaryPage.prototype.open): Removed the call to isElementInViewport. This wasn't really doing anything useful in
+        this component.
+        * unit-tests/measurement-set-tests.js: Added tests for hasFetchedRange.
+        (.waitForMeasurementSet): Moved to be used in a newly added test case.
+
+2016-07-09  Ryosuke Niwa  &lt;rniwa@webkit.org&gt;
+
</ins><span class="cx">         REGRESSION: manifest.json generation takes multiple seconds on perf dashboard
</span><span class="cx">         https://bugs.webkit.org/show_bug.cgi?id=159596
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3componentsbasejs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/components/base.js (203034 => 203035)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/components/base.js        2016-07-09 21:07:45 UTC (rev 203034)
+++ trunk/Websites/perf.webkit.org/public/v3/components/base.js        2016-07-09 22:58:54 UTC (rev 203035)
</span><span class="lines">@@ -75,16 +75,6 @@
</span><span class="cx">         }
</span><span class="cx">     }
</span><span class="cx"> 
</span><del>-    static isElementInViewport(element)
-    {
-        var viewportHeight = window.innerHeight;
-        var boundingRect = element.getBoundingClientRect();
-        if (viewportHeight &lt; boundingRect.top || boundingRect.bottom &lt; 0
-            || !boundingRect.width || !boundingRect.height)
-            return false;
-        return true;
-    }
-
</del><span class="cx">     static defineElement(name, elementInterface)
</span><span class="cx">     {
</span><span class="cx">         if (!ComponentBase._map)
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3componentschartpanebasejs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/components/chart-pane-base.js (203034 => 203035)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/components/chart-pane-base.js        2016-07-09 21:07:45 UTC (rev 203034)
+++ trunk/Websites/perf.webkit.org/public/v3/components/chart-pane-base.js        2016-07-09 22:58:54 UTC (rev 203035)
</span><span class="lines">@@ -197,6 +197,12 @@
</span><span class="cx"> 
</span><span class="cx">         super.render();
</span><span class="cx"> 
</span><ins>+        if (this._overviewChart)
+            this._overviewChart.enqueueToRender();
+
+        if (this._mainChart)
+            this._mainChart.enqueueToRender();
+
</ins><span class="cx">         if (this._errorMessage) {
</span><span class="cx">             this.renderReplace(this.content().querySelector('.chart-pane-main'), this._errorMessage);
</span><span class="cx">             return;
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3componentschartstylesjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/components/chart-styles.js (203034 => 203035)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/components/chart-styles.js        2016-07-09 21:07:45 UTC (rev 203034)
+++ trunk/Websites/perf.webkit.org/public/v3/components/chart-styles.js        2016-07-09 22:58:54 UTC (rev 203035)
</span><span class="lines">@@ -87,7 +87,6 @@
</span><span class="cx">     static dashboardOptions(valueFormatter)
</span><span class="cx">     {
</span><span class="cx">         return {
</span><del>-            updateOnRequestAnimationFrame: true,
</del><span class="cx">             axis: {
</span><span class="cx">                 yAxisWidth: 4, // rem
</span><span class="cx">                 xAxisHeight: 2, // rem
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3componentstimeserieschartjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/components/time-series-chart.js (203034 => 203035)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/components/time-series-chart.js        2016-07-09 21:07:45 UTC (rev 203034)
+++ trunk/Websites/perf.webkit.org/public/v3/components/time-series-chart.js        2016-07-09 22:58:54 UTC (rev 203035)
</span><span class="lines">@@ -21,12 +21,11 @@
</span><span class="cx">         this._contextScaleY = 1;
</span><span class="cx">         this._rem = null;
</span><span class="cx"> 
</span><del>-        if (this._options.updateOnRequestAnimationFrame) {
-            if (!TimeSeriesChart._chartList)
-                TimeSeriesChart._chartList = [];
-            TimeSeriesChart._chartList.push(this);
-            TimeSeriesChart._updateOnRAF();
</del><ins>+        if (!TimeSeriesChart._chartList) {
+            TimeSeriesChart._chartList = [];
+            window.addEventListener('resize', TimeSeriesChart._updateAllCharts.bind(TimeSeriesChart));
</ins><span class="cx">         }
</span><ins>+        TimeSeriesChart._chartList.push(this);
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     _ensureCanvas()
</span><span class="lines">@@ -51,16 +50,27 @@
</span><span class="cx">         return document.createElement('canvas');
</span><span class="cx">     }
</span><span class="cx"> 
</span><del>-    static _updateOnRAF()
</del><ins>+    static _updateAllCharts()
</ins><span class="cx">     {
</span><del>-        var self = this;
-        window.requestAnimationFrame(function ()
-        {
-            TimeSeriesChart._chartList.map(function (chart) { chart.render(); });
-            self._updateOnRAF();
-        });
</del><ins>+        TimeSeriesChart._chartList.map(function (chart) { chart.render(); });
</ins><span class="cx">     }
</span><span class="cx"> 
</span><ins>+    enqueueToRender()
+    {
+        if (!TimeSeriesChart._chartQueue) {
+            TimeSeriesChart._chartQueue = new Set;
+            window.requestAnimationFrame(TimeSeriesChart._renderEnqueuedCharts.bind(TimeSeriesChart));
+        }
+        TimeSeriesChart._chartQueue.add(this);
+    }
+
+    static _renderEnqueuedCharts()
+    {
+        for (var chart of TimeSeriesChart._chartQueue)
+            chart.render();
+        TimeSeriesChart._chartQueue = null;
+    }
+
</ins><span class="cx">     setDomain(startTime, endTime)
</span><span class="cx">     {
</span><span class="cx">         console.assert(startTime &lt; endTime, 'startTime must be before endTime');
</span><span class="lines">@@ -77,13 +87,21 @@
</span><span class="cx"> 
</span><span class="cx">     fetchMeasurementSets(noCache)
</span><span class="cx">     {
</span><ins>+        var fetching = false;
</ins><span class="cx">         for (var source of this._sourceList) {
</span><del>-            if (source.measurementSet)
</del><ins>+            if (source.measurementSet) {
+                if (source.measurementSet.hasFetchedRange(this._startTime, this._endTime))
+                    continue;
</ins><span class="cx">                 source.measurementSet.fetchBetween(this._startTime, this._endTime, this._didFetchMeasurementSet.bind(this, source.measurementSet), noCache);
</span><ins>+                fetching = true;
+            }
+
</ins><span class="cx">         }
</span><span class="cx">         this._sampledTimeSeriesData = null;
</span><span class="cx">         this._valueRangeCache = null;
</span><span class="cx">         this._annotationRows = null;
</span><ins>+        if (!fetching)
+            this.enqueueToRender();
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     _didFetchMeasurementSet(set)
</span><span class="lines">@@ -92,6 +110,8 @@
</span><span class="cx">         this._sampledTimeSeriesData = null;
</span><span class="cx">         this._valueRangeCache = null;
</span><span class="cx">         this._annotationRows = null;
</span><ins>+
+        this.enqueueToRender();
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     // FIXME: Figure out a way to make this readonly.
</span><span class="lines">@@ -135,8 +155,6 @@
</span><span class="cx"> 
</span><span class="cx">         // FIXME: Also detect horizontal scrolling.
</span><span class="cx">         var canvas = this._ensureCanvas();
</span><del>-        if (!TimeSeriesChart.isElementInViewport(canvas))
-            return;
</del><span class="cx"> 
</span><span class="cx">         var metrics = this._layout();
</span><span class="cx">         if (!metrics.doneWork)
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3modelsmeasurementsetjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/models/measurement-set.js (203034 => 203035)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/models/measurement-set.js        2016-07-09 21:07:45 UTC (rev 203034)
+++ trunk/Websites/perf.webkit.org/public/v3/models/measurement-set.js        2016-07-09 22:58:54 UTC (rev 203035)
</span><span class="lines">@@ -150,18 +150,23 @@
</span><span class="cx">     hasFetchedRange(startTime, endTime)
</span><span class="cx">     {
</span><span class="cx">         console.assert(startTime &lt; endTime);
</span><del>-        var hasHole = false;
</del><ins>+        var foundStart = false;
</ins><span class="cx">         var previousEndTime = null;
</span><span class="cx">         for (var cluster of this._sortedClusters) {
</span><del>-            if (cluster.startTime() &lt; startTime &amp;&amp; startTime &lt; cluster.endTime())
-                hasHole = false;
-            if (previousEndTime !== null &amp;&amp; previousEndTime != cluster.startTime())
-                hasHole = true;
-            if (cluster.startTime() &lt; endTime &amp;&amp; endTime &lt; cluster.endTime())
-                break;
</del><ins>+            var containsStart = cluster.startTime() &lt;= startTime &amp;&amp; startTime &lt;= cluster.endTime();
+            var containsEnd = cluster.startTime() &lt;= endTime &amp;&amp; endTime &lt;= cluster.endTime();
+            var preceedingClusterIsMissing = previousEndTime !== null &amp;&amp; previousEndTime != cluster.startTime();
+            if (containsStart &amp;&amp; containsEnd)
+                return true;
+            if (containsStart)
+                foundStart = true;
+            if (foundStart &amp;&amp; preceedingClusterIsMissing)
+                return false;
+            if (containsEnd)
+                return foundStart; // Return true iff there were not missing clusters from the one that contains startTime
</ins><span class="cx">             previousEndTime = cluster.endTime();
</span><span class="cx">         }
</span><del>-        return !hasHole;
</del><ins>+        return false; // Didn't find a cluster that contains startTime or endTime
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     fetchedTimeSeries(configType, includeOutliers, extendToFuture)
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3pageschartpanestatusviewjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/pages/chart-pane-status-view.js (203034 => 203035)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/pages/chart-pane-status-view.js        2016-07-09 21:07:45 UTC (rev 203034)
+++ trunk/Websites/perf.webkit.org/public/v3/pages/chart-pane-status-view.js        2016-07-09 22:58:54 UTC (rev 203035)
</span><span class="lines">@@ -117,7 +117,6 @@
</span><span class="cx">     {
</span><span class="cx">         this.updateStatusIfNeeded();
</span><span class="cx"> 
</span><del>-        this._forceRender = true;
</del><span class="cx">         for (var info of this._revisionList) {
</span><span class="cx">             if (info.repository == this._currentRepository) {
</span><span class="cx">                 this._setRevisionRange(false, info.repository, info.from, info.to);
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3pagesdashboardpagejs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/pages/dashboard-page.js (203034 => 203035)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/pages/dashboard-page.js        2016-07-09 21:07:45 UTC (rev 203034)
+++ trunk/Websites/perf.webkit.org/public/v3/pages/dashboard-page.js        2016-07-09 22:58:54 UTC (rev 203035)
</span><span class="lines">@@ -109,6 +109,9 @@
</span><span class="cx">             this._needsTableConstruction = false;
</span><span class="cx">         }
</span><span class="cx"> 
</span><ins>+        for (var chart of this._charts)
+            chart.enqueueToRender();
+
</ins><span class="cx">         if (this._needsStatusUpdate) {
</span><span class="cx">             for (var statusView of this._statusViews)
</span><span class="cx">                 statusView.render();
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3pagesheadingjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/pages/heading.js (203034 => 203035)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/pages/heading.js        2016-07-09 21:07:45 UTC (rev 203034)
+++ trunk/Websites/perf.webkit.org/public/v3/pages/heading.js        2016-07-09 22:58:54 UTC (rev 203035)
</span><span class="lines">@@ -52,8 +52,7 @@
</span><span class="cx">         if (this._toolbar)
</span><span class="cx">             this._toolbar.render();
</span><span class="cx"> 
</span><del>-        // Workaround the bounding rects being 0x0 when the content is empty.
-        if (this._renderedOnce &amp;&amp; !Heading.isElementInViewport(this.element()))
</del><ins>+        if (this._renderedOnce)
</ins><span class="cx">             return;
</span><span class="cx"> 
</span><span class="cx">         var title = this.content().querySelector('.heading-title a');
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgunittestsmeasurementsettestsjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/unit-tests/measurement-set-tests.js (203034 => 203035)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/unit-tests/measurement-set-tests.js        2016-07-09 21:07:45 UTC (rev 203034)
+++ trunk/Websites/perf.webkit.org/unit-tests/measurement-set-tests.js        2016-07-09 22:58:54 UTC (rev 203035)
</span><span class="lines">@@ -14,6 +14,15 @@
</span><span class="cx">         MeasurementSet._set = null;
</span><span class="cx">     });
</span><span class="cx"> 
</span><ins>+    function waitForMeasurementSet()
+    {
+        return Promise.resolve().then(function () {
+            return Promise.resolve();
+        }).then(function () {
+            return Promise.resolve();
+        });
+    }
+
</ins><span class="cx">     describe('findSet', function () {
</span><span class="cx">         it('should create a new MeasurementSet for a new pair of platform and matric', function () {
</span><span class="cx">             assert.notEqual(MeasurementSet.findSet(1, 1, 3000), MeasurementSet.findSet(1, 2, 3000));
</span><span class="lines">@@ -71,15 +80,6 @@
</span><span class="cx">             });
</span><span class="cx">         });
</span><span class="cx"> 
</span><del>-        function waitForMeasurementSet()
-        {
-            return Promise.resolve().then(function () {
-                return Promise.resolve();
-            }).then(function () {
-                return Promise.resolve();
-            });
-        }
-
</del><span class="cx">         it('should invoke the callback and fetch a secondary cluster when the cached primary cluster is up-to-date and within in the requested range', function (done) {
</span><span class="cx">             var set = MeasurementSet.findSet(1, 1, 3000);
</span><span class="cx">             var callCount = 0;
</span><span class="lines">@@ -517,4 +517,168 @@
</span><span class="cx"> 
</span><span class="cx">     });
</span><span class="cx"> 
</span><ins>+    describe('hasFetchedRange', function () {
+
+        it('should return false when no clusters had been fetched', function () {
+            var set = MeasurementSet.findSet(1, 1, 3000);
+            assert(!set.hasFetchedRange(2000, 3000));
+        });
+
+        it('should return true when a single cluster contains the entire range', function (done) {
+            var set = MeasurementSet.findSet(1, 1, 3000);
+            var promise = set.fetchBetween(2000, 3000);
+            assert.equal(requests.length, 1);
+            assert.equal(requests[0].url, '../data/measurement-set-1-1.json');
+
+            requests[0].resolve({
+                'clusterStart': 1000,
+                'clusterSize': 1000,
+                'formatMap': [],
+                'configurations': {current: []},
+                'startTime': 2000,
+                'endTime': 3000,
+                'lastModified': 3000,
+                'clusterCount': 2,
+                'status': 'OK'});
+
+            promise.then(function () {
+                assert(set.hasFetchedRange(2001, 2999));
+                assert(set.hasFetchedRange(2000, 3000));
+                done();
+            }).catch(function (error) {
+                done(error);
+            });
+        });
+
+        it('should return false when the range starts before the fetched cluster', function (done) {
+            var set = MeasurementSet.findSet(1, 1, 3000);
+            var promise = set.fetchBetween(2000, 3000);
+            assert.equal(requests.length, 1);
+            assert.equal(requests[0].url, '../data/measurement-set-1-1.json');
+
+            requests[0].resolve({
+                'clusterStart': 1000,
+                'clusterSize': 1000,
+                'formatMap': [],
+                'configurations': {current: []},
+                'startTime': 2000,
+                'endTime': 3000,
+                'lastModified': 3000,
+                'clusterCount': 2,
+                'status': 'OK'});
+
+            promise.then(function () {
+                assert(!set.hasFetchedRange(1500, 3000));
+                done();
+            }).catch(function (error) {
+                done(error);
+            });
+        });
+
+        it('should return false when the range ends after the fetched cluster', function (done) {
+            var set = MeasurementSet.findSet(1, 1, 3000);
+            var promise = set.fetchBetween(2000, 3000);
+            assert.equal(requests.length, 1);
+            assert.equal(requests[0].url, '../data/measurement-set-1-1.json');
+
+            requests[0].resolve({
+                'clusterStart': 1000,
+                'clusterSize': 1000,
+                'formatMap': [],
+                'configurations': {current: []},
+                'startTime': 2000,
+                'endTime': 3000,
+                'lastModified': 3000,
+                'clusterCount': 2,
+                'status': 'OK'});
+
+            promise.then(function () {
+                assert(!set.hasFetchedRange(2500, 3500));
+                done();
+            }).catch(function (error) {
+                done(error);
+            });
+        });
+
+        it('should return true when the range is within two fetched clusters', function (done) {
+            var set = MeasurementSet.findSet(1, 1, 5000);
+            var promise = set.fetchBetween(2000, 3000);
+            assert.equal(requests.length, 1);
+            assert.equal(requests[0].url, '../data/measurement-set-1-1.json');
+
+            requests[0].resolve({
+                'clusterStart': 1000,
+                'clusterSize': 1000,
+                'formatMap': [],
+                'configurations': {current: []},
+                'startTime': 3000,
+                'endTime': 4000,
+                'lastModified': 5000,
+                'clusterCount': 2,
+                'status': 'OK'});
+
+            waitForMeasurementSet().then(function () {
+                assert.equal(requests.length, 2);
+                assert.equal(requests[1].url, '../data/measurement-set-1-1-3000.json');
+                requests[1].resolve({
+                    'clusterStart': 1000,
+                    'clusterSize': 1000,
+                    'formatMap': [],
+                    'configurations': {current: []},
+                    'startTime': 2000,
+                    'endTime': 3000,
+                    'lastModified': 5000,
+                    'clusterCount': 2,
+                    'status': 'OK'});                
+            }).then(function () {
+                assert(set.hasFetchedRange(2500, 3500));
+                done();
+            }).catch(function (error) {
+                done(error);
+            });
+        });
+
+        it('should return false when there is a cluster missing in the range', function (done) {
+            var set = MeasurementSet.findSet(1, 1, 5000);
+            var promise = set.fetchBetween(2000, 5000);
+            assert.equal(requests.length, 1);
+            assert.equal(requests[0].url, '../data/measurement-set-1-1.json');
+
+            requests[0].resolve({
+                'clusterStart': 1000,
+                'clusterSize': 1000,
+                'formatMap': [],
+                'configurations': {current: []},
+                'startTime': 4000,
+                'endTime': 5000,
+                'lastModified': 5000,
+                'clusterCount': 2,
+                'status': 'OK'});
+
+            waitForMeasurementSet().then(function () {
+                assert.equal(requests.length, 3);
+                assert.equal(requests[1].url, '../data/measurement-set-1-1-3000.json');
+                assert.equal(requests[2].url, '../data/measurement-set-1-1-4000.json');
+                requests[1].resolve({
+                    'clusterStart': 1000,
+                    'clusterSize': 1000,
+                    'formatMap': [],
+                    'configurations': {current: []},
+                    'startTime': 2000,
+                    'endTime': 3000,
+                    'lastModified': 5000,
+                    'clusterCount': 2,
+                    'status': 'OK'});
+            }).then(function () {
+                assert(!set.hasFetchedRange(2500, 4500));
+                assert(set.hasFetchedRange(2100, 2300));
+                assert(set.hasFetchedRange(4000, 4800));
+                done();
+            }).catch(function (error) {
+                done(error);
+            });
+        });
+
+    });
+
</ins><span class="cx"> });
</span></span></pre>
</div>
</div>

</body>
</html>