Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(202)

Side by Side Diff: Source/devtools/front_end/timeline/TimelineFrameOverview.js

Issue 1354833002: DevTools: Nuke timeline frame overview (Closed) Base URL: svn://svn.chromium.org/blink/trunk
Patch Set: Created 5 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « Source/devtools/devtools.gypi ('k') | Source/devtools/front_end/timeline/TimelinePanel.js » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 /*
2 * Copyright (C) 2013 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31 /**
32 * @constructor
33 * @extends {WebInspector.TimelineOverviewBase}
34 * @param {!WebInspector.TimelineModel} model
35 * @param {!WebInspector.TimelineFrameModelBase} frameModel
36 */
37 WebInspector.TimelineFrameOverview = function(model, frameModel)
38 {
39 WebInspector.TimelineOverviewBase.call(this);
40 this.element.id = "timeline-overview-frames";
41 this._model = model;
42 this._frameModel = frameModel;
43 this.reset();
44
45 this._outerPadding = 4 * window.devicePixelRatio;
46 this._maxInnerBarWidth = 10 * window.devicePixelRatio;
47 this._topPadding = 6 * window.devicePixelRatio;
48
49 // The below two are really computed by update() -- but let's have something so that windowTimes() is happy.
50 this._actualPadding = 5 * window.devicePixelRatio;
51 this._actualOuterBarWidth = this._maxInnerBarWidth + this._actualPadding;
52
53 this._fillStyles = {};
54 var categories = WebInspector.TimelineUIUtils.categories();
55 for (var category in categories)
56 this._fillStyles[category] = WebInspector.TimelineUIUtils.createFillStyl eForCategory(this._context, this._maxInnerBarWidth, 0, categories[category]);
57
58 this._frameTopShadeGradient = this._context.createLinearGradient(0, 0, 0, th is._topPadding);
59 this._frameTopShadeGradient.addColorStop(0, "rgba(255, 255, 255, 0.9)");
60 this._frameTopShadeGradient.addColorStop(1, "rgba(255, 255, 255, 0.2)");
61
62 this.element.addEventListener("mousemove", this._onMouseMove.bind(this), fal se);
63 this.element.addEventListener("mouseout", this._onMouseOut.bind(this), false );
64 }
65
66 WebInspector.TimelineFrameOverview.Events = {
67 SelectionChanged: "SelectionChanged"
68 }
69
70 WebInspector.TimelineFrameOverview.prototype = {
71 /**
72 * @override
73 */
74 reset: function()
75 {
76 /** @type {!Array<!{startTime:number, endTime:number}>} */
77 this._barTimes = [];
78 /** @type {!Array<!WebInspector.TimelineFrame>} */
79 this._visibleFrames = [];
80 this._selectedBarIndex = null;
81 this._activeBarIndex = null;
82 },
83
84 /**
85 * @override
86 */
87 update: function()
88 {
89 this.resetCanvas();
90 this._barTimes = [];
91
92 var minBarWidth = 4 * window.devicePixelRatio;
93 var frames = this._frameModel.frames();
94 var framesPerBar = Math.max(1, frames.length * minBarWidth / this._canva s.width);
95 this._visibleFrames = this._aggregateFrames(frames, framesPerBar);
96 this._scale = (this._canvas.height - this._topPadding) / this._computeTa rgetFrameLength(this._visibleFrames);
97 var maxPadding = 5 * window.devicePixelRatio;
98 this._actualOuterBarWidth = Math.min((this._canvas.width - 2 * this._out erPadding) / this._visibleFrames.length, this._maxInnerBarWidth + maxPadding);
99 this._actualPadding = Math.min(Math.floor(this._actualOuterBarWidth / 3) , maxPadding);
100
101 this._context.save();
102 for (var i = this._visibleFrames.length - 1; i >= 0; --i)
103 this._drawBar(i);
104 this._drawTopShadeGradient();
105 this._drawFPSMarks();
106 this._drawSelection();
107 this._context.restore();
108 },
109
110 /**
111 * @param {?WebInspector.TimelineSelection} selection
112 */
113 select: function(selection)
114 {
115 var oldSelectionIndex = this._selectedBarIndex;
116 var frame = selection && selection.type() === WebInspector.TimelineSelec tion.Type.Frame ? /** @type {!WebInspector.TimelineFrame} */ (selection.object() ) : null;
117 var index = frame ? this._visibleFrames.indexOf(frame) : -1;
118 this._selectedBarIndex = index >= 0 ? index : null;
119 if (this._selectedBarIndex === oldSelectionIndex)
120 return;
121 if (typeof oldSelectionIndex === "number")
122 this._redrawBar(oldSelectionIndex);
123 this._drawSelection();
124 },
125
126 /**
127 * @override
128 * @param {!Event} event
129 * @return {boolean}
130 */
131 onClick: function(event)
132 {
133 var barIndex = this._screenPositionToBarIndex(event.clientX);
134 if (barIndex < 0 || barIndex >= this._visibleFrames.length)
135 return false;
136 var selection = WebInspector.TimelineSelection.fromFrame(this._visibleFr ames[barIndex]);
137 this.dispatchEventToListeners(WebInspector.TimelineFrameOverview.Events. SelectionChanged, selection);
138 return true;
139 },
140
141 /**
142 * @param {!Event} event
143 */
144 _onMouseMove: function(event)
145 {
146 var barIndex = this._screenPositionToBarIndex(event.clientX);
147 if (barIndex < 0 || barIndex >= this._visibleFrames.length)
148 barIndex = null;
149 this._setActiveBarIndex(barIndex);
150 },
151
152 /**
153 * @param {!Event} event
154 */
155 _onMouseOut: function(event)
156 {
157 this._setActiveBarIndex(null);
158 },
159
160 /**
161 * @param {?number} index
162 */
163 _setActiveBarIndex: function(index)
164 {
165 if (this._activeBarIndex === index)
166 return;
167 var oldActveBarIndex = this._activeBarIndex;
168 this._activeBarIndex = index;
169 if (typeof oldActveBarIndex === "number")
170 this._redrawBar(oldActveBarIndex);
171 if (typeof this._activeBarIndex === "number")
172 this._redrawBar(this._activeBarIndex);
173 },
174
175 /**
176 * @param {number} index
177 */
178 _redrawBar: function(index)
179 {
180 this._context.save();
181 this._context.beginPath();
182 var left = this._barIndexToScreenPosition(index) - this._actualPadding;
183 var right = Math.ceil(left + this._actualOuterBarWidth);
184 this._context.rect(left, 0, right - left + 1, this._canvas.height);
185 this._context.fillStyle = "rgb(255, 255, 255)";
186 this._context.clip();
187 this._context.fill();
188 if (index > 0)
189 this._drawBar(index - 1);
190 if (index + 1 < this._visibleFrames.length)
191 this._drawBar(index + 1);
192 this._drawBar(index);
193 this._drawTopShadeGradient();
194 this._drawFPSMarks();
195 if (typeof this._selectedBarIndex === "number")
196 this._drawSelection();
197 this._context.restore();
198 },
199
200 /**
201 * @param {!Array.<!WebInspector.TimelineFrame>} frames
202 * @param {number} framesPerBar
203 * @return {!Array.<!WebInspector.TimelineFrame>}
204 */
205 _aggregateFrames: function(frames, framesPerBar)
206 {
207 var visibleFrames = [];
208 for (var barIndex = 0, currentFrame = 0; currentFrame < frames.length; + +barIndex) {
209 var barStartTime = frames[currentFrame].startTime;
210 var longestFrame = null;
211 var longestDuration;
212
213 for (var lastFrame = Math.min(Math.floor((barIndex + 1) * framesPerB ar), frames.length);
214 currentFrame < lastFrame; ++currentFrame) {
215 var frame = frames[currentFrame];
216 var duration = frame.idle ? 0 : frame.duration; // Only consider idle frames if there are no regular frames.
217 if (!longestFrame || longestDuration < duration) {
218 longestFrame = frame;
219 longestDuration = duration;
220 }
221 }
222 var barEndTime = frames[currentFrame - 1].endTime;
223 if (longestFrame) {
224 visibleFrames.push(longestFrame);
225 this._barTimes.push({ startTime: barStartTime, endTime: barEndTi me });
226 }
227 }
228 return visibleFrames;
229 },
230
231 /**
232 * @param {!Array.<!WebInspector.TimelineFrame>} frames
233 * @return {number}
234 */
235 _computeTargetFrameLength: function(frames)
236 {
237 var targetFPS = 20;
238 var result = 1000.0 / targetFPS;
239 if (!frames.length)
240 return result;
241
242 var durations = frames.select("duration");
243 var medianFrameLength = durations.qselect(Math.floor(durations.length / 2));
244
245 // Optimize appearance for 30fps, but leave some space so it's evident w hen a frame overflows.
246 // However, if at least half frames won't fit at this scale, fall back t o using autoscale.
247 if (result >= medianFrameLength)
248 return result;
249
250 var maxFrameLength = Math.max.apply(Math, durations);
251 return Math.min(medianFrameLength * 2, maxFrameLength);
252 },
253
254 /**
255 * @param {number} n
256 */
257 _barIndexToScreenPosition: function(n)
258 {
259 return this._outerPadding + this._actualOuterBarWidth * n;
260 },
261
262 /**
263 * @param {number} clientX
264 */
265 _screenPositionToBarIndex: function(clientX)
266 {
267 var x = (clientX - this.element.totalOffsetLeft()) * window.devicePixelR atio;
268 return Math.floor((x - this._outerPadding) / this._actualOuterBarWidth);
269 },
270
271 _drawTopShadeGradient: function()
272 {
273 this._context.fillStyle = this._frameTopShadeGradient;
274 this._context.fillRect(0, 0, this._canvas.width, this._topPadding);
275 },
276
277 _drawFPSMarks: function()
278 {
279 var fpsMarks = [30, 60];
280
281 this._context.save();
282 this._context.beginPath();
283 this._context.font = (10 * window.devicePixelRatio) + "px " + window.get ComputedStyle(this.element, null).getPropertyValue("font-family");
284 this._context.textAlign = "right";
285 this._context.textBaseline = "alphabetic";
286
287 var labelPadding = 4 * window.devicePixelRatio;
288 var baselineHeight = 3 * window.devicePixelRatio;
289 var lineHeight = 12 * window.devicePixelRatio;
290 var labelTopMargin = 0;
291 var labelOffsetY = 0; // Labels are going to be under their grid lines.
292
293 for (var i = 0; i < fpsMarks.length; ++i) {
294 var fps = fpsMarks[i];
295 // Draw lines one pixel above they need to be, so 60pfs line does no t cross most of the frames tops.
296 var y = this._canvas.height - Math.floor(1000.0 / fps * this._scale) - 0.5;
297 var label = WebInspector.UIString("%d\u2009fps", fps);
298 var labelWidth = this._context.measureText(label).width + 2 * labelP adding;
299 var labelX = this._canvas.width;
300
301 if (!i && labelTopMargin < y - lineHeight)
302 labelOffsetY = -lineHeight; // Labels are going to be over their grid lines.
303 var labelY = y + labelOffsetY;
304 if (labelY < labelTopMargin || labelY + lineHeight > this._canvas.he ight)
305 break; // No space for the label, so no line as well.
306
307 this._context.moveTo(0, y);
308 this._context.lineTo(this._canvas.width, y);
309
310 this._context.fillStyle = "rgba(255, 255, 255, 0.5)";
311 this._context.fillRect(labelX - labelWidth, labelY, labelWidth, line Height);
312 this._context.fillStyle = "black";
313 this._context.fillText(label, labelX - labelPadding, labelY + lineHe ight - baselineHeight);
314 labelTopMargin = labelY + lineHeight;
315 }
316 this._context.strokeStyle = "rgba(60, 60, 60, 0.4)";
317 this._context.stroke();
318 this._context.restore();
319 },
320
321 /**
322 * @param {number} index
323 */
324 _drawBar: function(index)
325 {
326 var left = this._barIndexToScreenPosition(index);
327 var frame = this._visibleFrames[index];
328 var categories = Object.keys(WebInspector.TimelineUIUtils.categories());
329 var windowHeight = this._canvas.height;
330 var width = Math.floor(this._actualOuterBarWidth - this._actualPadding);
331
332 var x = Math.floor(left) + 0.5;
333
334 var totalCPUTime = frame.cpuTime;
335 var normalizedScale = this._scale;
336 if (totalCPUTime > frame.duration)
337 normalizedScale *= frame.duration / totalCPUTime;
338
339 for (var i = 0, bottomOffset = windowHeight; i < categories.length; ++i) {
340 var category = categories[i];
341 var duration = frame.timeByCategory[category];
342 if (!duration)
343 continue;
344 var height = Math.round(duration * normalizedScale);
345 var y = Math.floor(bottomOffset - height) + 0.5;
346
347 this._context.save();
348 this._context.translate(x, 0);
349 this._context.scale(width / this._maxInnerBarWidth, 1);
350 this._context.fillStyle = this._fillStyles[category];
351 this._context.fillRect(0, y, this._maxInnerBarWidth, Math.floor(heig ht));
352 this._context.strokeStyle = WebInspector.TimelineUIUtils.categories( )[category].borderColor;
353 this._context.beginPath();
354 this._context.moveTo(0, y);
355 this._context.lineTo(this._maxInnerBarWidth, y);
356 this._context.stroke();
357 this._context.restore();
358
359 bottomOffset -= height;
360 }
361 // Skip outline for idle frames, unless frame is selected.
362 if (frame.idle && index !== this._activeBarIndex)
363 return;
364
365 // Draw a contour for the total frame time.
366 var y0 = frame.idle ? bottomOffset + 0.5 : Math.floor(windowHeight - fra me.duration * this._scale) + 0.5;
367 var y1 = windowHeight + 0.5;
368
369 this._context.strokeStyle = index === this._activeBarIndex ? "rgba(0, 0, 0, 0.6)" : "rgba(90, 90, 90, 0.2)";
370 this._context.beginPath();
371 this._context.moveTo(x, y1);
372 this._context.lineTo(x, y0);
373 this._context.lineTo(x + width, y0);
374 this._context.lineTo(x + width, y1);
375 this._context.stroke();
376 },
377
378 _drawSelection: function()
379 {
380 if (typeof this._selectedBarIndex !== "number")
381 return;
382 var left = this._barIndexToScreenPosition(this._selectedBarIndex);
383 var width = Math.floor(this._actualOuterBarWidth - this._actualPadding);
384 var triangleHeight = 4 * window.devicePixelRatio;
385 this._context.save();
386 this._context.beginPath();
387 this._context.moveTo(left, 0);
388 this._context.lineTo(left + width, 0);
389 this._context.lineTo(left + width / 2, triangleHeight);
390 this._context.closePath();
391 this._context.fillStyle = "black";
392 this._context.fill();
393 this._context.restore();
394 },
395
396 /**
397 * @override
398 * @param {number} windowLeft
399 * @param {number} windowRight
400 * @return {!{startTime: number, endTime: number}}
401 */
402 windowTimes: function(windowLeft, windowRight)
403 {
404 if (!this._barTimes.length)
405 return WebInspector.TimelineOverviewBase.prototype.windowTimes.call( this, windowLeft, windowRight);
406 var windowSpan = this._canvas.width;
407 var leftOffset = windowLeft * windowSpan;
408 var rightOffset = windowRight * windowSpan;
409 var firstBar = Math.floor(Math.max(leftOffset - this._outerPadding + thi s._actualPadding, 0) / this._actualOuterBarWidth);
410 var lastBar = Math.min(Math.floor(Math.max(rightOffset - this._outerPadd ing, 0)/ this._actualOuterBarWidth), this._barTimes.length - 1);
411 if (firstBar >= this._barTimes.length)
412 return {startTime: Infinity, endTime: Infinity};
413
414 var snapTolerancePixels = 3;
415 return {
416 startTime: leftOffset > snapTolerancePixels ? this._barTimes[firstBa r].startTime : this._model.minimumRecordTime(),
417 endTime: (rightOffset + snapTolerancePixels > windowSpan) || (lastBa r >= this._barTimes.length) ? this._model.maximumRecordTime() : this._barTimes[l astBar].endTime
418 };
419 },
420
421 /**
422 * @override
423 * @param {number} startTime
424 * @param {number} endTime
425 * @return {!{left: number, right: number}}
426 */
427 windowBoundaries: function(startTime, endTime)
428 {
429 if (this._barTimes.length === 0)
430 return {left: 0, right: 1};
431 /**
432 * @param {number} time
433 * @param {!{startTime:number, endTime:number}} barTime
434 * @return {number}
435 */
436 function barStartComparator(time, barTime)
437 {
438 return time - barTime.startTime;
439 }
440 /**
441 * @param {number} time
442 * @param {!{startTime:number, endTime:number}} barTime
443 * @return {number}
444 */
445 function barEndComparator(time, barTime)
446 {
447 // We need a frame where time is in [barTime.startTime, barTime.endT ime), so exclude exact matches against endTime.
448 if (time === barTime.endTime)
449 return 1;
450 return time - barTime.endTime;
451 }
452 return {
453 left: this._windowBoundaryFromTime(startTime, barEndComparator),
454 right: this._windowBoundaryFromTime(endTime, barStartComparator)
455 };
456 },
457
458 /**
459 * @param {number} time
460 * @param {function(number, !{startTime:number, endTime:number}):number} com parator
461 */
462 _windowBoundaryFromTime: function(time, comparator)
463 {
464 if (time === Infinity)
465 return 1;
466 var index = this._firstBarAfter(time, comparator);
467 if (!index)
468 return 0;
469 return (this._barIndexToScreenPosition(index) - this._actualPadding / 2) / this._canvas.width;
470 },
471
472 /**
473 * @param {number} time
474 * @param {function(number, {startTime:number, endTime:number}):number} comp arator
475 */
476 _firstBarAfter: function(time, comparator)
477 {
478 return insertionIndexForObjectInListSortedByFunction(time, this._barTime s, comparator);
479 },
480
481 __proto__: WebInspector.TimelineOverviewBase.prototype
482 }
OLDNEW
« no previous file with comments | « Source/devtools/devtools.gypi ('k') | Source/devtools/front_end/timeline/TimelinePanel.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698