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"
12 #include "GeckoProfiler.h"
13 #include "nsRFPService.h"
14 #include "PerformanceEntry.h"
15 #include "PerformanceMainThread.h"
16 #include "PerformanceMark.h"
17 #include "PerformanceMeasure.h"
18 #include "PerformanceObserver.h"
19 #include "PerformanceResourceTiming.h"
20 #include "PerformanceService.h"
21 #include "PerformanceWorker.h"
22 #include "mozilla/BasePrincipal.h"
23 #include "mozilla/ErrorResult.h"
24 #include "mozilla/dom/MessagePortBinding.h"
25 #include "mozilla/dom/PerformanceBinding.h"
26 #include "mozilla/dom/PerformanceEntryEvent.h"
27 #include "mozilla/dom/PerformanceNavigationBinding.h"
28 #include "mozilla/dom/PerformanceObserverBinding.h"
29 #include "mozilla/dom/PerformanceNavigationTiming.h"
30 #include "mozilla/IntegerPrintfMacros.h"
31 #include "mozilla/Preferences.h"
32 #include "mozilla/TimeStamp.h"
33 #include "mozilla/dom/WorkerPrivate.h"
34 #include "mozilla/dom/WorkerRunnable.h"
35 #include "mozilla/dom/WorkerScope.h"
37 #define PERFLOG(msg, ...) printf_stderr(msg, ##__VA_ARGS__)
39 namespace mozilla::dom
{
41 enum class Performance::ResolveTimestampAttribute
{
47 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Performance
)
48 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper
)
50 NS_IMPL_CYCLE_COLLECTION_INHERITED(Performance
, DOMEventTargetHelper
,
51 mUserEntries
, mResourceEntries
,
52 mSecondaryResourceEntries
, mObservers
);
54 NS_IMPL_ADDREF_INHERITED(Performance
, DOMEventTargetHelper
)
55 NS_IMPL_RELEASE_INHERITED(Performance
, DOMEventTargetHelper
)
58 already_AddRefed
<Performance
> Performance::CreateForMainThread(
59 nsPIDOMWindowInner
* aWindow
, nsIPrincipal
* aPrincipal
,
60 nsDOMNavigationTiming
* aDOMTiming
, nsITimedChannel
* aChannel
) {
61 MOZ_ASSERT(NS_IsMainThread());
63 MOZ_ASSERT(aWindow
->AsGlobal());
64 RefPtr
<Performance
> performance
=
65 new PerformanceMainThread(aWindow
, aDOMTiming
, aChannel
);
66 return performance
.forget();
70 already_AddRefed
<Performance
> Performance::CreateForWorker(
71 WorkerGlobalScope
* aGlobalScope
) {
72 MOZ_ASSERT(aGlobalScope
);
73 // aWorkerPrivate->AssertIsOnWorkerThread();
75 RefPtr
<Performance
> performance
= new PerformanceWorker(aGlobalScope
);
76 return performance
.forget();
80 already_AddRefed
<Performance
> Performance::Get(JSContext
* aCx
,
81 nsIGlobalObject
* aGlobal
) {
82 RefPtr
<Performance
> performance
;
83 if (NS_IsMainThread()) {
84 nsCOMPtr
<nsPIDOMWindowInner
> window
= do_QueryInterface(aGlobal
);
89 performance
= window
->GetPerformance();
90 return performance
.forget();
93 const WorkerPrivate
* workerPrivate
= GetWorkerPrivateFromContext(aCx
);
98 WorkerGlobalScope
* scope
= workerPrivate
->GlobalScope();
100 performance
= scope
->GetPerformance();
102 return performance
.forget();
105 Performance::Performance(nsIGlobalObject
* aGlobal
)
106 : DOMEventTargetHelper(aGlobal
),
107 mResourceTimingBufferSize(kDefaultResourceTimingBufferSize
),
108 mPendingNotificationObserversTask(false),
109 mPendingResourceTimingBufferFullEvent(false),
110 mRTPCallerType(aGlobal
->GetRTPCallerType()),
111 mCrossOriginIsolated(aGlobal
->CrossOriginIsolated()),
112 mShouldResistFingerprinting(aGlobal
->ShouldResistFingerprinting(
113 RFPTarget::ReduceTimerPrecision
)) {}
115 Performance::~Performance() = default;
117 DOMHighResTimeStamp
Performance::TimeStampToDOMHighResForRendering(
118 TimeStamp aTimeStamp
) const {
119 DOMHighResTimeStamp stamp
= GetDOMTiming()->TimeStampToDOMHighRes(aTimeStamp
);
120 // 0 is an inappropriate mixin for this this area; however CSS Animations
121 // needs to have it's Time Reduction Logic refactored, so it's currently
122 // only clamping for RFP mode. RFP mode gives a much lower time precision,
123 // so we accept the security leak here for now.
124 return nsRFPService::ReduceTimePrecisionAsMSecsRFPOnly(stamp
, 0,
128 DOMHighResTimeStamp
Performance::Now() {
129 DOMHighResTimeStamp rawTime
= NowUnclamped();
131 // XXX: Removing this caused functions in pkcs11f.h to fail.
132 // Bug 1628021 investigates the root cause - it involves initializing
133 // the RNG service (part of GetRandomTimelineSeed()) off-main-thread
134 // but the underlying cause hasn't been identified yet.
135 if (mRTPCallerType
== RTPCallerType::SystemPrincipal
) {
139 return nsRFPService::ReduceTimePrecisionAsMSecs(
140 rawTime
, GetRandomTimelineSeed(), mRTPCallerType
);
143 DOMHighResTimeStamp
Performance::NowUnclamped() const {
144 TimeDuration duration
= TimeStamp::Now() - CreationTimeStamp();
145 return duration
.ToMilliseconds();
148 DOMHighResTimeStamp
Performance::TimeOrigin() {
149 if (!mPerformanceService
) {
150 mPerformanceService
= PerformanceService::GetOrCreate();
153 MOZ_ASSERT(mPerformanceService
);
154 DOMHighResTimeStamp rawTimeOrigin
=
155 mPerformanceService
->TimeOrigin(CreationTimeStamp());
156 // Time Origin is an absolute timestamp, so we supply a 0 context mix-in
157 return nsRFPService::ReduceTimePrecisionAsMSecs(rawTimeOrigin
, 0,
161 JSObject
* Performance::WrapObject(JSContext
* aCx
,
162 JS::Handle
<JSObject
*> aGivenProto
) {
163 return Performance_Binding::Wrap(aCx
, this, aGivenProto
);
166 void Performance::GetEntries(nsTArray
<RefPtr
<PerformanceEntry
>>& aRetval
) {
167 aRetval
= mResourceEntries
.Clone();
168 aRetval
.AppendElements(mUserEntries
);
169 aRetval
.Sort(PerformanceEntryComparator());
172 void Performance::GetEntriesByType(
173 const nsAString
& aEntryType
, nsTArray
<RefPtr
<PerformanceEntry
>>& aRetval
) {
174 if (aEntryType
.EqualsLiteral("resource")) {
175 aRetval
= mResourceEntries
.Clone();
181 if (aEntryType
.EqualsLiteral("mark") || aEntryType
.EqualsLiteral("measure")) {
182 RefPtr
<nsAtom
> entryType
= NS_Atomize(aEntryType
);
183 for (PerformanceEntry
* entry
: mUserEntries
) {
184 if (entry
->GetEntryType() == entryType
) {
185 aRetval
.AppendElement(entry
);
191 void Performance::GetEntriesByName(
192 const nsAString
& aName
, const Optional
<nsAString
>& aEntryType
,
193 nsTArray
<RefPtr
<PerformanceEntry
>>& aRetval
) {
196 RefPtr
<nsAtom
> name
= NS_Atomize(aName
);
197 RefPtr
<nsAtom
> entryType
=
198 aEntryType
.WasPassed() ? NS_Atomize(aEntryType
.Value()) : nullptr;
201 if (entryType
== nsGkAtoms::mark
|| entryType
== nsGkAtoms::measure
) {
202 for (PerformanceEntry
* entry
: mUserEntries
) {
203 if (entry
->GetName() == name
&& entry
->GetEntryType() == entryType
) {
204 aRetval
.AppendElement(entry
);
209 if (entryType
== nsGkAtoms::resource
) {
210 for (PerformanceEntry
* entry
: mResourceEntries
) {
211 MOZ_ASSERT(entry
->GetEntryType() == entryType
);
212 if (entry
->GetName() == name
) {
213 aRetval
.AppendElement(entry
);
222 nsTArray
<PerformanceEntry
*> qualifiedResourceEntries
;
223 nsTArray
<PerformanceEntry
*> qualifiedUserEntries
;
224 // ::Measure expects that results from this function are already
225 // passed through ReduceTimePrecision. mResourceEntries and mUserEntries
226 // are, so the invariant holds.
227 for (PerformanceEntry
* entry
: mResourceEntries
) {
228 if (entry
->GetName() == name
) {
229 qualifiedResourceEntries
.AppendElement(entry
);
233 for (PerformanceEntry
* entry
: mUserEntries
) {
234 if (entry
->GetName() == name
) {
235 qualifiedUserEntries
.AppendElement(entry
);
239 size_t resourceEntriesIdx
= 0, userEntriesIdx
= 0;
240 aRetval
.SetCapacity(qualifiedResourceEntries
.Length() +
241 qualifiedUserEntries
.Length());
243 PerformanceEntryComparator comparator
;
245 while (resourceEntriesIdx
< qualifiedResourceEntries
.Length() &&
246 userEntriesIdx
< qualifiedUserEntries
.Length()) {
247 if (comparator
.LessThan(qualifiedResourceEntries
[resourceEntriesIdx
],
248 qualifiedUserEntries
[userEntriesIdx
])) {
249 aRetval
.AppendElement(qualifiedResourceEntries
[resourceEntriesIdx
]);
250 ++resourceEntriesIdx
;
252 aRetval
.AppendElement(qualifiedUserEntries
[userEntriesIdx
]);
257 while (resourceEntriesIdx
< qualifiedResourceEntries
.Length()) {
258 aRetval
.AppendElement(qualifiedResourceEntries
[resourceEntriesIdx
]);
259 ++resourceEntriesIdx
;
262 while (userEntriesIdx
< qualifiedUserEntries
.Length()) {
263 aRetval
.AppendElement(qualifiedUserEntries
[userEntriesIdx
]);
268 void Performance::GetEntriesByTypeForObserver(
269 const nsAString
& aEntryType
, nsTArray
<RefPtr
<PerformanceEntry
>>& aRetval
) {
270 GetEntriesByType(aEntryType
, aRetval
);
273 void Performance::ClearUserEntries(const Optional
<nsAString
>& aEntryName
,
274 const nsAString
& aEntryType
) {
275 MOZ_ASSERT(!aEntryType
.IsEmpty());
276 RefPtr
<nsAtom
> name
=
277 aEntryName
.WasPassed() ? NS_Atomize(aEntryName
.Value()) : nullptr;
278 RefPtr
<nsAtom
> entryType
= NS_Atomize(aEntryType
);
279 mUserEntries
.RemoveElementsBy([name
, entryType
](const auto& entry
) {
280 return (!name
|| entry
->GetName() == name
) &&
281 (entry
->GetEntryType() == entryType
);
285 void Performance::ClearResourceTimings() { mResourceEntries
.Clear(); }
287 struct UserTimingMarker
: public BaseMarkerType
<UserTimingMarker
> {
288 static constexpr const char* Name
= "UserTiming";
289 static constexpr const char* Description
=
290 "UserTimingMeasure is created using the DOM API performance.measure().";
292 using MS
= MarkerSchema
;
293 static constexpr MS::PayloadField PayloadFields
[] = {
294 {"name", MS::InputType::String
, "User Marker Name", MS::Format::String
,
295 MS::PayloadFlags::Searchable
},
296 {"entryType", MS::InputType::Boolean
, "Entry Type"},
297 {"startMark", MS::InputType::String
, "Start Mark"},
298 {"endMark", MS::InputType::String
, "End Mark"}};
300 static constexpr MS::Location Locations
[] = {MS::Location::MarkerChart
,
301 MS::Location::MarkerTable
};
302 static constexpr const char* AllLabels
= "{marker.data.name}";
304 static constexpr MS::ETWMarkerGroup Group
= MS::ETWMarkerGroup::UserMarkers
;
306 static void StreamJSONMarkerData(
307 baseprofiler::SpliceableJSONWriter
& aWriter
,
308 const ProfilerString16View
& aName
, bool aIsMeasure
,
309 const Maybe
<ProfilerString16View
>& aStartMark
,
310 const Maybe
<ProfilerString16View
>& aEndMark
) {
311 StreamJSONMarkerDataImpl(
313 aIsMeasure
? MakeStringSpan("measure") : MakeStringSpan("mark"),
314 aStartMark
, aEndMark
);
318 already_AddRefed
<PerformanceMark
> Performance::Mark(
319 JSContext
* aCx
, const nsAString
& aName
,
320 const PerformanceMarkOptions
& aMarkOptions
, ErrorResult
& aRv
) {
321 nsCOMPtr
<nsIGlobalObject
> parent
= GetParentObject();
322 if (!parent
|| parent
->IsDying() || !parent
->HasJSGlobal()) {
323 aRv
.ThrowInvalidStateError("Global object is unavailable");
327 GlobalObject
global(aCx
, parent
->GetGlobalJSObject());
328 if (global
.Failed()) {
329 aRv
.ThrowInvalidStateError("Global object is unavailable");
333 RefPtr
<PerformanceMark
> performanceMark
=
334 PerformanceMark::Constructor(global
, aName
, aMarkOptions
, aRv
);
339 InsertUserEntry(performanceMark
);
341 if (profiler_is_collecting_markers()) {
342 Maybe
<uint64_t> innerWindowId
;
344 innerWindowId
= Some(GetOwner()->WindowID());
346 TimeStamp startTimeStamp
=
347 CreationTimeStamp() +
348 TimeDuration::FromMilliseconds(performanceMark
->UnclampedStartTime());
349 profiler_add_marker("UserTiming", geckoprofiler::category::DOM
,
350 MarkerOptions(MarkerTiming::InstantAt(startTimeStamp
),
351 MarkerInnerWindowId(innerWindowId
)),
352 UserTimingMarker
{}, aName
, /* aIsMeasure */ false,
353 Nothing
{}, Nothing
{});
356 return performanceMark
.forget();
359 void Performance::ClearMarks(const Optional
<nsAString
>& aName
) {
360 ClearUserEntries(aName
, u
"mark"_ns
);
363 // To be removed once bug 1124165 lands
364 bool Performance::IsPerformanceTimingAttribute(const nsAString
& aName
) const {
365 // Note that toJSON is added to this list due to bug 1047848
366 static const char* attributes
[] = {"navigationStart",
375 "secureConnectionStart",
382 "domContentLoadedEventStart",
383 "domContentLoadedEventEnd",
389 for (uint32_t i
= 0; attributes
[i
]; ++i
) {
390 if (aName
.EqualsASCII(attributes
[i
])) {
398 DOMHighResTimeStamp
Performance::ConvertMarkToTimestampWithString(
399 const nsAString
& aName
, ErrorResult
& aRv
, bool aReturnUnclamped
) {
400 if (IsPerformanceTimingAttribute(aName
)) {
401 return ConvertNameToTimestamp(aName
, aRv
);
404 RefPtr
<nsAtom
> name
= NS_Atomize(aName
);
405 // Just loop over the user entries
406 for (const PerformanceEntry
* entry
: Reversed(mUserEntries
)) {
407 if (entry
->GetName() == name
&& entry
->GetEntryType() == nsGkAtoms::mark
) {
408 if (aReturnUnclamped
) {
409 return entry
->UnclampedStartTime();
411 return entry
->StartTime();
415 nsPrintfCString
errorMsg("Given mark name, %s, is unknown",
416 NS_ConvertUTF16toUTF8(aName
).get());
417 aRv
.ThrowSyntaxError(errorMsg
);
421 DOMHighResTimeStamp
Performance::ConvertMarkToTimestampWithDOMHighResTimeStamp(
422 const ResolveTimestampAttribute aAttribute
,
423 const DOMHighResTimeStamp aTimestamp
, ErrorResult
& aRv
) {
424 if (aTimestamp
< 0) {
425 nsAutoCString attributeName
;
426 switch (aAttribute
) {
427 case ResolveTimestampAttribute::Start
:
428 attributeName
= "start";
430 case ResolveTimestampAttribute::End
:
431 attributeName
= "end";
433 case ResolveTimestampAttribute::Duration
:
434 attributeName
= "duration";
438 nsPrintfCString
errorMsg("Given attribute %s cannot be negative",
439 attributeName
.get());
440 aRv
.ThrowTypeError(errorMsg
);
445 DOMHighResTimeStamp
Performance::ConvertMarkToTimestamp(
446 const ResolveTimestampAttribute aAttribute
,
447 const OwningStringOrDouble
& aMarkNameOrTimestamp
, ErrorResult
& aRv
,
448 bool aReturnUnclamped
) {
449 if (aMarkNameOrTimestamp
.IsString()) {
450 return ConvertMarkToTimestampWithString(aMarkNameOrTimestamp
.GetAsString(),
451 aRv
, aReturnUnclamped
);
454 return ConvertMarkToTimestampWithDOMHighResTimeStamp(
455 aAttribute
, aMarkNameOrTimestamp
.GetAsDouble(), aRv
);
458 DOMHighResTimeStamp
Performance::ConvertNameToTimestamp(const nsAString
& aName
,
460 if (!IsGlobalObjectWindow()) {
461 nsPrintfCString
errorMsg(
462 "Cannot get PerformanceTiming attribute values for non-Window global "
464 NS_ConvertUTF16toUTF8(aName
).get());
465 aRv
.ThrowTypeError(errorMsg
);
469 if (aName
.EqualsASCII("navigationStart")) {
473 // We use GetPerformanceTimingFromString, rather than calling the
474 // navigationStart method timing function directly, because the former handles
475 // reducing precision against timing attacks.
476 const DOMHighResTimeStamp startTime
=
477 GetPerformanceTimingFromString(u
"navigationStart"_ns
);
478 const DOMHighResTimeStamp endTime
= GetPerformanceTimingFromString(aName
);
479 MOZ_ASSERT(endTime
>= 0);
481 nsPrintfCString
errorMsg(
482 "Given PerformanceTiming attribute, %s, isn't available yet",
483 NS_ConvertUTF16toUTF8(aName
).get());
484 aRv
.ThrowInvalidAccessError(errorMsg
);
488 return endTime
- startTime
;
491 DOMHighResTimeStamp
Performance::ResolveEndTimeForMeasure(
492 const Optional
<nsAString
>& aEndMark
,
493 const Maybe
<const PerformanceMeasureOptions
&>& aOptions
, ErrorResult
& aRv
,
494 bool aReturnUnclamped
) {
495 DOMHighResTimeStamp endTime
;
496 if (aEndMark
.WasPassed()) {
497 endTime
= ConvertMarkToTimestampWithString(aEndMark
.Value(), aRv
,
499 } else if (aOptions
&& aOptions
->mEnd
.WasPassed()) {
501 ConvertMarkToTimestamp(ResolveTimestampAttribute::End
,
502 aOptions
->mEnd
.Value(), aRv
, aReturnUnclamped
);
503 } else if (aOptions
&& aOptions
->mStart
.WasPassed() &&
504 aOptions
->mDuration
.WasPassed()) {
505 const DOMHighResTimeStamp start
=
506 ConvertMarkToTimestamp(ResolveTimestampAttribute::Start
,
507 aOptions
->mStart
.Value(), aRv
, aReturnUnclamped
);
512 const DOMHighResTimeStamp duration
=
513 ConvertMarkToTimestampWithDOMHighResTimeStamp(
514 ResolveTimestampAttribute::Duration
, aOptions
->mDuration
.Value(),
520 endTime
= start
+ duration
;
528 DOMHighResTimeStamp
Performance::ResolveStartTimeForMeasure(
529 const Maybe
<const nsAString
&>& aStartMark
,
530 const Maybe
<const PerformanceMeasureOptions
&>& aOptions
, ErrorResult
& aRv
,
531 bool aReturnUnclamped
) {
532 DOMHighResTimeStamp startTime
;
533 if (aOptions
&& aOptions
->mStart
.WasPassed()) {
535 ConvertMarkToTimestamp(ResolveTimestampAttribute::Start
,
536 aOptions
->mStart
.Value(), aRv
, aReturnUnclamped
);
537 } else if (aOptions
&& aOptions
->mDuration
.WasPassed() &&
538 aOptions
->mEnd
.WasPassed()) {
539 const DOMHighResTimeStamp duration
=
540 ConvertMarkToTimestampWithDOMHighResTimeStamp(
541 ResolveTimestampAttribute::Duration
, aOptions
->mDuration
.Value(),
547 const DOMHighResTimeStamp end
=
548 ConvertMarkToTimestamp(ResolveTimestampAttribute::End
,
549 aOptions
->mEnd
.Value(), aRv
, aReturnUnclamped
);
554 startTime
= end
- duration
;
555 } else if (aStartMark
) {
557 ConvertMarkToTimestampWithString(*aStartMark
, aRv
, aReturnUnclamped
);
565 static std::string
GetMarkerFilename() {
567 if (char* markerDir
= getenv("MOZ_PERFORMANCE_MARKER_DIR")) {
568 s
<< markerDir
<< "/";
571 s
<< "marker-" << GetCurrentProcessId() << ".txt";
573 s
<< "marker-" << getpid() << ".txt";
578 std::pair
<TimeStamp
, TimeStamp
> Performance::GetTimeStampsForMarker(
579 const Maybe
<const nsAString
&>& aStartMark
,
580 const Optional
<nsAString
>& aEndMark
,
581 const Maybe
<const PerformanceMeasureOptions
&>& aOptions
, ErrorResult
& aRv
) {
582 const DOMHighResTimeStamp unclampedStartTime
= ResolveStartTimeForMeasure(
583 aStartMark
, aOptions
, aRv
, /* aReturnUnclamped */ true);
584 const DOMHighResTimeStamp unclampedEndTime
=
585 ResolveEndTimeForMeasure(aEndMark
, aOptions
, aRv
, /* aReturnUnclamped */
588 TimeStamp startTimeStamp
=
589 CreationTimeStamp() + TimeDuration::FromMilliseconds(unclampedStartTime
);
590 TimeStamp endTimeStamp
=
591 CreationTimeStamp() + TimeDuration::FromMilliseconds(unclampedEndTime
);
593 return std::make_pair(startTimeStamp
, endTimeStamp
);
596 // This emits markers to an external marker-[pid].txt file for use by an
597 // external profiler like samply or etw-gecko
598 void Performance::MaybeEmitExternalProfilerMarker(
599 const nsAString
& aName
, Maybe
<const PerformanceMeasureOptions
&> aOptions
,
600 Maybe
<const nsAString
&> aStartMark
, const Optional
<nsAString
>& aEndMark
) {
601 static FILE* markerFile
= getenv("MOZ_USE_PERFORMANCE_MARKER_FILE")
602 ? fopen(GetMarkerFilename().c_str(), "w+")
609 auto [startTimeStamp
, endTimeStamp
] =
610 GetTimeStampsForMarker(aStartMark
, aEndMark
, aOptions
, rv
);
612 if (NS_WARN_IF(rv
.Failed())) {
617 uint64_t rawStart
= startTimeStamp
.RawClockMonotonicNanosecondsSinceBoot();
618 uint64_t rawEnd
= endTimeStamp
.RawClockMonotonicNanosecondsSinceBoot();
620 uint64_t rawStart
= startTimeStamp
.RawQueryPerformanceCounterValue().value();
621 uint64_t rawEnd
= endTimeStamp
.RawQueryPerformanceCounterValue().value();
623 uint64_t rawStart
= startTimeStamp
.RawMachAbsoluteTimeNanoseconds();
624 uint64_t rawEnd
= endTimeStamp
.RawMachAbsoluteTimeNanoseconds();
626 uint64_t rawStart
= 0;
628 MOZ_CRASH("no timestamp");
630 // Write a line for this measure to the marker file. The marker file uses a
631 // text-based format where every line is one marker, and each line has the
633 // `<raw_start_timestamp> <raw_end_timestamp> <measure_name>`
635 // The timestamp value is OS specific.
636 fprintf(markerFile
, "%" PRIu64
" %" PRIu64
" %s\n", rawStart
, rawEnd
,
637 NS_ConvertUTF16toUTF8(aName
).get());
641 already_AddRefed
<PerformanceMeasure
> Performance::Measure(
642 JSContext
* aCx
, const nsAString
& aName
,
643 const StringOrPerformanceMeasureOptions
& aStartOrMeasureOptions
,
644 const Optional
<nsAString
>& aEndMark
, ErrorResult
& aRv
) {
645 if (!GetParentObject()) {
646 aRv
.ThrowInvalidStateError("Global object is unavailable");
650 // Maybe is more readable than using the union type directly.
651 Maybe
<const PerformanceMeasureOptions
&> options
;
652 if (aStartOrMeasureOptions
.IsPerformanceMeasureOptions()) {
653 options
.emplace(aStartOrMeasureOptions
.GetAsPerformanceMeasureOptions());
656 const bool isOptionsNotEmpty
=
658 (!options
->mDetail
.isUndefined() || options
->mStart
.WasPassed() ||
659 options
->mEnd
.WasPassed() || options
->mDuration
.WasPassed());
660 if (isOptionsNotEmpty
) {
661 if (aEndMark
.WasPassed()) {
663 "Cannot provide separate endMark argument if "
664 "PerformanceMeasureOptions argument is given");
668 if (!options
->mStart
.WasPassed() && !options
->mEnd
.WasPassed()) {
670 "PerformanceMeasureOptions must have start and/or end member");
674 if (options
->mStart
.WasPassed() && options
->mDuration
.WasPassed() &&
675 options
->mEnd
.WasPassed()) {
677 "PerformanceMeasureOptions cannot have all of the following members: "
678 "start, duration, and end");
683 const DOMHighResTimeStamp endTime
= ResolveEndTimeForMeasure(
684 aEndMark
, options
, aRv
, /* aReturnUnclamped */ false);
685 if (NS_WARN_IF(aRv
.Failed())) {
689 // Convert to Maybe for consistency with options.
690 Maybe
<const nsAString
&> startMark
;
691 if (aStartOrMeasureOptions
.IsString()) {
692 startMark
.emplace(aStartOrMeasureOptions
.GetAsString());
694 const DOMHighResTimeStamp startTime
= ResolveStartTimeForMeasure(
695 startMark
, options
, aRv
, /* aReturnUnclamped */ false);
696 if (NS_WARN_IF(aRv
.Failed())) {
700 JS::Rooted
<JS::Value
> detail(aCx
);
701 if (options
&& !options
->mDetail
.isNullOrUndefined()) {
702 StructuredSerializeOptions serializeOptions
;
703 JS::Rooted
<JS::Value
> valueToClone(aCx
, options
->mDetail
);
704 nsContentUtils::StructuredClone(aCx
, GetParentObject(), valueToClone
,
705 serializeOptions
, &detail
, aRv
);
713 RefPtr
<PerformanceMeasure
> performanceMeasure
= new PerformanceMeasure(
714 GetParentObject(), aName
, startTime
, endTime
, detail
);
715 InsertUserEntry(performanceMeasure
);
717 MaybeEmitExternalProfilerMarker(aName
, options
, startMark
, aEndMark
);
719 if (profiler_is_collecting_markers()) {
720 auto [startTimeStamp
, endTimeStamp
] =
721 GetTimeStampsForMarker(startMark
, aEndMark
, options
, aRv
);
723 Maybe
<nsString
> endMark
;
724 if (aEndMark
.WasPassed()) {
725 endMark
.emplace(aEndMark
.Value());
728 Maybe
<uint64_t> innerWindowId
;
730 innerWindowId
= Some(GetOwner()->WindowID());
732 profiler_add_marker("UserTiming", geckoprofiler::category::DOM
,
733 {MarkerTiming::Interval(startTimeStamp
, endTimeStamp
),
734 MarkerInnerWindowId(innerWindowId
)},
735 UserTimingMarker
{}, aName
, /* aIsMeasure */ true,
739 return performanceMeasure
.forget();
742 void Performance::ClearMeasures(const Optional
<nsAString
>& aName
) {
743 ClearUserEntries(aName
, u
"measure"_ns
);
746 void Performance::LogEntry(PerformanceEntry
* aEntry
,
747 const nsACString
& aOwner
) const {
748 PERFLOG("Performance Entry: %s|%s|%s|%f|%f|%" PRIu64
"\n",
749 aOwner
.BeginReading(),
750 NS_ConvertUTF16toUTF8(aEntry
->GetEntryType()->GetUTF16String()).get(),
751 NS_ConvertUTF16toUTF8(aEntry
->GetName()->GetUTF16String()).get(),
752 aEntry
->StartTime(), aEntry
->Duration(),
753 static_cast<uint64_t>(PR_Now() / PR_USEC_PER_MSEC
));
756 void Performance::TimingNotification(PerformanceEntry
* aEntry
,
757 const nsACString
& aOwner
,
758 const double aEpoch
) {
759 PerformanceEntryEventInit init
;
760 init
.mBubbles
= false;
761 init
.mCancelable
= false;
762 aEntry
->GetName(init
.mName
);
763 aEntry
->GetEntryType(init
.mEntryType
);
764 init
.mStartTime
= aEntry
->StartTime();
765 init
.mDuration
= aEntry
->Duration();
766 init
.mEpoch
= aEpoch
;
767 CopyUTF8toUTF16(aOwner
, init
.mOrigin
);
769 RefPtr
<PerformanceEntryEvent
> perfEntryEvent
=
770 PerformanceEntryEvent::Constructor(this, u
"performanceentry"_ns
, init
);
772 nsCOMPtr
<EventTarget
> et
= do_QueryInterface(GetOwner());
774 et
->DispatchEvent(*perfEntryEvent
);
778 void Performance::InsertUserEntry(PerformanceEntry
* aEntry
) {
779 mUserEntries
.InsertElementSorted(aEntry
, PerformanceEntryComparator());
785 * Steps are labeled according to the description found at
786 * https://w3c.github.io/resource-timing/#sec-extensions-performance-interface.
790 void Performance::BufferEvent() {
792 * While resource timing secondary buffer is not empty,
793 * run the following substeps:
795 while (!mSecondaryResourceEntries
.IsEmpty()) {
796 uint32_t secondaryResourceEntriesBeforeCount
= 0;
797 uint32_t secondaryResourceEntriesAfterCount
= 0;
800 * Let number of excess entries before be resource
801 * timing secondary buffer current size.
803 secondaryResourceEntriesBeforeCount
= mSecondaryResourceEntries
.Length();
806 * If can add resource timing entry returns false,
807 * then fire an event named resourcetimingbufferfull
808 * at the Performance object.
810 if (!CanAddResourceTimingEntry()) {
811 DispatchBufferFullEvent();
815 * Run copy secondary buffer.
817 * While resource timing secondary buffer is not
818 * empty and can add resource timing entry returns
821 while (!mSecondaryResourceEntries
.IsEmpty() &&
822 CanAddResourceTimingEntry()) {
824 * Let entry be the oldest PerformanceResourceTiming
825 * in resource timing secondary buffer. Add entry to
826 * the end of performance entry buffer. Increment
827 * resource timing buffer current size by 1.
829 mResourceEntries
.InsertElementSorted(
830 mSecondaryResourceEntries
.ElementAt(0), PerformanceEntryComparator());
832 * Remove entry from resource timing secondary buffer.
833 * Decrement resource timing secondary buffer current
836 mSecondaryResourceEntries
.RemoveElementAt(0);
840 * Let number of excess entries after be resource
841 * timing secondary buffer current size.
843 secondaryResourceEntriesAfterCount
= mSecondaryResourceEntries
.Length();
846 * If number of excess entries before is lower than
847 * or equals number of excess entries after, then
848 * remove all entries from resource timing secondary
849 * buffer, set resource timing secondary buffer current
850 * size to 0, and abort these steps.
852 if (secondaryResourceEntriesBeforeCount
<=
853 secondaryResourceEntriesAfterCount
) {
854 mSecondaryResourceEntries
.Clear();
859 * Set resource timing buffer full event pending flag
862 mPendingResourceTimingBufferFullEvent
= false;
865 void Performance::SetResourceTimingBufferSize(uint64_t aMaxSize
) {
866 mResourceTimingBufferSize
= aMaxSize
;
870 * Steps are labeled according to the description found at
871 * https://w3c.github.io/resource-timing/#sec-extensions-performance-interface.
873 * Can Add Resource Timing Entry
875 MOZ_ALWAYS_INLINE
bool Performance::CanAddResourceTimingEntry() {
877 * If resource timing buffer current size is smaller than resource timing
878 * buffer size limit, return true. [Otherwise,] [r]eturn false.
880 return mResourceEntries
.Length() < mResourceTimingBufferSize
;
884 * Steps are labeled according to the description found at
885 * https://w3c.github.io/resource-timing/#sec-extensions-performance-interface.
887 * Add a PerformanceResourceTiming Entry
889 void Performance::InsertResourceEntry(PerformanceEntry
* aEntry
) {
895 * Let new entry be the input PerformanceEntry to be added.
897 * If can add resource timing entry returns true and resource
898 * timing buffer full event pending flag is false ...
900 if (CanAddResourceTimingEntry() && !mPendingResourceTimingBufferFullEvent
) {
902 * Add new entry to the performance entry buffer.
903 * Increase resource timing buffer current size by 1.
905 mResourceEntries
.InsertElementSorted(aEntry
, PerformanceEntryComparator());
910 * If resource timing buffer full event pending flag is
913 if (!mPendingResourceTimingBufferFullEvent
) {
915 * Set resource timing buffer full event pending flag
918 mPendingResourceTimingBufferFullEvent
= true;
921 * Queue a task to run fire a buffer full event.
923 NS_DispatchToCurrentThread(NewCancelableRunnableMethod(
924 "Performance::BufferEvent", this, &Performance::BufferEvent
));
927 * Add new entry to the resource timing secondary buffer.
928 * Increase resource timing secondary buffer current size
931 mSecondaryResourceEntries
.InsertElementSorted(aEntry
,
932 PerformanceEntryComparator());
935 void Performance::AddObserver(PerformanceObserver
* aObserver
) {
936 mObservers
.AppendElementUnlessExists(aObserver
);
939 void Performance::RemoveObserver(PerformanceObserver
* aObserver
) {
940 mObservers
.RemoveElement(aObserver
);
943 void Performance::NotifyObservers() {
944 mPendingNotificationObserversTask
= false;
945 NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mObservers
, Notify
, ());
948 void Performance::CancelNotificationObservers() {
949 mPendingNotificationObserversTask
= false;
952 class NotifyObserversTask final
: public CancelableRunnable
{
954 explicit NotifyObserversTask(Performance
* aPerformance
)
955 : CancelableRunnable("dom::NotifyObserversTask"),
956 mPerformance(aPerformance
) {
957 MOZ_ASSERT(mPerformance
);
960 // MOZ_CAN_RUN_SCRIPT_BOUNDARY for now until Runnable::Run is
961 // MOZ_CAN_RUN_SCRIPT.
962 MOZ_CAN_RUN_SCRIPT_BOUNDARY
963 NS_IMETHOD
Run() override
{
964 MOZ_ASSERT(mPerformance
);
965 RefPtr
<Performance
> performance(mPerformance
);
966 performance
->NotifyObservers();
970 nsresult
Cancel() override
{
971 mPerformance
->CancelNotificationObservers();
972 mPerformance
= nullptr;
977 ~NotifyObserversTask() = default;
979 RefPtr
<Performance
> mPerformance
;
982 void Performance::QueueNotificationObserversTask() {
983 if (!mPendingNotificationObserversTask
) {
984 RunNotificationObserversTask();
988 void Performance::RunNotificationObserversTask() {
989 mPendingNotificationObserversTask
= true;
990 nsCOMPtr
<nsIRunnable
> task
= new NotifyObserversTask(this);
992 if (nsIGlobalObject
* global
= GetOwnerGlobal()) {
993 rv
= global
->Dispatch(task
.forget());
995 rv
= NS_DispatchToCurrentThread(task
.forget());
997 if (NS_WARN_IF(NS_FAILED(rv
))) {
998 mPendingNotificationObserversTask
= false;
1002 void Performance::QueueEntry(PerformanceEntry
* aEntry
) {
1003 nsTObserverArray
<PerformanceObserver
*> interestedObservers
;
1004 if (!mObservers
.IsEmpty()) {
1005 const auto [begin
, end
] = mObservers
.NonObservingRange();
1006 std::copy_if(begin
, end
, MakeBackInserter(interestedObservers
),
1007 [aEntry
](PerformanceObserver
* observer
) {
1008 return observer
->ObservesTypeOfEntry(aEntry
);
1012 NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(interestedObservers
, QueueEntry
,
1015 aEntry
->BufferEntryIfNeeded();
1017 if (!interestedObservers
.IsEmpty()) {
1018 QueueNotificationObserversTask();
1022 // We could clear User entries here, but doing so could break sites that call
1023 // performance.measure() if the marks disappeared without warning. Chrome
1024 // allows "infinite" entries.
1025 void Performance::MemoryPressure() {}
1027 size_t Performance::SizeOfUserEntries(
1028 mozilla::MallocSizeOf aMallocSizeOf
) const {
1029 size_t userEntries
= 0;
1030 for (const PerformanceEntry
* entry
: mUserEntries
) {
1031 userEntries
+= entry
->SizeOfIncludingThis(aMallocSizeOf
);
1036 size_t Performance::SizeOfResourceEntries(
1037 mozilla::MallocSizeOf aMallocSizeOf
) const {
1038 size_t resourceEntries
= 0;
1039 for (const PerformanceEntry
* entry
: mResourceEntries
) {
1040 resourceEntries
+= entry
->SizeOfIncludingThis(aMallocSizeOf
);
1042 return resourceEntries
;
1045 } // namespace mozilla::dom