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 2cd5efd8681d9011c0faab9ec83ef93ac7d996b8..fa2894db584fef2d2fc56e0792f892f09b78a228 100644 |
--- a/Source/devtools/front_end/timeline/TracingTimelineModel.js |
+++ b/Source/devtools/front_end/timeline/TracingTimelineModel.js |
@@ -47,6 +47,11 @@ WebInspector.TracingTimelineModel.RecordType = { |
ScrollLayer: "ScrollLayer", |
CompositeLayers: "CompositeLayers", |
+ StyleRecalcInvalidationTracking: "StyleRecalcInvalidationTracking", |
+ LayoutInvalidationTracking: "LayoutInvalidationTracking", |
+ LayerInvalidationTracking: "LayerInvalidationTracking", |
+ PaintInvalidationTracking: "PaintInvalidationTracking", |
+ |
ParseHTML: "ParseHTML", |
TimerInstall: "TimerInstall", |
@@ -138,7 +143,9 @@ WebInspector.TracingTimelineModel.prototype = { |
WebInspector.TracingModel.ConsoleEventCategory |
]; |
if (captureCauses) { |
- categoriesArray.push(disabledByDefault("devtools.timeline.stack")); |
+ categoriesArray = categoriesArray.concat([ |
+ disabledByDefault("devtools.timeline.stack"), |
+ disabledByDefault("devtools.timeline.invalidationTracking")]); |
if (Runtime.experiments.isEnabled("timelineJSCPUProfile")) { |
this._jsProfilerStarted = true; |
this._currentTarget = WebInspector.context.flavor(WebInspector.Target); |
@@ -475,6 +482,7 @@ WebInspector.TracingTimelineModel.prototype = { |
this._sendRequestEvents = {}; |
this._timerEvents = {}; |
this._requestAnimationFrameEvents = {}; |
+ this._invalidationTracker = new WebInspector.InvalidationTracker(); |
this._layoutInvalidate = {}; |
this._lastScheduleStyleRecalculation = {}; |
this._webSocketCreateEvents = {}; |
@@ -591,6 +599,46 @@ WebInspector.TracingTimelineModel.prototype = { |
this._lastRecalculateStylesEvent = event; |
break; |
+ case recordTypes.StyleRecalcInvalidationTracking: |
+ this._invalidationTracker.addInvalidation({ |
+ type: recordTypes.StyleRecalcInvalidationTracking, |
+ frameId: event.args.data.frame, |
+ nodeId: event.args.data.nodeId, |
+ nodeName: event.args.data.nodeName, |
+ reason: event.args.data.reason, |
+ callstack: event.args.data.callstack |
+ }); |
+ break; |
+ |
+ case recordTypes.LayoutInvalidationTracking: |
+ this._invalidationTracker.addInvalidation({ |
+ type: recordTypes.LayoutInvalidationTracking, |
+ frameId: event.args.data.frame, |
+ nodeId: event.args.data.nodeId, |
+ nodeName: event.args.data.nodeName, |
+ callstack: event.args.data.callstack |
+ }); |
+ break; |
+ |
+ case recordTypes.LayerInvalidationTracking: |
+ this._invalidationTracker.addInvalidation({ |
+ type: recordTypes.LayerInvalidationTracking, |
+ frameId: event.args.data.frame, |
+ paintId: event.args.data.paintId, |
+ reason: event.args.data.reason |
+ }); |
+ break; |
+ |
+ case recordTypes.PaintInvalidationTracking: |
+ this._invalidationTracker.addInvalidation({ |
+ type: recordTypes.PaintInvalidationTracking, |
+ frameId: event.args.data.frame, |
+ nodeId: event.args.data.nodeId, |
+ nodeName: event.args.data.nodeName, |
+ paintId: event.args.data.paintId |
+ }); |
+ break; |
+ |
case recordTypes.InvalidateLayout: |
// Consider style recalculation as a reason for layout invalidation, |
// but only if we had no earlier layout invalidation records. |
@@ -635,6 +683,7 @@ WebInspector.TracingTimelineModel.prototype = { |
break; |
case recordTypes.Paint: |
+ this._invalidationTracker.didPaint(event); |
event.highlightQuad = event.args["data"]["clip"]; |
event.backendNodeId = event.args["data"]["nodeId"]; |
var layerUpdateEvent = this._findAncestorEvent(recordTypes.UpdateLayer); |
@@ -1103,3 +1152,88 @@ WebInspector.TracingTimelineSaver.prototype = { |
*/ |
onError: function(reader, event) { }, |
} |
+ |
+/** |
+ * Track invalidation events across frames. |
+ */ |
+WebInspector.InvalidationTracker = function() |
+{ |
+ this._invalidationEvents = []; |
+ this._lastPaintWithLayer = undefined; |
+ this._didPaint = false; |
+} |
+ |
+WebInspector.InvalidationTracker.prototype = { |
+ |
+ addInvalidation: function(invalidation) |
+ { |
+ this._startNewFrameIfNeeded(); |
+ if (!invalidation.nodeId && !invalidation.paintId) { |
+ console.error('Invalidation lacks node information.'); |
+ console.error(invalidation); |
+ } |
+ |
+ // Record the paintIds for style recalc or layout invalidations. |
+ // FIXME: This O(n^2) loop could be optimized with a map. |
+ if (invalidation.type == WebInspector.TracingTimelineModel.RecordType.PaintInvalidationTracking) { |
+ this._invalidationEvents.forEach(function(invalidationToUpdate) { |
+ if (invalidationToUpdate.nodeId != invalidation.nodeId) |
+ return; |
+ switch (invalidationToUpdate.type) { |
+ case WebInspector.TracingTimelineModel.RecordType.StyleRecalcInvalidationTracking: |
+ invalidationToUpdate.paintId = invalidation.paintId; |
+ break; |
+ case WebInspector.TracingTimelineModel.RecordType.LayoutInvalidationTracking: |
+ invalidationToUpdate.paintId = invalidation.paintId; |
+ break; |
+ } |
+ }); |
+ } else { |
+ this._invalidationEvents.push(invalidation); |
+ } |
+ }, |
+ |
+ didPaint: function(paintEvent) |
+ { |
+ this._didPaint = true; |
+ |
+ // If a paint doesn't have a corresponding graphics layer id, it paints into it's parent so |
+ // add an effectivePaintId to these events. |
+ // FIXME: The parent layer is assumed to be the last paint event that had a layerId set. Is that right? |
+ var layerId = paintEvent.args.data.layerId; |
+ if (layerId) |
+ this._lastPaintWithLayer = paintEvent; |
+ |
+ if (!this._lastPaintWithLayer) { |
+ console.error("Failed to find the paint container for a paint event."); |
+ return; |
+ } |
+ |
+ var effectivePaintId = this._lastPaintWithLayer.args.data.nodeId; |
+ this._processPaint(paintEvent, effectivePaintId); |
+ }, |
+ |
+ _processPaint: function(paintEvent, effectivePaintId) |
+ { |
+ var frameId = paintEvent.args.data.frame; |
+ |
+ this._invalidationEvents.forEach(function(invalidation) { |
+ if (invalidation.paintId == effectivePaintId && invalidation.frameId == frameId) { |
+ if (!paintEvent.invalidationTrackingEvents) |
+ paintEvent.invalidationTrackingEvents = []; |
+ paintEvent.invalidationTrackingEvents.push(invalidation); |
+ } |
+ }); |
+ }, |
+ |
+ _startNewFrameIfNeeded: function() |
+ { |
+ if (!this._didPaint) |
+ return; |
+ |
+ // Prepare for the next frame. |
+ this._invalidationEvents = []; |
+ this._lastPaintWithLayer = undefined; |
+ this._didPaint = false; |
+ } |
+} |