Bug 1050770 - Add paint details to timeline. r=smaug, r=mattwoodrow
authorTom Tromey <tom@tromey.com>
Thu, 11 Dec 2014 10:40:00 -0500
changeset 219499 d639e397f2f0df891b21c6ed782d92e6df45cd69
parent 219498 eb32947900a9ee686dcb45274050791fbf2a9f44
child 219500 c02c0845ac35fe0e2aa9e915a58918a52552fa99
push idunknown
push userunknown
push dateunknown
reviewerssmaug, mattwoodrow
bugs1050770
milestone37.0a1
Bug 1050770 - Add paint details to timeline. r=smaug, r=mattwoodrow
docshell/base/nsDocShell.cpp
docshell/base/nsDocShell.h
docshell/test/browser/browser_timelineMarkers-02.js
dom/webidl/ProfileTimelineMarker.webidl
layout/base/FrameLayerBuilder.cpp
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -2893,16 +2893,21 @@ nsDocShell::PopProfileTimelineMarkers(JS
   // this array.
   nsTArray<TimelineMarker*> keptMarkers;
 
   for (uint32_t i = 0; i < mProfileTimelineMarkers.Length(); ++i) {
     TimelineMarker* startPayload = mProfileTimelineMarkers[i];
     const char* startMarkerName = startPayload->GetName();
 
     bool hasSeenPaintedLayer = false;
+    bool isPaint = strcmp(startMarkerName, "Paint") == 0;
+
+    // If we are processing a Paint marker, we append information from
+    // all the embedded Layer markers to this array.
+    mozilla::dom::Sequence<mozilla::dom::ProfileTimelineLayerRect> layerRectangles;
 
     if (startPayload->GetMetaData() == TRACING_INTERVAL_START) {
       bool hasSeenEnd = false;
 
       // DOM events can be nested, so we must take care when searching
       // for the matching end.  It doesn't hurt to apply this logic to
       // all event types.
       uint32_t markerDepth = 0;
@@ -2910,40 +2915,44 @@ nsDocShell::PopProfileTimelineMarkers(JS
       // The assumption is that the devtools timeline flushes markers frequently
       // enough for the amount of markers to always be small enough that the
       // nested for loop isn't going to be a performance problem.
       for (uint32_t j = i + 1; j < mProfileTimelineMarkers.Length(); ++j) {
         TimelineMarker* endPayload = mProfileTimelineMarkers[j];
         const char* endMarkerName = endPayload->GetName();
 
         // Look for Layer markers to stream out paint markers.
-        if (strcmp(endMarkerName, "Layer") == 0) {
+        if (isPaint && strcmp(endMarkerName, "Layer") == 0) {
           hasSeenPaintedLayer = true;
+          endPayload->AddLayerRectangles(layerRectangles);
         }
 
         if (!startPayload->Equals(endPayload)) {
           continue;
         }
-        bool isPaint = strcmp(startMarkerName, "Paint") == 0;
 
         // Pair start and end markers.
         if (endPayload->GetMetaData() == TRACING_INTERVAL_START) {
           ++markerDepth;
         } else if (endPayload->GetMetaData() == TRACING_INTERVAL_END) {
           if (markerDepth > 0) {
             --markerDepth;
           } else {
             // But ignore paint start/end if no layer has been painted.
             if (!isPaint || (isPaint && hasSeenPaintedLayer)) {
               mozilla::dom::ProfileTimelineMarker marker;
 
               marker.mName = NS_ConvertUTF8toUTF16(startPayload->GetName());
               marker.mStart = startPayload->GetTime();
               marker.mEnd = endPayload->GetTime();
-              startPayload->AddDetails(marker);
+              if (isPaint) {
+                marker.mRectangles.Construct(layerRectangles);
+              } else {
+                startPayload->AddDetails(marker);
+              }
               profileTimelineMarkers.AppendElement(marker);
             }
 
             // We want the start to be dropped either way.
             hasSeenEnd = true;
 
             break;
           }
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -300,16 +300,21 @@ public:
         }
 
         // Add details specific to this marker type to aMarker.  The
         // standard elements have already been set.
         virtual void AddDetails(mozilla::dom::ProfileTimelineMarker& aMarker)
         {
         }
 
+        virtual void AddLayerRectangles(mozilla::dom::Sequence<mozilla::dom::ProfileTimelineLayerRect>&)
+        {
+            MOZ_ASSERT_UNREACHABLE("can only be called on layer markers");
+        }
+
         const char* GetName() const
         {
             return mName;
         }
 
         TracingMetadata GetMetaData() const
         {
             return mMetaData;
--- a/docshell/test/browser/browser_timelineMarkers-02.js
+++ b/docshell/test/browser/browser_timelineMarkers-02.js
@@ -2,43 +2,57 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Test that the docShell profile timeline API returns the right markers when
 // restyles, reflows and paints occur
 
 let URL = '<!DOCTYPE html><style>' +
+          'body {margin:0; padding: 0;} ' +
           'div {width:100px;height:100px;background:red;} ' +
           '.resize-change-color {width:50px;height:50px;background:blue;} ' +
           '.change-color {width:50px;height:50px;background:yellow;} ' +
           '.add-class {}' +
           '</style><div></div>';
 
 let TESTS = [{
   desc: "Changing the width of the test element",
   setup: function(div) {
     div.setAttribute("class", "resize-change-color");
   },
   check: function(markers) {
     ok(markers.length > 0, "markers were returned");
     console.log(markers);
+    info(JSON.stringify(markers.filter(m => m.name == "Paint")));
     ok(markers.some(m => m.name == "Reflow"), "markers includes Reflow");
     ok(markers.some(m => m.name == "Paint"), "markers includes Paint");
+    for (let marker of markers.filter(m => m.name == "Paint")) {
+      // This change should generate at least one rectangle.
+      ok(marker.rectangles.length >= 1, "marker has one rectangle");
+      // One of the rectangles should contain the div.
+      ok(marker.rectangles.some(r => rectangleContains(r, 0, 0, 100, 100)));
+    }
     ok(markers.some(m => m.name == "Styles"), "markers includes Restyle");
   }
 }, {
   desc: "Changing the test element's background color",
   setup: function(div) {
     div.setAttribute("class", "change-color");
   },
   check: function(markers) {
     ok(markers.length > 0, "markers were returned");
     ok(!markers.some(m => m.name == "Reflow"), "markers doesn't include Reflow");
     ok(markers.some(m => m.name == "Paint"), "markers includes Paint");
+    for (let marker of markers.filter(m => m.name == "Paint")) {
+      // This change should generate at least one rectangle.
+      ok(marker.rectangles.length >= 1, "marker has one rectangle");
+      // One of the rectangles should contain the div.
+      ok(marker.rectangles.some(r => rectangleContains(r, 0, 0, 50, 50)));
+    }
     ok(markers.some(m => m.name == "Styles"), "markers includes Restyle");
   }
 }, {
   desc: "Changing the test element's classname",
   setup: function(div) {
     div.setAttribute("class", "change-color add-class");
   },
   check: function(markers) {
@@ -140,8 +154,13 @@ function waitForMarkers(docshell) {
       if (waitIterationCount > maxWaitIterationCount) {
         clearInterval(interval);
         resolve([]);
       }
       waitIterationCount++;
     }, 200);
   });
 }
+
+function rectangleContains(rect, x, y, width, height) {
+  return rect.x <= x && rect.y <= y && rect.width >= width &&
+    rect.height >= height;
+}
--- a/dom/webidl/ProfileTimelineMarker.webidl
+++ b/dom/webidl/ProfileTimelineMarker.webidl
@@ -1,16 +1,25 @@
 /* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/.
  */
 
+dictionary ProfileTimelineLayerRect {
+  long x = 0;
+  long y = 0;
+  long width = 0;
+  long height = 0;
+};
+
 dictionary ProfileTimelineMarker {
   DOMString name = "";
   DOMHighResTimeStamp start = 0;
   DOMHighResTimeStamp end = 0;
   /* For ConsoleTime markers.  */
   DOMString causeName;
   /* For DOMEvent markers.  */
   DOMString type;
   unsigned short eventPhase;
+  /* For Paint markers.  */
+  sequence<ProfileTimelineLayerRect> rectangles;
 };
--- a/layout/base/FrameLayerBuilder.cpp
+++ b/layout/base/FrameLayerBuilder.cpp
@@ -21,16 +21,17 @@
 #include "LayerTreeInvalidation.h"
 #include "nsSVGIntegrationUtils.h"
 #include "ImageContainer.h"
 #include "ActiveLayerTracker.h"
 #include "gfx2DGlue.h"
 #include "mozilla/LookAndFeel.h"
 #include "nsDocShell.h"
 #include "nsImageFrame.h"
+#include "mozilla/dom/ProfileTimelineMarkerBinding.h"
 
 #include "GeckoProfiler.h"
 #include "mozilla/gfx/Tools.h"
 #include "mozilla/gfx/2D.h"
 #include "gfxPrefs.h"
 
 #include <algorithm>
 
@@ -4414,16 +4415,46 @@ static void DrawForcedBackgroundColor(Dr
 {
   if (NS_GET_A(aBackgroundColor) > 0) {
     nsIntRect r = aLayer->GetVisibleRegion().GetBounds();
     ColorPattern color(ToDeviceColor(aBackgroundColor));
     aDrawTarget.FillRect(Rect(r.x, r.y, r.width, r.height), color);
   }
 }
 
+class LayerTimelineMarker : public nsDocShell::TimelineMarker
+{
+public:
+  LayerTimelineMarker(nsDocShell* aDocShell, const nsIntRegion& aRegion)
+    : nsDocShell::TimelineMarker(aDocShell, "Layer", TRACING_EVENT)
+    , mRegion(aRegion)
+  {
+  }
+
+  ~LayerTimelineMarker()
+  {
+  }
+
+  virtual void AddLayerRectangles(mozilla::dom::Sequence<mozilla::dom::ProfileTimelineLayerRect>& aRectangles)
+  {
+    nsIntRegionRectIterator it(mRegion);
+    while (const nsIntRect* iterRect = it.Next()) {
+      mozilla::dom::ProfileTimelineLayerRect rect;
+      rect.mX = iterRect->X();
+      rect.mY = iterRect->Y();
+      rect.mWidth = iterRect->Width();
+      rect.mHeight = iterRect->Height();
+      aRectangles.AppendElement(rect);
+    }
+  }
+
+private:
+  nsIntRegion mRegion;
+};
+
 /*
  * A note on residual transforms:
  *
  * In a transformed subtree we sometimes apply the PaintedLayer's
  * "residual transform" when drawing content into the PaintedLayer.
  * This is a translation by components in the range [-0.5,0.5) provided
  * by the layer system; applying the residual transform followed by the
  * transforms used by layer compositing ensures that the subpixel alignment
@@ -4563,17 +4594,23 @@ FrameLayerBuilder::DrawPaintedLayer(Pain
         gfxUtils::ClipToRegion(aContext, aRegionToDraw);
       }
     }
     FlashPaint(aContext);
   }
 
   if (presContext && presContext->GetDocShell() && isActiveLayerManager) {
     nsDocShell* docShell = static_cast<nsDocShell*>(presContext->GetDocShell());
-    docShell->AddProfileTimelineMarker("Layer", TRACING_EVENT);
+    bool isRecording;
+    docShell->GetRecordProfileTimelineMarkers(&isRecording);
+    if (isRecording) {
+      mozilla::UniquePtr<nsDocShell::TimelineMarker> marker =
+        MakeUnique<LayerTimelineMarker>(docShell, aRegionToDraw);
+      docShell->AddProfileTimelineMarker(marker);
+    }
   }
 
   if (!aRegionToInvalidate.IsEmpty()) {
     aLayer->AddInvalidRect(aRegionToInvalidate.GetBounds());
   }
 }
 
 bool