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
parent 995f3117b0
commit 23519e0c22
7 changed files with 281 additions and 46 deletions
+5
View File
@@ -100,3 +100,8 @@ MSG_DEF(MSG_TIME_VALUE_OUT_OF_RANGE, 1, JSEXN_TYPEERR, "{0} is outside the suppo
MSG_DEF(MSG_ONLY_IF_CACHED_WITHOUT_SAME_ORIGIN, 1, JSEXN_TYPEERR, "Request mode '{0}' was used, but request cache mode 'only-if-cached' can only be used with request mode 'same-origin'.")
MSG_DEF(MSG_THRESHOLD_RANGE_ERROR, 0, JSEXN_RANGEERR, "Threshold values must all be in the range [0, 1].")
MSG_DEF(MSG_CACHE_OPEN_FAILED, 0, JSEXN_TYPEERR, "CacheStorage.open() failed to access the storage system.")
MSG_DEF(MSG_NO_NEGATIVE_ATTR, 1, JSEXN_TYPEERR, "Given attribute {0} cannot be negative.")
MSG_DEF(MSG_PMO_NO_SEPARATE_ENDMARK, 0, JSEXN_TYPEERR, "Cannot provide separate endMark argument if PerformanceMeasureOptions argument is given.")
MSG_DEF(MSG_PMO_MISSING_STARTENDMARK, 0, JSEXN_TYPEERR, "PerformanceMeasureOptions must have start and/or end member.")
MSG_DEF(MSG_PMO_INVALID_MEMBERS, 0, JSEXN_TYPEERR, "PerformanceMeasureOptions cannot have all of the following members: start, duration, and end.")
MSG_DEF(MSG_PMO_UNKNOWN_MARK_NAME, 1, JSEXN_SYNTAXERR, "Given mark name, {0}, is unknown.")
+181 -32
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
+32 -7
View File
@@ -20,11 +20,16 @@ class ErrorResult;
namespace dom {
class OwningStringOrDouble;
class StringOrPerformanceMeasureOptions;
class PerformanceEntry;
struct PerformanceMeasureOptions;
class PerformanceMeasure;
class PerformanceNavigation;
class PerformanceObserver;
class PerformanceService;
class PerformanceTiming;
struct StructuredSerializeOptions;
namespace workers {
class WorkerPrivate;
@@ -75,10 +80,10 @@ public:
void ClearMarks(const Optional<nsAString>& aName);
void Measure(const nsAString& aName,
const Optional<nsAString>& aStartMark,
const Optional<nsAString>& aEndMark,
ErrorResult& aRv);
already_AddRefed<PerformanceMeasure> Measure(
JSContext* aCx, const nsAString& aName,
const StringOrPerformanceMeasureOptions& aStartOrMeasureOptions,
const Optional<nsAString>& aEndMark, ErrorResult& aRv);
void ClearMeasures(const Optional<nsAString>& aName);
@@ -116,9 +121,6 @@ protected:
void ClearUserEntries(const Optional<nsAString>& aEntryName,
const nsAString& aEntryType);
DOMHighResTimeStamp ResolveTimestampFromName(const nsAString& aName,
ErrorResult& aRv);
virtual nsISupports* GetAsISupports() = 0;
virtual void DispatchBufferFullEvent() = 0;
@@ -163,6 +165,29 @@ protected:
bool mPendingNotificationObserversTask;
RefPtr<PerformanceService> mPerformanceService;
private:
// The attributes of a PerformanceMeasureOptions that we call
// ResolveTimestamp* on.
enum class ResolveTimestampAttribute;
DOMHighResTimeStamp ConvertMarkToTimestampWithString(const nsAString& aName,
ErrorResult& aRv);
DOMHighResTimeStamp ConvertMarkToTimestampWithDOMHighResTimeStamp(
const ResolveTimestampAttribute aAttribute, const double aTimestamp,
ErrorResult& aRv);
DOMHighResTimeStamp ConvertMarkToTimestamp(
const ResolveTimestampAttribute aAttribute,
const OwningStringOrDouble& aMarkNameOrTimestamp, ErrorResult& aRv);
DOMHighResTimeStamp ResolveEndTimeForMeasure(
const Optional<nsAString>& aEndMark,
const PerformanceMeasureOptions* aOptions,
ErrorResult& aRv);
DOMHighResTimeStamp ResolveStartTimeForMeasure(
const nsAString* aStartMark,
const PerformanceMeasureOptions* aOptions,
ErrorResult& aRv);
};
} // namespace dom
+40 -5
View File
@@ -12,10 +12,12 @@ using namespace mozilla::dom;
PerformanceMeasure::PerformanceMeasure(nsISupports* aParent,
const nsAString& aName,
DOMHighResTimeStamp aStartTime,
DOMHighResTimeStamp aEndTime)
: PerformanceEntry(aParent, aName, NS_LITERAL_STRING("measure")),
mStartTime(aStartTime),
mDuration(aEndTime - aStartTime)
DOMHighResTimeStamp aEndTime,
const JS::Handle<JS::Value>& aDetail)
: PerformanceEntry(aParent, aName, NS_LITERAL_STRING("measure"))
, mStartTime(aStartTime)
, mDuration(aEndTime - aStartTime)
, mDetail(aDetail)
{
// mParent is null in workers.
MOZ_ASSERT(mParent || !NS_IsMainThread());
@@ -23,10 +25,43 @@ PerformanceMeasure::PerformanceMeasure(nsISupports* aParent,
PerformanceMeasure::~PerformanceMeasure()
{
mozilla::DropJSObjects(this);
}
NS_IMPL_CYCLE_COLLECTION_CLASS(PerformanceMeasure)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(PerformanceMeasure,
PerformanceEntry)
tmp->mDetail.setUndefined();
mozilla::DropJSObjects(tmp);
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PerformanceMeasure,
PerformanceEntry)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(PerformanceMeasure,
PerformanceEntry)
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mDetail)
NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PerformanceMeasure)
NS_INTERFACE_MAP_END_INHERITING(PerformanceEntry)
NS_IMPL_ADDREF_INHERITED(PerformanceMeasure, PerformanceEntry)
NS_IMPL_RELEASE_INHERITED(PerformanceMeasure, PerformanceEntry)
JSObject*
PerformanceMeasure::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
PerformanceMeasure::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto)
{
return PerformanceMeasureBinding::Wrap(aCx, this, aGivenProto);
}
void
PerformanceMeasure::GetDetail(JSContext* aCx,
JS::MutableHandle<JS::Value> aRv)
{
// Return a copy so that this method always returns the value it is set to
// (i.e. it'll return the same value even if the caller assigns to it). Note
// that if detail is an object, its contents can be mutated and this is
// expected.
aRv.set(mDetail);
}
+11 -1
View File
@@ -15,10 +15,15 @@ namespace dom {
class PerformanceMeasure final : public PerformanceEntry
{
public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(PerformanceMeasure,
PerformanceEntry);
PerformanceMeasure(nsISupports* aParent,
const nsAString& aName,
DOMHighResTimeStamp aStartTime,
DOMHighResTimeStamp aEndTime);
DOMHighResTimeStamp aEndTime,
const JS::Handle<JS::Value>& aDetail);
virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
@@ -32,10 +37,15 @@ public:
return mDuration;
}
void GetDetail(JSContext* aCx, JS::MutableHandle<JS::Value> aRv);
protected:
virtual ~PerformanceMeasure();
DOMHighResTimeStamp mStartTime;
DOMHighResTimeStamp mDuration;
private:
JS::Heap<JS::Value> mDetail;
};
} // namespace dom
+11 -1
View File
@@ -44,6 +44,14 @@ partial interface Performance {
entryType);
};
// https://w3c.github.io/user-timing/#extensions-performance-interface
dictionary PerformanceMeasureOptions {
any detail;
(DOMString or DOMHighResTimeStamp) start;
DOMHighResTimeStamp duration;
(DOMString or DOMHighResTimeStamp)end;
};
// http://www.w3.org/TR/resource-timing/#extensions-performance-interface
[Exposed=Window]
partial interface Performance {
@@ -72,7 +80,9 @@ partial interface Performance {
[Func="Performance::IsEnabled"]
void clearMarks(optional DOMString markName);
[Func="Performance::IsEnabled", Throws]
void measure(DOMString measureName, optional DOMString startMark, optional DOMString endMark);
PerformanceMeasure measure(DOMString measureName,
optional (DOMString or PerformanceMeasureOptions) startOrMeasureOptions,
optional DOMString endMark);
[Func="Performance::IsEnabled"]
void clearMeasures(optional DOMString measureName);
};
+1
View File
@@ -10,4 +10,5 @@
[Exposed=(Window,Worker)]
interface PerformanceMeasure : PerformanceEntry
{
readonly attribute any detail;
};