Index: chrome/browser/resources/tracing/timeline_track.js |
diff --git a/chrome/browser/resources/tracing/timeline_track.js b/chrome/browser/resources/tracing/timeline_track.js |
index 206dc976e6f43489d200cc5547221de47b4a5778..e8f22545c1c37a53c03a8797cf20c90665c9d11a 100644 |
--- a/chrome/browser/resources/tracing/timeline_track.js |
+++ b/chrome/browser/resources/tracing/timeline_track.js |
@@ -66,64 +66,32 @@ cr.define('tracing', function() { |
return w; |
} |
- function addTrack(thisTrack, slices) { |
- var track = new TimelineSliceTrack(); |
- |
- track.heading = ''; |
- track.slices = slices; |
- track.viewport = thisTrack.viewport_; |
- |
- thisTrack.tracks_.push(track); |
- thisTrack.appendChild(track); |
- } |
- |
/** |
- * Generic base class for timeline tracks |
+ * A generic track that contains other tracks as its children. |
+ * @constructor |
*/ |
- TimelineThreadTrack = cr.ui.define('div'); |
- TimelineThreadTrack.prototype = { |
+ var TimelineContainerTrack = cr.ui.define('div'); |
+ TimelineContainerTrack.prototype = { |
__proto__: HTMLDivElement.prototype, |
decorate: function() { |
- this.className = 'timeline-thread-track'; |
- }, |
- |
- set thread(thread) { |
- this.thread_ = thread; |
- this.updateChildTracks_(); |
+ this.tracks_ = []; |
}, |
- /** |
- * @return {string} A human-readable name for the track. |
- */ |
- get heading() { |
- if (!this.thread_) |
- return ''; |
- var tname = this.thread_.name || this.thread_.tid; |
- return this.thread_.parent.pid + ': ' + |
- tname + ':'; |
+ detach: function() { |
+ for (var i = 0; i < this.tracks_.length; i++) |
+ this.tracks_[i].detach(); |
}, |
- set headingWidth(width) { |
- for (var i = 0; i < this.tracks_.length; i++) |
- this.tracks_[i].headingWidth = width; |
+ get viewport() { |
+ return this.viewport_; |
}, |
set viewport(v) { |
this.viewport_ = v; |
for (var i = 0; i < this.tracks_.length; i++) |
this.tracks_[i].viewport = v; |
- this.invalidate(); |
- }, |
- |
- invalidate: function() { |
- if (this.parentNode) |
- this.parentNode.invalidate(); |
- }, |
- |
- onResize: function() { |
- for (var i = 0; i < this.tracks_.length; i++) |
- this.tracks_[i].onResize(); |
+ this.updateChildTracks_(); |
}, |
get firstCanvas() { |
@@ -132,30 +100,6 @@ cr.define('tracing', function() { |
return undefined; |
}, |
- redraw: function() { |
- for (var i = 0; i < this.tracks_.length; i++) |
- this.tracks_[i].redraw(); |
- }, |
- |
- updateChildTracks_: function() { |
- this.textContent = ''; |
- this.tracks_ = []; |
- if (this.thread_) { |
- for (var srI = 0; srI < this.thread_.nonNestedSubRows.length; ++srI) { |
- addTrack(this, this.thread_.nonNestedSubRows[srI]); |
- } |
- for (var srI = 0; srI < this.thread_.subRows.length; ++srI) { |
- addTrack(this, this.thread_.subRows[srI]); |
- } |
- if (this.tracks_.length > 0) { |
- this.tracks_[0].heading = this.heading; |
- this.tracks_[0].tooltip = 'pid: ' + this.thread_.parent.pid + |
- ', tid: ' + this.thread_.tid + |
- (this.thread_.name ? ', name: ' + this.thread_.name : ''); |
- } |
- } |
- }, |
- |
/** |
* Picks a slice, if any, at a given location. |
* @param {number} wX X location to search at, in worldspace. |
@@ -199,33 +143,120 @@ cr.define('tracing', function() { |
}; |
/** |
- * Creates a new timeline track div element |
+ * Visualizes a TimelineThread using a series of of TimelineSliceTracks. |
+ * @constructor |
+ */ |
+ var TimelineThreadTrack = cr.ui.define(TimelineContainerTrack); |
+ TimelineThreadTrack.prototype = { |
+ __proto__: TimelineContainerTrack.prototype, |
+ |
+ decorate: function() { |
+ this.classList.add('timeline-thread-track'); |
+ }, |
+ |
+ get thread(thread) { |
+ return this.thread_; |
+ }, |
+ |
+ set thread(thread) { |
+ this.thread_ = thread; |
+ this.updateChildTracks_(); |
+ }, |
+ |
+ get tooltip() { |
+ return this.tooltip_; |
+ }, |
+ |
+ set tooltip(value) { |
+ this.tooltip_ = value; |
+ this.updateChildTracks_(); |
+ }, |
+ |
+ get heading() { |
+ return this.heading_; |
+ }, |
+ |
+ set heading(h) { |
+ this.heading_ = h; |
+ this.updateChildTracks_(); |
+ }, |
+ |
+ get headingWidth() { |
+ return this.headingWidth_; |
+ }, |
+ |
+ set headingWidth(width) { |
+ this.headingWidth_ = width; |
+ this.updateChildTracks_(); |
+ }, |
+ |
+ addTrack_: function(slices) { |
+ var track = new TimelineSliceTrack(); |
+ track.heading = ''; |
+ track.slices = slices; |
+ track.headingWidth = this.headingWidth_; |
+ track.viewport = this.viewport_; |
+ |
+ this.tracks_.push(track); |
+ this.appendChild(track); |
+ }, |
+ |
+ updateChildTracks_: function() { |
+ this.detach(); |
+ this.textContent = ''; |
+ this.tracks_ = []; |
+ if (this.thread_) { |
+ for (var srI = 0; srI < this.thread_.nonNestedSubRows.length; ++srI) { |
+ this.addTrack_(this.thread_.nonNestedSubRows[srI]); |
+ } |
+ for (var srI = 0; srI < this.thread_.subRows.length; ++srI) { |
+ this.addTrack_(this.thread_.subRows[srI]); |
+ } |
+ if (this.tracks_.length > 0) { |
+ this.tracks_[0].heading = this.heading_; |
+ this.tracks_[0].tooltip = this.tooltip_; |
+ } |
+ } |
+ } |
+ }; |
+ |
+ /** |
+ * A canvas-based track constructed. Provides the basic heading and |
+ * invalidation-managment infrastructure. Subclasses must implement drawing |
+ * and picking code. |
* @constructor |
* @extends {HTMLDivElement} |
*/ |
- TimelineSliceTrack = cr.ui.define('div'); |
+ var CanvasBasedTrack = cr.ui.define('div'); |
- TimelineSliceTrack.prototype = { |
+ CanvasBasedTrack.prototype = { |
__proto__: HTMLDivElement.prototype, |
decorate: function() { |
- this.className = 'timeline-slice-track'; |
+ this.className = 'timeline-canvas-based-track'; |
this.slices_ = null; |
this.headingDiv_ = document.createElement('div'); |
- this.headingDiv_.className = 'timeline-slice-track-title'; |
+ this.headingDiv_.className = 'timeline-canvas-based-track-title'; |
this.appendChild(this.headingDiv_); |
this.canvasContainer_ = document.createElement('div'); |
- this.canvasContainer_.className = 'timeline-slice-track-canvas-container'; |
+ this.canvasContainer_.className = |
+ 'timeline-canvas-based-track-canvas-container'; |
this.appendChild(this.canvasContainer_); |
this.canvas_ = document.createElement('canvas'); |
- this.canvas_.className = 'timeline-slice-track-canvas'; |
+ this.canvas_.className = 'timeline-canvas-based-track-canvas'; |
this.canvasContainer_.appendChild(this.canvas_); |
this.ctx_ = this.canvas_.getContext('2d'); |
}, |
+ detach: function() { |
+ if (this.viewport_) |
+ this.viewport_.removeEventListener('change', |
+ this.viewportChangeBoundToThis_); |
+ }, |
+ |
set headingWidth(width) { |
this.headingDiv_.style.width = width; |
}, |
@@ -242,41 +273,85 @@ cr.define('tracing', function() { |
this.headingDiv_.title = text; |
}, |
- set slices(slices) { |
- this.slices_ = slices; |
- this.invalidate(); |
+ get viewport() { |
+ return this.viewport_; |
}, |
set viewport(v) { |
this.viewport_ = v; |
+ if (this.viewport_) |
+ this.viewport_.removeEventListener('change', |
+ this.viewportChangeBoundToThis_); |
+ this.viewport_ = v; |
+ if (this.viewport_) { |
+ this.viewportChangeBoundToThis_ = this.viewportChange_.bind(this); |
+ this.viewport_.addEventListener('change', |
+ this.viewportChangeBoundToThis_); |
+ } |
+ this.invalidate(); |
+ }, |
+ |
+ viewportChange_: function() { |
this.invalidate(); |
}, |
invalidate: function() { |
- if (this.parentNode) |
- this.parentNode.invalidate(); |
+ if (this.rafPending_) |
+ return; |
+ webkitRequestAnimationFrame(function() { |
+ this.rafPending_ = false; |
+ if (!this.viewport_) |
+ return; |
+ |
+ if (this.canvas_.width != this.canvasContainer_.clientWidth) |
+ this.canvas_.width = this.canvasContainer_.clientWidth; |
+ if (this.canvas_.height != this.canvasContainer_.clientHeight) |
+ this.canvas_.height = this.canvasContainer_.clientHeight; |
+ |
+ this.redraw(); |
+ }.bind(this), this); |
+ this.rafPending_ = true; |
}, |
get firstCanvas() { |
return this.canvas_; |
+ } |
+ |
+ }; |
+ |
+ /** |
+ * A track that displays an array of TimelineSlice objects. |
+ * @constructor |
+ * @extends {CanvasBasedTrack} |
+ */ |
+ |
+ var TimelineSliceTrack = cr.ui.define(CanvasBasedTrack); |
+ |
+ TimelineSliceTrack.prototype = { |
+ |
+ __proto__: CanvasBasedTrack.prototype, |
+ |
+ decorate: function() { |
+ this.classList.add('timeline-slice-track'); |
+ }, |
+ |
+ get slices() { |
+ return this.slices_; |
}, |
- onResize: function() { |
- this.canvas_.width = this.canvasContainer_.clientWidth; |
- this.canvas_.height = this.canvasContainer_.clientHeight; |
+ set slices(slices) { |
+ this.slices_ = slices; |
this.invalidate(); |
}, |
redraw: function() { |
- if (!this.viewport_) |
- return; |
var ctx = this.ctx_; |
var canvasW = this.canvas_.width; |
var canvasH = this.canvas_.height; |
ctx.clearRect(0, 0, canvasW, canvasH); |
- // culling... |
+ // Culling parameters. |
var vp = this.viewport_; |
var pixWidth = vp.xViewVectorToWorld(1); |
var viewLWorld = vp.xViewToWorld(0); |
@@ -301,11 +376,11 @@ cr.define('tracing', function() { |
ctx.stroke(); |
} |
- // begin rendering in world space |
+ // Begin rendering in world space. |
ctx.save(); |
vp.applyTransformToCanavs(ctx); |
- // tracks |
+ // Slices. |
var tr = new tracing.FastRectRenderer(ctx, viewLWorld, 2 * pixWidth, |
2 * pixWidth, viewRWorld, pallette); |
tr.setYandH(0, canvasH); |
@@ -343,7 +418,7 @@ cr.define('tracing', function() { |
tr.flush(); |
ctx.restore(); |
- // labels |
+ // Labels. |
ctx.textAlign = 'center'; |
ctx.textBaseline = 'top'; |
ctx.font = '10px sans-serif'; |
@@ -473,7 +548,153 @@ cr.define('tracing', function() { |
}; |
+ /** |
+ * A track that displays a TimelineCounter object. |
+ * @constructor |
+ * @extends {CanvasBasedTrack} |
+ */ |
+ |
+ var TimelineCounterTrack = cr.ui.define(CanvasBasedTrack); |
+ |
+ TimelineCounterTrack.prototype = { |
+ |
+ __proto__: CanvasBasedTrack.prototype, |
+ |
+ decorate: function() { |
+ this.classList.add('timeline-counter-track'); |
+ }, |
+ |
+ get counter() { |
+ return this.counter_; |
+ }, |
+ |
+ set counter(counter) { |
+ this.counter_ = counter; |
+ this.invalidate(); |
+ }, |
+ |
+ redraw: function() { |
+ var ctr = this.counter_; |
+ var ctx = this.ctx_; |
+ var canvasW = this.canvas_.width; |
+ var canvasH = this.canvas_.height; |
+ |
+ ctx.clearRect(0, 0, canvasW, canvasH); |
+ |
+ // Culling parametrs. |
+ var vp = this.viewport_; |
+ var pixWidth = vp.xViewVectorToWorld(1); |
+ var viewLWorld = vp.xViewToWorld(0); |
+ var viewRWorld = vp.xViewToWorld(canvasW); |
+ |
+ // Drop sampels that are less than skipDistancePix apart. |
+ var skipDistancePix = 16; |
+ var skipDistanceWorld = vp.xViewVectorToWorld(skipDistancePix); |
+ |
+ // Begin rendering in world space. |
+ ctx.save(); |
+ vp.applyTransformToCanavs(ctx); |
+ |
+ // Figure out where drawing should begin. |
+ var numSeries = ctr.numSeries; |
+ var numSamples = ctr.numSamples; |
+ var startIndex = tracing.findLowIndexInSortedArray(ctr.timestamps, |
+ function() { |
+ }, |
+ viewLWorld); |
+ |
+ // Draw indices one by one until we fall off the viewRWorld. |
+ var yScale = canvasH / ctr.maxTotal; |
+ for (var seriesIndex = ctr.numSeries - 1; |
+ seriesIndex >= 0; seriesIndex--) { |
+ var colorId = ctr.seriesColors[seriesIndex]; |
+ ctx.fillStyle = pallette[colorId]; |
+ ctx.beginPath(); |
+ |
+ // Set iLast and xLast such that the first sample we draw is the |
+ // startIndex sample. |
+ var iLast = startIndex - 1; |
+ var xLast = iLast >= 0 ? ctr.timestamps[iLast] - skipDistanceWorld : -1; |
+ var yLastView = canvasH; |
+ |
+ // Iterate over samples from iLast onward until we either fall off the |
+ // viewRWorld or we run out of samples. To avoid drawing too much, after |
+ // drawing a sample at xLast, skip subsequent samples that are less than |
+ // skipDistanceWorld from xLast. |
+ var hasMoved = false; |
+ while (true) { |
+ var i = iLast + 1; |
+ if (i >= numSamples) { |
+ ctx.lineTo(xLast, yLastView); |
+ ctx.lineTo(xLast + 8 * pixWidth, yLastView); |
+ ctx.lineTo(xLast + 8 * pixWidth, canvasH); |
+ break; |
+ } |
+ |
+ var x = ctr.timestamps[i]; |
+ |
+ var y = ctr.totals[i * numSeries + seriesIndex]; |
+ var yView = canvasH - (yScale * y); |
+ |
+ if (x > viewRWorld) { |
+ ctx.lineTo(x, yLastView); |
+ ctx.lineTo(x, canvasH); |
+ break; |
+ } |
+ |
+ if (x - xLast < skipDistanceWorld) { |
+ iLast = i; |
+ continue; |
+ } |
+ |
+ if (!hasMoved) { |
+ ctx.moveTo(viewLWorld, canvasH); |
+ hasMoved = true; |
+ } |
+ ctx.lineTo(x, yLastView); |
+ ctx.lineTo(x, yView); |
+ iLast = i; |
+ xLast = x; |
+ yLastView = yView; |
+ } |
+ ctx.closePath(); |
+ ctx.fill(); |
+ } |
+ ctx.restore(); |
+ }, |
+ |
+ /** |
+ * Picks a slice, if any, at a given location. |
+ * @param {number} wX X location to search at, in worldspace. |
+ * @param {number} wY Y location to search at, in offset space. |
+ * offset space. |
+ * @param {function():*} onHitCallback Callback to call with the slice, |
+ * if one is found. |
+ * @return {boolean} true if a slice was found, otherwise false. |
+ */ |
+ pick: function(wX, wY, onHitCallback) { |
+ }, |
+ |
+ /** |
+ * Finds slices intersecting the given interval. |
+ * @param {number} loWX Lower X bound of the interval to search, in |
+ * worldspace. |
+ * @param {number} hiWX Upper X bound of the interval to search, in |
+ * worldspace. |
+ * @param {number} loY Lower Y bound of the interval to search, in |
+ * offset space. |
+ * @param {number} hiY Upper Y bound of the interval to search, in |
+ * offset space. |
+ * @param {function():*} onHitCallback Function to call for each slice |
+ * intersecting the interval. |
+ */ |
+ pickRange: function(loWX, hiWX, loY, hiY, onHitCallback) { |
+ } |
+ |
+ }; |
+ |
return { |
+ TimelineCounterTrack: TimelineCounterTrack, |
TimelineSliceTrack: TimelineSliceTrack, |
TimelineThreadTrack: TimelineThreadTrack |
}; |