1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "Performance.h"
9 #include "GeckoProfiler.h"
10 #include "nsRFPService.h"
11 #include "PerformanceEntry.h"
12 #include "PerformanceMainThread.h"
13 #include "PerformanceMark.h"
14 #include "PerformanceMeasure.h"
15 #include "PerformanceObserver.h"
16 #include "PerformanceResourceTiming.h"
17 #include "PerformanceService.h"
18 #include "PerformanceWorker.h"
19 #include "mozilla/BasePrincipal.h"
20 #include "mozilla/ErrorResult.h"
21 #include "mozilla/dom/PerformanceBinding.h"
22 #include "mozilla/dom/PerformanceEntryEvent.h"
23 #include "mozilla/dom/PerformanceNavigationBinding.h"
24 #include "mozilla/dom/PerformanceObserverBinding.h"
25 #include "mozilla/dom/PerformanceNavigationTiming.h"
26 #include "mozilla/IntegerPrintfMacros.h"
27 #include "mozilla/Preferences.h"
28 #include "mozilla/dom/WorkerPrivate.h"
29 #include "mozilla/dom/WorkerRunnable.h"
31 #define PERFLOG(msg, ...) printf_stderr(msg, ##__VA_ARGS__)
33 namespace mozilla::dom
{
35 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Performance
)
36 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper
)
38 NS_IMPL_CYCLE_COLLECTION_INHERITED(Performance
, DOMEventTargetHelper
,
39 mUserEntries
, mResourceEntries
,
40 mSecondaryResourceEntries
, mObservers
);
42 NS_IMPL_ADDREF_INHERITED(Performance
, DOMEventTargetHelper
)
43 NS_IMPL_RELEASE_INHERITED(Performance
, DOMEventTargetHelper
)
46 already_AddRefed
<Performance
> Performance::CreateForMainThread(
47 nsPIDOMWindowInner
* aWindow
, nsIPrincipal
* aPrincipal
,
48 nsDOMNavigationTiming
* aDOMTiming
, nsITimedChannel
* aChannel
) {
49 MOZ_ASSERT(NS_IsMainThread());
51 MOZ_ASSERT(aWindow
->AsGlobal());
52 RefPtr
<Performance
> performance
= new PerformanceMainThread(
53 aWindow
, aDOMTiming
, aChannel
, aPrincipal
->IsSystemPrincipal());
54 return performance
.forget();
58 already_AddRefed
<Performance
> Performance::CreateForWorker(
59 WorkerPrivate
* aWorkerPrivate
) {
60 MOZ_ASSERT(aWorkerPrivate
);
61 aWorkerPrivate
->AssertIsOnWorkerThread();
63 RefPtr
<Performance
> performance
= new PerformanceWorker(aWorkerPrivate
);
64 return performance
.forget();
67 Performance::Performance(nsIGlobalObject
* aGlobal
, bool aSystemPrincipal
)
68 : DOMEventTargetHelper(aGlobal
),
69 mResourceTimingBufferSize(kDefaultResourceTimingBufferSize
),
70 mPendingNotificationObserversTask(false),
71 mPendingResourceTimingBufferFullEvent(false),
72 mSystemPrincipal(aSystemPrincipal
) {
73 MOZ_ASSERT(!NS_IsMainThread());
76 Performance::Performance(nsPIDOMWindowInner
* aWindow
, bool aSystemPrincipal
)
77 : DOMEventTargetHelper(aWindow
),
78 mResourceTimingBufferSize(kDefaultResourceTimingBufferSize
),
79 mPendingNotificationObserversTask(false),
80 mPendingResourceTimingBufferFullEvent(false),
81 mSystemPrincipal(aSystemPrincipal
) {
82 MOZ_ASSERT(NS_IsMainThread());
85 Performance::~Performance() = default;
87 DOMHighResTimeStamp
Performance::TimeStampToDOMHighResForRendering(
88 TimeStamp aTimeStamp
) const {
89 DOMHighResTimeStamp stamp
= GetDOMTiming()->TimeStampToDOMHighRes(aTimeStamp
);
90 if (!IsSystemPrincipal()) {
91 // 0 is an inappropriate mixin for this this area; however CSS Animations
92 // needs to have it's Time Reduction Logic refactored, so it's currently
93 // only clamping for RFP mode. RFP mode gives a much lower time precision,
94 // so we accept the security leak here for now.
95 return nsRFPService::ReduceTimePrecisionAsMSecsRFPOnly(stamp
, 0);
100 DOMHighResTimeStamp
Performance::Now() {
101 DOMHighResTimeStamp rawTime
= NowUnclamped();
103 // XXX: Remove this would cause functions in pkcs11f.h to fail.
104 // Bug 1628021 will find out the root cause.
105 if (mSystemPrincipal
) {
109 return nsRFPService::ReduceTimePrecisionAsMSecs(
110 rawTime
, GetRandomTimelineSeed(), mSystemPrincipal
,
111 CrossOriginIsolated());
114 DOMHighResTimeStamp
Performance::NowUnclamped() const {
115 TimeDuration duration
= TimeStamp::NowUnfuzzed() - CreationTimeStamp();
116 return duration
.ToMilliseconds();
119 DOMHighResTimeStamp
Performance::TimeOrigin() {
120 if (!mPerformanceService
) {
121 mPerformanceService
= PerformanceService::GetOrCreate();
124 MOZ_ASSERT(mPerformanceService
);
125 DOMHighResTimeStamp rawTimeOrigin
=
126 mPerformanceService
->TimeOrigin(CreationTimeStamp());
127 // Time Origin is an absolute timestamp, so we supply a 0 context mix-in
128 return nsRFPService::ReduceTimePrecisionAsMSecs(
129 rawTimeOrigin
, 0, mSystemPrincipal
, CrossOriginIsolated());
132 JSObject
* Performance::WrapObject(JSContext
* aCx
,
133 JS::Handle
<JSObject
*> aGivenProto
) {
134 return Performance_Binding::Wrap(aCx
, this, aGivenProto
);
137 void Performance::GetEntries(nsTArray
<RefPtr
<PerformanceEntry
>>& aRetval
) {
138 // We return an empty list when 'privacy.resistFingerprinting' is on.
139 if (nsContentUtils::ShouldResistFingerprinting()) {
144 aRetval
= mResourceEntries
.Clone();
145 aRetval
.AppendElements(mUserEntries
);
146 aRetval
.Sort(PerformanceEntryComparator());
149 void Performance::GetEntriesByType(
150 const nsAString
& aEntryType
, nsTArray
<RefPtr
<PerformanceEntry
>>& aRetval
) {
151 // We return an empty list when 'privacy.resistFingerprinting' is on.
152 if (nsContentUtils::ShouldResistFingerprinting()) {
157 if (aEntryType
.EqualsLiteral("resource")) {
158 aRetval
= mResourceEntries
.Clone();
164 if (aEntryType
.EqualsLiteral("mark") || aEntryType
.EqualsLiteral("measure")) {
165 for (PerformanceEntry
* entry
: mUserEntries
) {
166 if (entry
->GetEntryType().Equals(aEntryType
)) {
167 aRetval
.AppendElement(entry
);
173 void Performance::GetEntriesByName(
174 const nsAString
& aName
, const Optional
<nsAString
>& aEntryType
,
175 nsTArray
<RefPtr
<PerformanceEntry
>>& aRetval
) {
178 // We return an empty list when 'privacy.resistFingerprinting' is on.
179 if (nsContentUtils::ShouldResistFingerprinting()) {
183 // ::Measure expects that results from this function are already
184 // passed through ReduceTimePrecision. mResourceEntries and mUserEntries
185 // are, so the invariant holds.
186 for (PerformanceEntry
* entry
: mResourceEntries
) {
187 if (entry
->GetName().Equals(aName
) &&
188 (!aEntryType
.WasPassed() ||
189 entry
->GetEntryType().Equals(aEntryType
.Value()))) {
190 aRetval
.AppendElement(entry
);
194 for (PerformanceEntry
* entry
: mUserEntries
) {
195 if (entry
->GetName().Equals(aName
) &&
196 (!aEntryType
.WasPassed() ||
197 entry
->GetEntryType().Equals(aEntryType
.Value()))) {
198 aRetval
.AppendElement(entry
);
202 aRetval
.Sort(PerformanceEntryComparator());
205 void Performance::ClearUserEntries(const Optional
<nsAString
>& aEntryName
,
206 const nsAString
& aEntryType
) {
207 mUserEntries
.RemoveElementsBy([&aEntryName
, &aEntryType
](const auto& entry
) {
208 return (!aEntryName
.WasPassed() ||
209 entry
->GetName().Equals(aEntryName
.Value())) &&
210 (aEntryType
.IsEmpty() || entry
->GetEntryType().Equals(aEntryType
));
214 void Performance::ClearResourceTimings() { mResourceEntries
.Clear(); }
216 #ifdef MOZ_GECKO_PROFILER
217 struct UserTimingMarker
{
218 static constexpr Span
<const char> MarkerTypeName() {
219 return MakeStringSpan("UserTiming");
221 static void StreamJSONMarkerData(
222 baseprofiler::SpliceableJSONWriter
& aWriter
,
223 const ProfilerString16View
& aName
, bool aIsMeasure
,
224 const Maybe
<ProfilerString16View
>& aStartMark
,
225 const Maybe
<ProfilerString16View
>& aEndMark
) {
226 aWriter
.StringProperty("name", NS_ConvertUTF16toUTF8(aName
.Data()));
228 aWriter
.StringProperty("entryType", "measure");
230 aWriter
.StringProperty("entryType", "mark");
233 if (aStartMark
.isSome()) {
234 aWriter
.StringProperty("startMark",
235 NS_ConvertUTF16toUTF8(aStartMark
->Data()));
237 aWriter
.NullProperty("startMark");
239 if (aEndMark
.isSome()) {
240 aWriter
.StringProperty("endMark",
241 NS_ConvertUTF16toUTF8(aEndMark
->Data()));
243 aWriter
.NullProperty("endMark");
246 static MarkerSchema
MarkerTypeDisplay() {
247 using MS
= MarkerSchema
;
248 MS schema
{MS::Location::markerChart
, MS::Location::markerTable
};
249 schema
.SetAllLabels("{marker.data.name}");
250 schema
.AddStaticLabelValue("Marker", "UserTiming");
251 schema
.AddKeyLabelFormat("entryType", "Entry Type", MS::Format::string
);
252 schema
.AddKeyLabelFormat("name", "Name", MS::Format::string
);
253 schema
.AddKeyLabelFormat("startMark", "Start Mark", MS::Format::string
);
254 schema
.AddKeyLabelFormat("endMark", "End Mark", MS::Format::string
);
255 schema
.AddStaticLabelValue("Description",
256 "UserTimingMeasure is created using the DOM API "
257 "performance.measure().");
263 void Performance::Mark(const nsAString
& aName
, ErrorResult
& aRv
) {
264 // We add nothing when 'privacy.resistFingerprinting' is on.
265 if (nsContentUtils::ShouldResistFingerprinting()) {
269 if (IsPerformanceTimingAttribute(aName
)) {
270 aRv
.Throw(NS_ERROR_DOM_SYNTAX_ERR
);
274 RefPtr
<PerformanceMark
> performanceMark
=
275 new PerformanceMark(GetParentObject(), aName
, Now());
276 InsertUserEntry(performanceMark
);
278 #ifdef MOZ_GECKO_PROFILER
279 if (profiler_can_accept_markers()) {
280 Maybe
<uint64_t> innerWindowId
;
282 innerWindowId
= Some(GetOwner()->WindowID());
284 profiler_add_marker("UserTiming", geckoprofiler::category::DOM
,
285 MarkerInnerWindowId(innerWindowId
), UserTimingMarker
{},
286 aName
, /* aIsMeasure */ false, Nothing
{}, Nothing
{});
291 void Performance::ClearMarks(const Optional
<nsAString
>& aName
) {
292 ClearUserEntries(aName
, u
"mark"_ns
);
295 DOMHighResTimeStamp
Performance::ResolveTimestampFromName(
296 const nsAString
& aName
, ErrorResult
& aRv
) {
297 AutoTArray
<RefPtr
<PerformanceEntry
>, 1> arr
;
298 Optional
<nsAString
> typeParam
;
300 str
.AssignLiteral("mark");
302 GetEntriesByName(aName
, typeParam
, arr
);
303 if (!arr
.IsEmpty()) {
304 return arr
.LastElement()->StartTime();
307 if (!IsPerformanceTimingAttribute(aName
)) {
308 aRv
.Throw(NS_ERROR_DOM_SYNTAX_ERR
);
312 DOMHighResTimeStamp ts
= GetPerformanceTimingFromString(aName
);
314 aRv
.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR
);
318 return ts
- CreationTime();
321 void Performance::Measure(const nsAString
& aName
,
322 const Optional
<nsAString
>& aStartMark
,
323 const Optional
<nsAString
>& aEndMark
,
325 // We add nothing when 'privacy.resistFingerprinting' is on.
326 if (nsContentUtils::ShouldResistFingerprinting()) {
330 DOMHighResTimeStamp startTime
;
331 DOMHighResTimeStamp endTime
;
333 if (aStartMark
.WasPassed()) {
334 startTime
= ResolveTimestampFromName(aStartMark
.Value(), aRv
);
335 if (NS_WARN_IF(aRv
.Failed())) {
339 // Navigation start is used in this case, but since DOMHighResTimeStamp is
340 // in relation to navigation start, this will be zero if a name is not
345 if (aEndMark
.WasPassed()) {
346 endTime
= ResolveTimestampFromName(aEndMark
.Value(), aRv
);
347 if (NS_WARN_IF(aRv
.Failed())) {
354 RefPtr
<PerformanceMeasure
> performanceMeasure
=
355 new PerformanceMeasure(GetParentObject(), aName
, startTime
, endTime
);
356 InsertUserEntry(performanceMeasure
);
358 #ifdef MOZ_GECKO_PROFILER
359 if (profiler_can_accept_markers()) {
360 TimeStamp startTimeStamp
=
361 CreationTimeStamp() + TimeDuration::FromMilliseconds(startTime
);
362 TimeStamp endTimeStamp
=
363 CreationTimeStamp() + TimeDuration::FromMilliseconds(endTime
);
365 // Convert to Maybe values so that Optional types do not need to be used in
367 Maybe
<nsString
> startMark
;
368 if (aStartMark
.WasPassed()) {
369 startMark
.emplace(aStartMark
.Value());
371 Maybe
<nsString
> endMark
;
372 if (aEndMark
.WasPassed()) {
373 endMark
.emplace(aEndMark
.Value());
376 Maybe
<uint64_t> innerWindowId
;
378 innerWindowId
= Some(GetOwner()->WindowID());
380 profiler_add_marker("UserTiming", geckoprofiler::category::DOM
,
381 {MarkerTiming::Interval(startTimeStamp
, endTimeStamp
),
382 MarkerInnerWindowId(innerWindowId
)},
383 UserTimingMarker
{}, aName
, /* aIsMeasure */ true,
389 void Performance::ClearMeasures(const Optional
<nsAString
>& aName
) {
390 ClearUserEntries(aName
, u
"measure"_ns
);
393 void Performance::LogEntry(PerformanceEntry
* aEntry
,
394 const nsACString
& aOwner
) const {
396 "Performance Entry: %s|%s|%s|%f|%f|%" PRIu64
"\n", aOwner
.BeginReading(),
397 NS_ConvertUTF16toUTF8(aEntry
->GetEntryType()).get(),
398 NS_ConvertUTF16toUTF8(aEntry
->GetName()).get(), aEntry
->StartTime(),
399 aEntry
->Duration(), static_cast<uint64_t>(PR_Now() / PR_USEC_PER_MSEC
));
402 void Performance::TimingNotification(PerformanceEntry
* aEntry
,
403 const nsACString
& aOwner
,
405 PerformanceEntryEventInit init
;
406 init
.mBubbles
= false;
407 init
.mCancelable
= false;
408 init
.mName
= aEntry
->GetName();
409 init
.mEntryType
= aEntry
->GetEntryType();
410 init
.mStartTime
= aEntry
->StartTime();
411 init
.mDuration
= aEntry
->Duration();
412 init
.mEpoch
= aEpoch
;
413 CopyUTF8toUTF16(aOwner
, init
.mOrigin
);
415 RefPtr
<PerformanceEntryEvent
> perfEntryEvent
=
416 PerformanceEntryEvent::Constructor(this, u
"performanceentry"_ns
, init
);
418 nsCOMPtr
<EventTarget
> et
= do_QueryInterface(GetOwner());
420 et
->DispatchEvent(*perfEntryEvent
);
424 void Performance::InsertUserEntry(PerformanceEntry
* aEntry
) {
425 mUserEntries
.InsertElementSorted(aEntry
, PerformanceEntryComparator());
431 * Steps are labeled according to the description found at
432 * https://w3c.github.io/resource-timing/#sec-extensions-performance-interface.
436 void Performance::BufferEvent() {
438 * While resource timing secondary buffer is not empty,
439 * run the following substeps:
441 while (!mSecondaryResourceEntries
.IsEmpty()) {
442 uint32_t secondaryResourceEntriesBeforeCount
= 0;
443 uint32_t secondaryResourceEntriesAfterCount
= 0;
446 * Let number of excess entries before be resource
447 * timing secondary buffer current size.
449 secondaryResourceEntriesBeforeCount
= mSecondaryResourceEntries
.Length();
452 * If can add resource timing entry returns false,
453 * then fire an event named resourcetimingbufferfull
454 * at the Performance object.
456 if (!CanAddResourceTimingEntry()) {
457 DispatchBufferFullEvent();
461 * Run copy secondary buffer.
463 * While resource timing secondary buffer is not
464 * empty and can add resource timing entry returns
467 while (!mSecondaryResourceEntries
.IsEmpty() &&
468 CanAddResourceTimingEntry()) {
470 * Let entry be the oldest PerformanceResourceTiming
471 * in resource timing secondary buffer. Add entry to
472 * the end of performance entry buffer. Increment
473 * resource timing buffer current size by 1.
475 mResourceEntries
.InsertElementSorted(
476 mSecondaryResourceEntries
.ElementAt(0), PerformanceEntryComparator());
478 * Remove entry from resource timing secondary buffer.
479 * Decrement resource timing secondary buffer current
482 mSecondaryResourceEntries
.RemoveElementAt(0);
486 * Let number of excess entries after be resource
487 * timing secondary buffer current size.
489 secondaryResourceEntriesAfterCount
= mSecondaryResourceEntries
.Length();
492 * If number of excess entries before is lower than
493 * or equals number of excess entries after, then
494 * remove all entries from resource timing secondary
495 * buffer, set resource timing secondary buffer current
496 * size to 0, and abort these steps.
498 if (secondaryResourceEntriesBeforeCount
<=
499 secondaryResourceEntriesAfterCount
) {
500 mSecondaryResourceEntries
.Clear();
505 * Set resource timing buffer full event pending flag
508 mPendingResourceTimingBufferFullEvent
= false;
511 void Performance::SetResourceTimingBufferSize(uint64_t aMaxSize
) {
512 mResourceTimingBufferSize
= aMaxSize
;
516 * Steps are labeled according to the description found at
517 * https://w3c.github.io/resource-timing/#sec-extensions-performance-interface.
519 * Can Add Resource Timing Entry
521 MOZ_ALWAYS_INLINE
bool Performance::CanAddResourceTimingEntry() {
523 * If resource timing buffer current size is smaller than resource timing
524 * buffer size limit, return true. [Otherwise,] [r]eturn false.
526 return mResourceEntries
.Length() < mResourceTimingBufferSize
;
530 * Steps are labeled according to the description found at
531 * https://w3c.github.io/resource-timing/#sec-extensions-performance-interface.
533 * Add a PerformanceResourceTiming Entry
535 void Performance::InsertResourceEntry(PerformanceEntry
* aEntry
) {
538 if (nsContentUtils::ShouldResistFingerprinting()) {
543 * Let new entry be the input PerformanceEntry to be added.
545 * If can add resource timing entry returns true and resource
546 * timing buffer full event pending flag is false ...
548 if (CanAddResourceTimingEntry() && !mPendingResourceTimingBufferFullEvent
) {
550 * Add new entry to the performance entry buffer.
551 * Increase resource timing buffer current size by 1.
553 mResourceEntries
.InsertElementSorted(aEntry
, PerformanceEntryComparator());
559 * If resource timing buffer full event pending flag is
562 if (!mPendingResourceTimingBufferFullEvent
) {
564 * Set resource timing buffer full event pending flag
567 mPendingResourceTimingBufferFullEvent
= true;
570 * Queue a task to run fire a buffer full event.
572 NS_DispatchToCurrentThread(NewCancelableRunnableMethod(
573 "Performance::BufferEvent", this, &Performance::BufferEvent
));
576 * Add new entry to the resource timing secondary buffer.
577 * Increase resource timing secondary buffer current size
580 mSecondaryResourceEntries
.InsertElementSorted(aEntry
,
581 PerformanceEntryComparator());
584 void Performance::AddObserver(PerformanceObserver
* aObserver
) {
585 mObservers
.AppendElementUnlessExists(aObserver
);
588 void Performance::RemoveObserver(PerformanceObserver
* aObserver
) {
589 mObservers
.RemoveElement(aObserver
);
592 void Performance::NotifyObservers() {
593 mPendingNotificationObserversTask
= false;
594 NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mObservers
, Notify
, ());
597 void Performance::CancelNotificationObservers() {
598 mPendingNotificationObserversTask
= false;
601 class NotifyObserversTask final
: public CancelableRunnable
{
603 explicit NotifyObserversTask(Performance
* aPerformance
)
604 : CancelableRunnable("dom::NotifyObserversTask"),
605 mPerformance(aPerformance
) {
606 MOZ_ASSERT(mPerformance
);
609 // MOZ_CAN_RUN_SCRIPT_BOUNDARY for now until Runnable::Run is
610 // MOZ_CAN_RUN_SCRIPT.
611 MOZ_CAN_RUN_SCRIPT_BOUNDARY
612 NS_IMETHOD
Run() override
{
613 MOZ_ASSERT(mPerformance
);
614 RefPtr
<Performance
> performance(mPerformance
);
615 performance
->NotifyObservers();
619 nsresult
Cancel() override
{
620 mPerformance
->CancelNotificationObservers();
621 mPerformance
= nullptr;
626 ~NotifyObserversTask() = default;
628 RefPtr
<Performance
> mPerformance
;
631 void Performance::QueueNotificationObserversTask() {
632 if (!mPendingNotificationObserversTask
) {
633 RunNotificationObserversTask();
637 void Performance::RunNotificationObserversTask() {
638 mPendingNotificationObserversTask
= true;
639 nsCOMPtr
<nsIRunnable
> task
= new NotifyObserversTask(this);
641 if (GetOwnerGlobal()) {
642 rv
= GetOwnerGlobal()->Dispatch(TaskCategory::Other
, task
.forget());
644 rv
= NS_DispatchToCurrentThread(task
);
646 if (NS_WARN_IF(NS_FAILED(rv
))) {
647 mPendingNotificationObserversTask
= false;
651 void Performance::QueueEntry(PerformanceEntry
* aEntry
) {
652 if (mObservers
.IsEmpty()) {
656 nsTObserverArray
<PerformanceObserver
*> interestedObservers
;
657 const auto [begin
, end
] = mObservers
.NonObservingRange();
658 std::copy_if(begin
, end
, MakeBackInserter(interestedObservers
),
659 [aEntry
](PerformanceObserver
* observer
) {
660 return observer
->ObservesTypeOfEntry(aEntry
);
663 if (interestedObservers
.IsEmpty()) {
667 NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(interestedObservers
, QueueEntry
,
670 QueueNotificationObserversTask();
673 void Performance::MemoryPressure() { mUserEntries
.Clear(); }
675 size_t Performance::SizeOfUserEntries(
676 mozilla::MallocSizeOf aMallocSizeOf
) const {
677 size_t userEntries
= 0;
678 for (const PerformanceEntry
* entry
: mUserEntries
) {
679 userEntries
+= entry
->SizeOfIncludingThis(aMallocSizeOf
);
684 size_t Performance::SizeOfResourceEntries(
685 mozilla::MallocSizeOf aMallocSizeOf
) const {
686 size_t resourceEntries
= 0;
687 for (const PerformanceEntry
* entry
: mResourceEntries
) {
688 resourceEntries
+= entry
->SizeOfIncludingThis(aMallocSizeOf
);
690 return resourceEntries
;
693 } // namespace mozilla::dom