Issue #2053 - Part 2: Update PerformanceMeasure to User Timing L3

There are a few minor differences between this and Mozilla's implementation:
(a) The type error messages were not hardcoded to the Performance class and were moved to Errors.msg instead.
(b) PerformanceMeasureOptions is used directly and doesn't use the the Maybe<> container class. I haven't found the reason yet, but PerformanceMeasureOptions is disallowed from having a copy constructor and without that, it isn't possible to use it with Maybe<>... There doesn't seem to be any problem with using it directly, though.
(c) Resist fingerprinting-pref changes were skipped.

Partially based on https://bugzilla.mozilla.org/show_bug.cgi?id=1762482
This commit is contained in:
FranklinDM 2023-04-05 22:22:34 +08:00 committed by roytam1
commit 23519e0c22
7 changed files with 278 additions and 43 deletions

View file

@ -20,6 +20,7 @@
#include "mozilla/dom/PerformanceNavigationBinding.h"
#include "mozilla/dom/PerformanceObserverBinding.h"
#include "mozilla/dom/PerformanceNavigationTiming.h"
#include "mozilla/dom/MessagePortBinding.h"
#include "mozilla/IntegerPrintfMacros.h"
#include "mozilla/Preferences.h"
#include "mozilla/TimerClamping.h"
@ -65,6 +66,12 @@ private:
} // anonymous namespace
enum class Performance::ResolveTimestampAttribute {
Start,
End,
Duration,
};
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(Performance)
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
@ -257,8 +264,8 @@ Performance::ClearMarks(const Optional<nsAString>& aName)
}
DOMHighResTimeStamp
Performance::ResolveTimestampFromName(const nsAString& aName,
ErrorResult& aRv)
Performance::ConvertMarkToTimestampWithString(const nsAString& aName,
ErrorResult& aRv)
{
AutoTArray<RefPtr<PerformanceEntry>, 1> arr;
DOMHighResTimeStamp ts;
@ -272,7 +279,7 @@ Performance::ResolveTimestampFromName(const nsAString& aName,
}
if (!IsPerformanceTimingAttribute(aName)) {
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
aRv.ThrowTypeError<MSG_PMO_UNKNOWN_MARK_NAME>(aName);
return 0;
}
@ -285,50 +292,192 @@ Performance::ResolveTimestampFromName(const nsAString& aName,
return ts - CreationTime();
}
void
Performance::Measure(const nsAString& aName,
const Optional<nsAString>& aStartMark,
DOMHighResTimeStamp
Performance::ConvertMarkToTimestampWithDOMHighResTimeStamp(
const ResolveTimestampAttribute aAttribute,
const DOMHighResTimeStamp aTimestamp,
ErrorResult& aRv)
{
if (aTimestamp < 0) {
nsAutoString attributeName;
switch (aAttribute) {
case ResolveTimestampAttribute::Start:
attributeName = NS_LITERAL_STRING("start");
break;
case ResolveTimestampAttribute::End:
attributeName = NS_LITERAL_STRING("end");
break;
case ResolveTimestampAttribute::Duration:
attributeName = NS_LITERAL_STRING("duration");
break;
}
aRv.ThrowTypeError<MSG_NO_NEGATIVE_ATTR>(attributeName);
}
return aTimestamp;
}
DOMHighResTimeStamp
Performance::ConvertMarkToTimestamp(
const ResolveTimestampAttribute aAttribute,
const OwningStringOrDouble& aMarkNameOrTimestamp,
ErrorResult& aRv)
{
if (aMarkNameOrTimestamp.IsString()) {
return ConvertMarkToTimestampWithString(aMarkNameOrTimestamp.GetAsString(),
aRv);
}
return ConvertMarkToTimestampWithDOMHighResTimeStamp(
aAttribute, aMarkNameOrTimestamp.GetAsDouble(), aRv);
}
DOMHighResTimeStamp
Performance::ResolveEndTimeForMeasure(
const Optional<nsAString>& aEndMark,
const PerformanceMeasureOptions* aOptions,
ErrorResult& aRv)
{
DOMHighResTimeStamp endTime;
if (aEndMark.WasPassed()) {
endTime = ConvertMarkToTimestampWithString(aEndMark.Value(), aRv);
} else if (aOptions != nullptr && aOptions->mEnd.WasPassed()) {
endTime = ConvertMarkToTimestamp(ResolveTimestampAttribute::End,
aOptions->mEnd.Value(), aRv);
} else if (aOptions != nullptr && aOptions->mStart.WasPassed() &&
aOptions->mDuration.WasPassed()) {
const DOMHighResTimeStamp start = ConvertMarkToTimestamp(
ResolveTimestampAttribute::Start, aOptions->mStart.Value(), aRv);
if (aRv.Failed()) {
return 0;
}
const DOMHighResTimeStamp duration =
ConvertMarkToTimestampWithDOMHighResTimeStamp(
ResolveTimestampAttribute::Duration,
aOptions->mDuration.Value(),
aRv);
if (aRv.Failed()) {
return 0;
}
endTime = start + duration;
} else {
endTime = Now();
}
return endTime;
}
DOMHighResTimeStamp
Performance::ResolveStartTimeForMeasure(
const nsAString* aStartMark,
const PerformanceMeasureOptions* aOptions,
ErrorResult& aRv)
{
DOMHighResTimeStamp startTime;
if (aOptions != nullptr && aOptions->mStart.WasPassed()) {
startTime = ConvertMarkToTimestamp(ResolveTimestampAttribute::Start,
aOptions->mStart.Value(),
aRv);
} else if (aOptions != nullptr && aOptions->mDuration.WasPassed() &&
aOptions->mEnd.WasPassed()) {
const DOMHighResTimeStamp duration =
ConvertMarkToTimestampWithDOMHighResTimeStamp(
ResolveTimestampAttribute::Duration,
aOptions->mDuration.Value(),
aRv);
if (aRv.Failed()) {
return 0;
}
const DOMHighResTimeStamp end = ConvertMarkToTimestamp(
ResolveTimestampAttribute::End, aOptions->mEnd.Value(), aRv);
if (aRv.Failed()) {
return 0;
}
startTime = end - duration;
} else if (aStartMark != nullptr) {
startTime = ConvertMarkToTimestampWithString(*aStartMark, aRv);
} else {
startTime = 0;
}
return startTime;
}
already_AddRefed<PerformanceMeasure>
Performance::Measure(JSContext* aCx,
const nsAString& aName,
const StringOrPerformanceMeasureOptions& aStartOrMeasureOptions,
const Optional<nsAString>& aEndMark,
ErrorResult& aRv)
{
// Don't add the entry if the buffer is full. XXX should be removed by bug
// 1159003.
if (mUserEntries.Length() >= mResourceTimingBufferSize) {
return;
return nullptr;
}
DOMHighResTimeStamp startTime;
DOMHighResTimeStamp endTime;
if (IsPerformanceTimingAttribute(aName)) {
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
return;
const PerformanceMeasureOptions* options = nullptr;
if (aStartOrMeasureOptions.IsPerformanceMeasureOptions()) {
options = &aStartOrMeasureOptions.GetAsPerformanceMeasureOptions();
}
if (aStartMark.WasPassed()) {
startTime = ResolveTimestampFromName(aStartMark.Value(), aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
const bool isOptionsNotEmpty =
(options != nullptr) &&
(!options->mDetail.isUndefined() || options->mStart.WasPassed() ||
options->mEnd.WasPassed() || options->mDuration.WasPassed());
if (isOptionsNotEmpty) {
if (aEndMark.WasPassed()) {
aRv.ThrowTypeError<MSG_PMO_NO_SEPARATE_ENDMARK>();
return nullptr;
}
if (!options->mStart.WasPassed() && !options->mEnd.WasPassed()) {
aRv.ThrowTypeError<MSG_PMO_MISSING_STARTENDMARK>();
return nullptr;
}
if (options->mStart.WasPassed() && options->mDuration.WasPassed() &&
options->mEnd.WasPassed()) {
aRv.ThrowTypeError<MSG_PMO_INVALID_MEMBERS>();
return nullptr;
}
}
const DOMHighResTimeStamp endTime =
ResolveEndTimeForMeasure(aEndMark, options, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
const nsAString* startMark = nullptr;
if (aStartOrMeasureOptions.IsString()) {
startMark = &aStartOrMeasureOptions.GetAsString();
}
const DOMHighResTimeStamp startTime =
ResolveStartTimeForMeasure(startMark, options, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
JS::Rooted<JS::Value> detail(aCx);
if (options != nullptr && !options->mDetail.isNullOrUndefined()) {
StructuredSerializeOptions serializeOptions;
JS::Rooted<JS::Value> valueToClone(aCx, options->mDetail);
nsContentUtils::StructuredClone(aCx, GetParentObject(), valueToClone,
serializeOptions, &detail, aRv);
if (aRv.Failed()) {
return nullptr;
}
} else {
// Navigation start is used in this case, but since DOMHighResTimeStamp is
// in relation to navigation start, this will be zero if a name is not
// passed.
startTime = 0;
detail.setNull();
}
if (aEndMark.WasPassed()) {
endTime = ResolveTimestampFromName(aEndMark.Value(), aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
} else {
endTime = Now();
}
RefPtr<PerformanceMeasure> performanceMeasure =
new PerformanceMeasure(GetAsISupports(), aName, startTime, endTime);
RefPtr<PerformanceMeasure> performanceMeasure = new PerformanceMeasure(
GetAsISupports(), aName, startTime, endTime, detail);
InsertUserEntry(performanceMeasure);
return performanceMeasure.forget();
}
void