summaryrefslogtreecommitdiffstats
path: root/docshell/base/timeline/ObservedDocShell.cpp
blob: 19429ad43ffc93aee98873439905322c69fa6b15 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
/* -*- Mode: C++; tab-width: 8; 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/. */

#include "ObservedDocShell.h"

#include "AbstractTimelineMarker.h"
#include "LayerTimelineMarker.h"
#include "MainThreadUtils.h"
#include "mozilla/Move.h"
#include "mozilla/AutoRestore.h"

namespace mozilla {

ObservedDocShell::ObservedDocShell(nsIDocShell* aDocShell)
  : MarkersStorage("ObservedDocShellMutex")
  , mDocShell(aDocShell)
  , mPopping(false)
{
  MOZ_ASSERT(NS_IsMainThread());
}

void
ObservedDocShell::AddMarker(UniquePtr<AbstractTimelineMarker>&& aMarker)
{
  // Only allow main thread markers to go into this list. No need to lock
  // here since `mTimelineMarkers` will only be accessed or modified on the
  // main thread only.
  MOZ_ASSERT(NS_IsMainThread());
  // Don't accept any markers generated by the process of popping
  // markers.
  if (!mPopping) {
    mTimelineMarkers.AppendElement(Move(aMarker));
  }
}

void
ObservedDocShell::AddOTMTMarker(UniquePtr<AbstractTimelineMarker>&& aMarker)
{
  // Only allow off the main thread markers to go into this list. Since most
  // of our markers come from the main thread, be a little more efficient and
  // avoid dealing with multithreading scenarios until all the markers are
  // actually cleared or popped in `ClearMarkers` or `PopMarkers`.
  MOZ_ASSERT(!NS_IsMainThread());
  MutexAutoLock lock(GetLock()); // for `mOffTheMainThreadTimelineMarkers`.
  mOffTheMainThreadTimelineMarkers.AppendElement(Move(aMarker));
}

void
ObservedDocShell::ClearMarkers()
{
  MOZ_ASSERT(NS_IsMainThread());
  MutexAutoLock lock(GetLock()); // for `mOffTheMainThreadTimelineMarkers`.
  mTimelineMarkers.Clear();
  mOffTheMainThreadTimelineMarkers.Clear();
}

void
ObservedDocShell::PopMarkers(JSContext* aCx,
                             nsTArray<dom::ProfileTimelineMarker>& aStore)
{
  MOZ_ASSERT(NS_IsMainThread());
  MutexAutoLock lock(GetLock()); // for `mOffTheMainThreadTimelineMarkers`.

  MOZ_RELEASE_ASSERT(!mPopping);
  AutoRestore<bool> resetPopping(mPopping);
  mPopping = true;

  // First, move all of our markers into a single array. We'll chose
  // the `mTimelineMarkers` store because that's where we expect most of
  // our markers to be.
  mTimelineMarkers.AppendElements(Move(mOffTheMainThreadTimelineMarkers));

  // If we see an unpaired START, we keep it around for the next call
  // to ObservedDocShell::PopMarkers. We store the kept START objects here.
  nsTArray<UniquePtr<AbstractTimelineMarker>> keptStartMarkers;

  for (uint32_t i = 0; i < mTimelineMarkers.Length(); ++i) {
    UniquePtr<AbstractTimelineMarker>& startPayload = mTimelineMarkers.ElementAt(i);

    // If this is a TIMESTAMP marker, there's no corresponding END,
    // as it's a single unit of time, not a duration.
    if (startPayload->GetTracingType() == MarkerTracingType::TIMESTAMP) {
      dom::ProfileTimelineMarker* marker = aStore.AppendElement();
      marker->mName = NS_ConvertUTF8toUTF16(startPayload->GetName());
      marker->mStart = startPayload->GetTime();
      marker->mEnd = startPayload->GetTime();
      marker->mStack = startPayload->GetStack();
      startPayload->AddDetails(aCx, *marker);
      continue;
    }

    // Whenever a START marker is found, look for the corresponding END
    // and build a {name,start,end} JS object.
    if (startPayload->GetTracingType() == MarkerTracingType::START) {
      bool hasSeenEnd = false;

      // "Paint" markers are different because painting is handled at root
      // docshell level. The information that a paint was done is stored at
      // sub-docshell level, but we can only be sure that a paint did actually
      // happen in if a "Layer" marker was recorded too.
      bool startIsPaintType = strcmp(startPayload->GetName(), "Paint") == 0;
      bool hasSeenLayerType = false;

      // If we are processing a "Paint" marker, we append information from
      // all the embedded "Layer" markers to this array.
      dom::Sequence<dom::ProfileTimelineLayerRect> layerRectangles;

      // 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;

      // 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 < mTimelineMarkers.Length(); ++j) {
        UniquePtr<AbstractTimelineMarker>& endPayload = mTimelineMarkers.ElementAt(j);
        bool endIsLayerType = strcmp(endPayload->GetName(), "Layer") == 0;

        // Look for "Layer" markers to stream out "Paint" markers.
        if (startIsPaintType && endIsLayerType) {
          AbstractTimelineMarker* raw = endPayload.get();
          LayerTimelineMarker* layerPayload = static_cast<LayerTimelineMarker*>(raw);
          layerPayload->AddLayerRectangles(layerRectangles);
          hasSeenLayerType = true;
        }
        if (!startPayload->Equals(*endPayload)) {
          continue;
        }
        if (endPayload->GetTracingType() == MarkerTracingType::START) {
          ++markerDepth;
          continue;
        }
        if (endPayload->GetTracingType() == MarkerTracingType::END) {
          if (markerDepth > 0) {
            --markerDepth;
            continue;
          }
          if (!startIsPaintType || (startIsPaintType && hasSeenLayerType)) {
            dom::ProfileTimelineMarker* marker = aStore.AppendElement();
            marker->mName = NS_ConvertUTF8toUTF16(startPayload->GetName());
            marker->mStart = startPayload->GetTime();
            marker->mEnd = endPayload->GetTime();
            marker->mStack = startPayload->GetStack();
            if (hasSeenLayerType) {
              marker->mRectangles.Construct(layerRectangles);
            }
            startPayload->AddDetails(aCx, *marker);
            endPayload->AddDetails(aCx, *marker);
          }
          hasSeenEnd = true;
          break;
        }
      }

      // If we did not see the corresponding END, keep the START.
      if (!hasSeenEnd) {
        keptStartMarkers.AppendElement(Move(mTimelineMarkers.ElementAt(i)));
        mTimelineMarkers.RemoveElementAt(i);
        --i;
      }
    }
  }

  mTimelineMarkers.SwapElements(keptStartMarkers);
}

} // namespace mozilla