Index: Source/devtools/front_end/timeline/TracingTimelineModel.js |
diff --git a/Source/devtools/front_end/timeline/TracingTimelineModel.js b/Source/devtools/front_end/timeline/TracingTimelineModel.js |
index 4ad7f03b0e177afaeb7fac52c2ba97ce2d4936c1..5b4fed00229e28680bc6855ac7541773c2117e1e 100644 |
--- a/Source/devtools/front_end/timeline/TracingTimelineModel.js |
+++ b/Source/devtools/front_end/timeline/TracingTimelineModel.js |
@@ -32,6 +32,9 @@ WebInspector.TracingTimelineModel.RecordType = { |
DrawFrame: "DrawFrame", |
ScheduleStyleRecalculation: "ScheduleStyleRecalculation", |
RecalculateStyles: "RecalculateStyles", |
+ StyleInvalidationTracking: "StyleInvalidationTracking", |
+ LayoutInvalidationTracking: "LayoutInvalidationTracking", |
+ PaintInvalidationTracking: "PaintInvalidationTracking", |
InvalidateLayout: "InvalidateLayout", |
Layout: "Layout", |
UpdateLayer: "UpdateLayer", |
@@ -115,8 +118,9 @@ WebInspector.TracingTimelineModel.prototype = { |
* @param {boolean} captureStacks |
* @param {boolean} captureMemory |
* @param {boolean} capturePictures |
+ * @param {boolean} captureInvalidationTracking |
*/ |
- startRecording: function(captureStacks, captureMemory, capturePictures) |
+ startRecording: function(captureStacks, captureMemory, capturePictures, captureInvalidationTracking) |
{ |
function disabledByDefault(category) |
{ |
@@ -143,6 +147,13 @@ WebInspector.TracingTimelineModel.prototype = { |
disabledByDefault("devtools.timeline.picture"), |
disabledByDefault("blink.graphics_context_annotations")]); |
} |
+ if (captureInvalidationTracking) { |
+ categoriesArray = categoriesArray.concat([ |
+ disabledByDefault("devtools.timeline.styleInvalidationTracking"), |
+ disabledByDefault("devtools.timeline.layoutInvalidationTracking"), |
+ disabledByDefault("devtools.timeline.paintInvalidationTracking") |
+ ]); |
+ } |
var categories = categoriesArray.join(","); |
this._startRecordingWithCategories(categories); |
}, |
@@ -378,6 +389,7 @@ WebInspector.TracingTimelineModel.prototype = { |
this._sendRequestEvents = {}; |
this._timerEvents = {}; |
this._requestAnimationFrameEvents = {}; |
+ this._invalidationTracker = new WebInspector.InvalidationTracker(); |
this._layoutInvalidate = {}; |
this._lastScheduleStyleRecalculation = {}; |
this._webSocketCreateEvents = {}; |
@@ -488,6 +500,30 @@ WebInspector.TracingTimelineModel.prototype = { |
this._lastRecalculateStylesEvent = event; |
break; |
+ case recordTypes.StyleInvalidationTracking: |
+ this._invalidationTracker.addStyleInvalidation({ |
+ frameId: event.args.data.frame, |
+ nodeId: event.args.data.nodeId, |
+ nodeName: event.args.data.nodeName, |
+ callstack: event.args.data.callstack |
+ }); |
+ break; |
+ |
+ case recordTypes.LayoutInvalidationTracking: |
+ this._invalidationTracker.addLayoutInvalidation({ |
+ frameId: event.args.data.frame, |
+ nodeId: event.args.data.nodeId, |
+ callstack: event.args.data.callstack |
+ }); |
+ break; |
+ |
+ case recordTypes.PaintInvalidationTracking: |
+ this._invalidationTracker.addPaintInvalidation({ |
+ frameId: event.args.data.frame, |
+ nodeId: event.args.data.nodeId |
+ }); |
+ break; |
+ |
case recordTypes.InvalidateLayout: |
// Consider style recalculation as a reason for layout invalidation, |
// but only if we had no earlier layout invalidation records. |
@@ -529,6 +565,7 @@ WebInspector.TracingTimelineModel.prototype = { |
break; |
case recordTypes.Paint: |
+ this._invalidationTracker.setCurrentPaint(event); |
event.highlightQuad = event.args["data"]["clip"]; |
event.backendNodeId = event.args["data"]["nodeId"]; |
var layerUpdateEvent = this._findAncestorEvent(recordTypes.UpdateLayer); |
@@ -1016,3 +1053,125 @@ WebInspector.TracingTimelineSaver.prototype = { |
this._writeNextChunk(stream); |
} |
} |
+ |
+/** |
+ * TODO: Properly doc this class. |
+ * |
+ * Track style, layout, and paint invalidation events. |
+ * |
+ * As style or layout tracking invalidation events come in, they should be |
+ * registered here with add{Style,Layout}Invalidation. |
+ * When a paint event occurs (not invalidation tracking event), setCurrentPaint |
+ * should be called. |
+ * As paint tracking invalidation events come in, they should be registered |
+ * here with addPaintInvalidation. This function will annote the correct |
+ * paint event with style and layout invalidations. |
+ * |
+ * This class assumes events will come in the following order (expressed in pdr-bnf): |
+ * 1) (one or more) layout or style invalidation tracking events. |
+ * 2) (one or more) paint event followed by one or more paint invalidation tracking events. |
+ */ |
+WebInspector.InvalidationTracker = function() |
+{ |
+ // Invalidation events for the current frame. |
+ this._styleTrackingEvents = {}; |
+ this._layoutTrackingEvents = {}; |
+ |
+ // Invalidation events for the currently painting frame. |
+ // FIXME: these maps grow over time and need to be cleared after the frame ends. |
+ this._paintingStyleInvalidationMap = {}; |
+ this._paintingLayoutInvalidationMap = {}; |
+ |
+ this._currentPaintEvent = undefined; |
+} |
+ |
+WebInspector.InvalidationTracker.prototype = { |
+ |
+ addStyleInvalidation: function(invalidation) |
+ { |
+ this._currentPaintEvent = undefined; |
+ |
+ var frameId = invalidation.frameId; |
+ if (this._styleTrackingEvents[frameId] === undefined) |
+ this._styleTrackingEvents[frameId] = []; |
+ this._styleTrackingEvents[frameId].push(invalidation); |
+ }, |
+ |
+ addLayoutInvalidation: function(invalidation) |
+ { |
+ this._currentPaintEvent = undefined; |
+ |
+ var frameId = invalidation.frameId; |
+ if (this._layoutTrackingEvents[frameId] === undefined) |
+ this._layoutTrackingEvents[frameId] = []; |
+ this._layoutTrackingEvents[frameId].push(invalidation); |
+ }, |
+ |
+ addPaintInvalidation: function(invalidation) |
+ { |
+ var frameId = invalidation.frameId; |
+ var paintEvent = this._currentPaintEvent; |
+ |
+ if (paintEvent === undefined) { |
+ console.warn("Received paint event with no style or layout invalidations."); |
+ return; |
+ } |
+ |
+ var styleNodeIdMap = this._paintingStyleInvalidationMap[frameId]; |
+ if (styleNodeIdMap && styleNodeIdMap[invalidation.nodeId]) { |
+ styleNodeIdMap[invalidation.nodeId].forEach(function(styleEvent) { |
+ if (paintEvent.styleInvalidationTrackingEvents.indexOf(styleEvent) == -1) |
+ paintEvent.styleInvalidationTrackingEvents.push(styleEvent); |
+ }); |
+ } |
+ var layoutNodeIdMap = this._paintingLayoutInvalidationMap[frameId]; |
+ if (layoutNodeIdMap && layoutNodeIdMap[invalidation.nodeId]) { |
+ layoutNodeIdMap[invalidation.nodeId].forEach(function(layoutEvent) { |
+ if (paintEvent.layoutInvalidationTrackingEvents.indexOf(layoutEvent) == -1) |
+ paintEvent.layoutInvalidationTrackingEvents.push(layoutEvent); |
+ }); |
+ } |
+ }, |
+ |
+ setCurrentPaint: function(event) |
+ { |
+ this._startNewFrameIfNeeded(event.args.data.frame); |
+ |
+ event.styleInvalidationTrackingEvents = []; |
+ event.layoutInvalidationTrackingEvents = []; |
+ this._currentPaintEvent = event; |
+ }, |
+ |
+ // If there's a new frame, save off all current invalidations. |
+ _startNewFrameIfNeeded: function(frameId) |
+ { |
+ // FIXME: Need to track compositor events as well, as they can cause |
+ // paints without style or layout invalidating. For now, we |
+ // assume that the existance of style or layout invalidations |
+ // after paint begins indicates a new frame has started. |
+ if (this._styleTrackingEvents[frameId] === undefined && |
+ this._layoutTrackingEvents[frameId] === undefined) |
+ return; |
+ |
+ var styleTrackingEvents = this._styleTrackingEvents[frameId] || []; |
+ var layoutTrackingEvents = this._layoutTrackingEvents[frameId] || []; |
+ this._styleTrackingEvents[frameId] = undefined; |
+ this._layoutTrackingEvents[frameId] = undefined; |
+ |
+ var styleInvalidationMap = {}; |
+ styleTrackingEvents.forEach(function(styleEvent) { |
+ if (styleInvalidationMap[styleEvent.nodeId] === undefined) |
+ styleInvalidationMap[styleEvent.nodeId] = []; |
+ styleInvalidationMap[styleEvent.nodeId].push(styleEvent); |
+ }); |
+ this._paintingStyleInvalidationMap[frameId] = styleInvalidationMap; |
+ |
+ var layoutInvalidationMap = {}; |
+ layoutTrackingEvents.forEach(function(layoutEvent) { |
+ if (layoutInvalidationMap[layoutEvent.nodeId] === undefined) |
+ layoutInvalidationMap[layoutEvent.nodeId] = []; |
+ layoutInvalidationMap[layoutEvent.nodeId].push(layoutEvent); |
+ }); |
+ this._paintingLayoutInvalidationMap[frameId] = layoutInvalidationMap; |
+ } |
+} |