diff options
author | Moonchild <moonchild@palemoon.org> | 2023-04-11 21:32:22 +0000 |
---|---|---|
committer | Moonchild <moonchild@palemoon.org> | 2023-04-11 21:32:22 +0000 |
commit | c95e970df58fafc176cdeedac12c1f6199c301cd (patch) | |
tree | 481b67d375e2eae59319f3caa7a90adc2a92932a | |
parent | ff29aa37a86fcbc97cba6870d7bdb50020b442ae (diff) | |
parent | 9e976ddf7f2591a8abf4b79baf58627939a7c5eb (diff) | |
download | uxp-c95e970df58fafc176cdeedac12c1f6199c301cd.tar.gz |
Merge pull request 'Update Performance API to User Timing L3' (#2208) from FranklinDM/UXP-contrib:work_js-user-timing-l3 into master
Reviewed-on: https://repo.palemoon.org/MoonchildProductions/UXP/pulls/2208
-rw-r--r-- | dom/base/domerr.msg | 6 | ||||
-rw-r--r-- | dom/bindings/Bindings.conf | 6 | ||||
-rw-r--r-- | dom/bindings/Errors.msg | 7 | ||||
-rw-r--r-- | dom/bindings/Exceptions.cpp | 1 | ||||
-rwxr-xr-x | dom/performance/Performance.cpp | 314 | ||||
-rw-r--r-- | dom/performance/Performance.h | 64 | ||||
-rw-r--r-- | dom/performance/PerformanceMainThread.cpp | 22 | ||||
-rw-r--r-- | dom/performance/PerformanceMainThread.h | 7 | ||||
-rw-r--r-- | dom/performance/PerformanceMark.cpp | 96 | ||||
-rw-r--r-- | dom/performance/PerformanceMark.h | 28 | ||||
-rw-r--r-- | dom/performance/PerformanceMeasure.cpp | 45 | ||||
-rw-r--r-- | dom/performance/PerformanceMeasure.h | 12 | ||||
-rw-r--r-- | dom/webidl/Performance.webidl | 22 | ||||
-rw-r--r-- | dom/webidl/PerformanceMark.webidl | 3 | ||||
-rw-r--r-- | dom/webidl/PerformanceMeasure.webidl | 1 | ||||
-rw-r--r-- | xpcom/base/ErrorList.h | 10 | ||||
-rw-r--r-- | xpcom/base/nsError.h | 1 |
17 files changed, 552 insertions, 93 deletions
diff --git a/dom/base/domerr.msg b/dom/base/domerr.msg index 9c84f42a3f..baf1f05735 100644 --- a/dom/base/domerr.msg +++ b/dom/base/domerr.msg @@ -157,6 +157,12 @@ DOM4_MSG_DEF(NotSupportedError, "The media resource indicated by the src attribu DOM4_MSG_DEF(SyntaxError, "The URI is malformed.", NS_ERROR_DOM_MALFORMED_URI) DOM4_MSG_DEF(SyntaxError, "Invalid header name.", NS_ERROR_DOM_INVALID_HEADER_NAME) +/* User Timing API errors */ +DOM4_MSG_DEF(SyntaxError, "An unknown mark name was provided.", NS_ERROR_DOM_UT_UNKNOWN_MARK_NAME) +DOM4_MSG_DEF(SyntaxError, "markName cannot be a performance timing attribute.", NS_ERROR_DOM_UT_INVALID_TIMING_ATTR) +DOM4_MSG_DEF(InvalidAccessError, "A PerformanceTiming attribute which isn't available yet was provided.", NS_ERROR_DOM_UT_UNAVAILABLE_ATTR) +DOM4_MSG_DEF(InvalidStateError, "Global object is unavailable.", NS_ERROR_DOM_UT_UNAVAILABLE_GLOBAL_OBJECT) + /* XMLHttpRequest errors. */ DOM4_MSG_DEF(InvalidStateError, "XMLHttpRequest has an invalid context.", NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT) DOM4_MSG_DEF(InvalidStateError, "XMLHttpRequest state must be OPENED.", NS_ERROR_DOM_INVALID_STATE_XHR_MUST_BE_OPENED) diff --git a/dom/bindings/Bindings.conf b/dom/bindings/Bindings.conf index 146bf8d15b..8b13babdbb 100644 --- a/dom/bindings/Bindings.conf +++ b/dom/bindings/Bindings.conf @@ -642,6 +642,12 @@ DOMInterfaces = { 'wrapperCache': False }, +'Performance' : { + 'implicitJSContext': [ + 'mark' + ], +}, + 'Plugin': { 'headerFile' : 'nsPluginArray.h', 'nativeType': 'nsPluginElement', diff --git a/dom/bindings/Errors.msg b/dom/bindings/Errors.msg index c47f758750..c894c6c7b4 100644 --- a/dom/bindings/Errors.msg +++ b/dom/bindings/Errors.msg @@ -100,3 +100,10 @@ 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_CONSTRUCTOR_INACCESSIBLE, 0, JSEXN_TYPEERR, "Can't access PerformanceMark constructor, performance is null.") +MSG_DEF(MSG_PMO_UNEXPECTED_START_TIME, 0, JSEXN_TYPEERR, "Expected startTime >= 0.") +MSG_DEF(MSG_PMO_INVALID_ATTR_FOR_NON_GLOBAL, 1, JSEXN_TYPEERR, "Cannot get PerformanceTiming attribute values for non-Window global object. Given: {0}.") diff --git a/dom/bindings/Exceptions.cpp b/dom/bindings/Exceptions.cpp index 1785010cc5..1d361740a2 100644 --- a/dom/bindings/Exceptions.cpp +++ b/dom/bindings/Exceptions.cpp @@ -195,6 +195,7 @@ CreateException(JSContext* aCx, nsresult aRv, const nsACString& aMessage) case NS_ERROR_MODULE_DOM_ANIM: case NS_ERROR_MODULE_DOM_PUSH: case NS_ERROR_MODULE_DOM_MEDIA: + case NS_ERROR_MODULE_DOM_USER_TIMING: if (aMessage.IsEmpty()) { return DOMException::Create(aRv); } diff --git a/dom/performance/Performance.cpp b/dom/performance/Performance.cpp index 020dc0d89a..9a1d134626 100755 --- a/dom/performance/Performance.cpp +++ b/dom/performance/Performance.cpp @@ -20,11 +20,13 @@ #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" #include "WorkerPrivate.h" #include "WorkerRunnable.h" +#include "WorkerScope.h" #define PERFLOG(msg, ...) printf_stderr(msg, ##__VA_ARGS__) @@ -65,6 +67,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) @@ -98,6 +106,27 @@ Performance::CreateForWorker(workers::WorkerPrivate* aWorkerPrivate) return performance.forget(); } +/* static */ +already_AddRefed<Performance> Performance::Get(JSContext* aCx, + nsIGlobalObject* aGlobal) { + RefPtr<Performance> performance; + nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal); + if (window) { + performance = window->GetPerformance(); + } else { + const WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx); + if (!workerPrivate) { + return nullptr; + } + + WorkerGlobalScope* scope = workerPrivate->GlobalScope(); + MOZ_ASSERT(scope); + performance = scope->GetPerformance(); + } + + return performance.forget(); +} + Performance::Performance() : mResourceTimingBufferSize(kDefaultResourceTimingBufferSize) , mPendingNotificationObserversTask(false) @@ -227,27 +256,42 @@ Performance::RoundTime(double aTime) const return floor(aTime / maxResolutionMs) * maxResolutionMs; } - -void -Performance::Mark(const nsAString& aName, ErrorResult& aRv) +already_AddRefed<PerformanceMark> Performance::Mark( + JSContext* aCx, + const nsAString& aName, + const PerformanceMarkOptions& aMarkOptions, + 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; } - if (IsPerformanceTimingAttribute(aName)) { - aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); - return; + nsCOMPtr<nsIGlobalObject> parent = GetParentObject(); + if (!parent || parent->IsDying() || !parent->GetGlobalJSObject()) { + aRv.Throw(NS_ERROR_DOM_UT_UNAVAILABLE_GLOBAL_OBJECT); + return nullptr; + } + + GlobalObject global(aCx, parent->GetGlobalJSObject()); + if (global.Failed()) { + aRv.Throw(NS_ERROR_DOM_UT_UNAVAILABLE_GLOBAL_OBJECT); + return nullptr; } RefPtr<PerformanceMark> performanceMark = - new PerformanceMark(GetAsISupports(), aName, Now()); + PerformanceMark::Constructor(global, aName, aMarkOptions, aRv); + if (aRv.Failed()) { + return nullptr; + } + InsertUserEntry(performanceMark); if (profiler_is_active()) { PROFILER_MARKER(NS_ConvertUTF16toUTF8(aName).get()); } + + return performanceMark.forget(); } void @@ -256,12 +300,37 @@ Performance::ClearMarks(const Optional<nsAString>& aName) ClearUserEntries(aName, NS_LITERAL_STRING("mark")); } +// To be removed once bug 1124165 lands +bool +Performance::IsPerformanceTimingAttribute(const nsAString& aName) const +{ + // Note that toJSON is added to this list due to bug 1047848 + static const char* attributes[] = + {"navigationStart", "unloadEventStart", "unloadEventEnd", "redirectStart", + "redirectEnd", "fetchStart", "domainLookupStart", "domainLookupEnd", + "connectStart", "secureConnectionStart", "connectEnd", "requestStart", "responseStart", + "responseEnd", "domLoading", "domInteractive", + "domContentLoadedEventStart", "domContentLoadedEventEnd", "domComplete", + "loadEventStart", "loadEventEnd", nullptr}; + + for (uint32_t i = 0; attributes[i]; ++i) { + if (aName.EqualsASCII(attributes[i])) { + return true; + } + } + + return false; +} + DOMHighResTimeStamp -Performance::ResolveTimestampFromName(const nsAString& aName, - ErrorResult& aRv) +Performance::ConvertMarkToTimestampWithString(const nsAString& aName, + ErrorResult& aRv) { + if (IsPerformanceTimingAttribute(aName)) { + return ConvertNameToTimestamp(aName, aRv); + } + AutoTArray<RefPtr<PerformanceEntry>, 1> arr; - DOMHighResTimeStamp ts; Optional<nsAString> typeParam; nsAutoString str; str.AssignLiteral("mark"); @@ -271,64 +340,223 @@ Performance::ResolveTimestampFromName(const nsAString& aName, return arr.LastElement()->StartTime(); } - if (!IsPerformanceTimingAttribute(aName)) { - aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); + aRv.Throw(NS_ERROR_DOM_UT_UNKNOWN_MARK_NAME); + return 0; +} + +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::ConvertNameToTimestamp(const nsAString& aName, + ErrorResult& aRv) { + if (!IsGlobalObjectWindow()) { + aRv.ThrowTypeError<MSG_PMO_INVALID_ATTR_FOR_NON_GLOBAL>(aName); return 0; } - ts = GetPerformanceTimingFromString(aName); - if (!ts) { - aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR); + if (aName.EqualsASCII("navigationStart")) { return 0; } - return ts - CreationTime(); + // We use GetPerformanceTimingFromString, rather than calling the + // navigationStart method timing function directly, because the former handles + // reducing precision against timing attacks. + const DOMHighResTimeStamp startTime = + GetPerformanceTimingFromString(NS_LITERAL_STRING("navigationStart")); + const DOMHighResTimeStamp endTime = + GetPerformanceTimingFromString(aName); + MOZ_ASSERT(endTime >= 0); + if (endTime == 0) { + aRv.Throw(NS_ERROR_DOM_UT_UNAVAILABLE_ATTR); + return 0; + } + + return endTime - startTime; } -void -Performance::Measure(const nsAString& aName, - const Optional<nsAString>& aStartMark, +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; } - } 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; } - if (aEndMark.WasPassed()) { - endTime = ResolveTimestampFromName(aEndMark.Value(), aRv); - if (NS_WARN_IF(aRv.Failed())) { - return; + 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 { - endTime = Now(); + detail.setNull(); } - 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 diff --git a/dom/performance/Performance.h b/dom/performance/Performance.h index 2574fcdd3e..1b2bb5586c 100644 --- a/dom/performance/Performance.h +++ b/dom/performance/Performance.h @@ -20,11 +20,18 @@ class ErrorResult; namespace dom { +class OwningStringOrDouble; +class StringOrPerformanceMeasureOptions; class PerformanceEntry; +class PerformanceMark; +struct PerformanceMarkOptions; +struct PerformanceMeasureOptions; +class PerformanceMeasure; class PerformanceNavigation; class PerformanceObserver; class PerformanceService; class PerformanceTiming; +struct StructuredSerializeOptions; namespace workers { class WorkerPrivate; @@ -50,6 +57,10 @@ public: static already_AddRefed<Performance> CreateForWorker(workers::WorkerPrivate* aWorkerPrivate); + // This will return nullptr if called outside of a Window or Worker. + static already_AddRefed<Performance> Get(JSContext* aCx, + nsIGlobalObject* aGlobal); + JSObject* WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto) override; @@ -71,14 +82,16 @@ public: DOMHighResTimeStamp TimeOrigin(); - void Mark(const nsAString& aName, ErrorResult& aRv); + already_AddRefed<PerformanceMark> Mark( + JSContext* aCx, const nsAString& aName, + const PerformanceMarkOptions& aMarkOptions, ErrorResult& aRv); 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); @@ -104,6 +117,13 @@ public: virtual nsITimedChannel* GetChannel() const = 0; + bool IsPerformanceTimingAttribute(const nsAString& aName) const; + + virtual bool IsGlobalObjectWindow() const + { + return false; + } + protected: Performance(); explicit Performance(nsPIDOMWindowInner* aWindow); @@ -116,9 +136,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; @@ -127,11 +144,6 @@ protected: virtual DOMHighResTimeStamp CreationTime() const = 0; - virtual bool IsPerformanceTimingAttribute(const nsAString& aName) - { - return false; - } - virtual DOMHighResTimeStamp GetPerformanceTimingFromString(const nsAString& aTimingName) { @@ -163,6 +175,32 @@ 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 ConvertNameToTimestamp(const nsAString& aName, + 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 diff --git a/dom/performance/PerformanceMainThread.cpp b/dom/performance/PerformanceMainThread.cpp index acb69ea358..a4dbf88799 100644 --- a/dom/performance/PerformanceMainThread.cpp +++ b/dom/performance/PerformanceMainThread.cpp @@ -177,28 +177,6 @@ PerformanceMainThread::AddEntry(nsIHttpChannel* channel, } } -// To be removed once bug 1124165 lands -bool -PerformanceMainThread::IsPerformanceTimingAttribute(const nsAString& aName) -{ - // Note that toJSON is added to this list due to bug 1047848 - static const char* attributes[] = - {"navigationStart", "unloadEventStart", "unloadEventEnd", "redirectStart", - "redirectEnd", "fetchStart", "domainLookupStart", "domainLookupEnd", - "connectStart", "secureConnectionStart", "connectEnd", "requestStart", "responseStart", - "responseEnd", "domLoading", "domInteractive", - "domContentLoadedEventStart", "domContentLoadedEventEnd", "domComplete", - "loadEventStart", "loadEventEnd", nullptr}; - - for (uint32_t i = 0; attributes[i]; ++i) { - if (aName.EqualsASCII(attributes[i])) { - return true; - } - } - - return false; -} - DOMHighResTimeStamp PerformanceMainThread::GetPerformanceTimingFromString(const nsAString& aProperty) { diff --git a/dom/performance/PerformanceMainThread.h b/dom/performance/PerformanceMainThread.h index 35fd4ab0e2..702483e9de 100644 --- a/dom/performance/PerformanceMainThread.h +++ b/dom/performance/PerformanceMainThread.h @@ -57,6 +57,11 @@ public: const Optional<nsAString>& aEntryType, nsTArray<RefPtr<PerformanceEntry>>& aRetval) override; + bool IsGlobalObjectWindow() const override + { + return true; + } + protected: ~PerformanceMainThread(); @@ -67,8 +72,6 @@ protected: void InsertUserEntry(PerformanceEntry* aEntry) override; - bool IsPerformanceTimingAttribute(const nsAString& aName) override; - DOMHighResTimeStamp GetPerformanceTimingFromString(const nsAString& aTimingName) override; diff --git a/dom/performance/PerformanceMark.cpp b/dom/performance/PerformanceMark.cpp index c37d057b66..771fc64467 100644 --- a/dom/performance/PerformanceMark.cpp +++ b/dom/performance/PerformanceMark.cpp @@ -6,25 +6,119 @@ #include "PerformanceMark.h" #include "MainThreadUtils.h" #include "mozilla/dom/PerformanceMarkBinding.h" +#include "mozilla/dom/MessagePortBinding.h" +using mozilla::dom::StructuredSerializeOptions; using namespace mozilla::dom; PerformanceMark::PerformanceMark(nsISupports* aParent, const nsAString& aName, - DOMHighResTimeStamp aStartTime) + DOMHighResTimeStamp aStartTime, + const JS::Handle<JS::Value>& aDetail) : PerformanceEntry(aParent, aName, NS_LITERAL_STRING("mark")) , mStartTime(aStartTime) + , mDetail(aDetail) { // mParent is null in workers. MOZ_ASSERT(mParent || !NS_IsMainThread()); + mozilla::HoldJSObjects(this); +} + + +already_AddRefed<PerformanceMark> PerformanceMark::Constructor( + const GlobalObject& aGlobal, + const nsAString& aMarkName, + const PerformanceMarkOptions& aMarkOptions, + ErrorResult& aRv) +{ + const nsCOMPtr<nsIGlobalObject> global = + do_QueryInterface(aGlobal.GetAsSupports()); + return PerformanceMark::Constructor(aGlobal.Context(), global, aMarkName, + aMarkOptions, aRv); +} + +already_AddRefed<PerformanceMark> PerformanceMark::Constructor( + JSContext* aCx, + nsIGlobalObject* aGlobal, + const nsAString& aMarkName, + const PerformanceMarkOptions& aMarkOptions, + ErrorResult& aRv) +{ + RefPtr<Performance> performance = Performance::Get(aCx, aGlobal); + if (!performance) { + // This is similar to the message that occurs when accessing `performance` + // from outside a valid global. + aRv.ThrowTypeError<MSG_PMO_CONSTRUCTOR_INACCESSIBLE>(); + return nullptr; + } + + if (performance->IsGlobalObjectWindow() && + performance->IsPerformanceTimingAttribute(aMarkName)) { + aRv.Throw(NS_ERROR_DOM_UT_INVALID_TIMING_ATTR); + return nullptr; + } + + DOMHighResTimeStamp startTime = aMarkOptions.mStartTime.WasPassed() ? + aMarkOptions.mStartTime.Value() : + performance->Now(); + if (startTime < 0) { + aRv.ThrowTypeError<MSG_PMO_UNEXPECTED_START_TIME>(); + return nullptr; + } + + JS::Rooted<JS::Value> detail(aCx); + if (aMarkOptions.mDetail.isNullOrUndefined()) { + detail.setNull(); + } else { + StructuredSerializeOptions serializeOptions; + JS::Rooted<JS::Value> valueToClone(aCx, aMarkOptions.mDetail); + nsContentUtils::StructuredClone(aCx, aGlobal, valueToClone, + serializeOptions, &detail, aRv); + if (aRv.Failed()) { + return nullptr; + } + } + + return do_AddRef(new PerformanceMark(aGlobal, aMarkName, startTime, detail)); } PerformanceMark::~PerformanceMark() { + mozilla::DropJSObjects(this); } +NS_IMPL_CYCLE_COLLECTION_CLASS(PerformanceMark) +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(PerformanceMark, + PerformanceEntry) + tmp->mDetail.setUndefined(); + mozilla::DropJSObjects(tmp); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PerformanceMark, + PerformanceEntry) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(PerformanceMark, + PerformanceEntry) + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mDetail) +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PerformanceMark) +NS_INTERFACE_MAP_END_INHERITING(PerformanceEntry) +NS_IMPL_ADDREF_INHERITED(PerformanceMark, PerformanceEntry) +NS_IMPL_RELEASE_INHERITED(PerformanceMark, PerformanceEntry) + JSObject* PerformanceMark::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { return PerformanceMarkBinding::Wrap(aCx, this, aGivenProto); } + +void PerformanceMark::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); +} diff --git a/dom/performance/PerformanceMark.h b/dom/performance/PerformanceMark.h index a080af6a82..4e98bfaa92 100644 --- a/dom/performance/PerformanceMark.h +++ b/dom/performance/PerformanceMark.h @@ -11,16 +11,40 @@ namespace mozilla { namespace dom { +struct PerformanceMarkOptions; + // http://www.w3.org/TR/user-timing/#performancemark class PerformanceMark final : public PerformanceEntry { -public: +private: PerformanceMark(nsISupports* aParent, const nsAString& aName, - DOMHighResTimeStamp aStartTime); + DOMHighResTimeStamp aStartTime, + const JS::Handle<JS::Value>& aDetail); + + JS::Heap<JS::Value> mDetail; + +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(PerformanceMark, + PerformanceEntry); + + static already_AddRefed<PerformanceMark> Constructor( + const GlobalObject& aGlobal, + const nsAString& aMarkName, + const PerformanceMarkOptions& aMarkOptions, + ErrorResult& aRv); + + static already_AddRefed<PerformanceMark> Constructor( + JSContext* aCx, nsIGlobalObject* aGlobal, + const nsAString& aMarkName, + const PerformanceMarkOptions& aMarkOptions, + ErrorResult& aRv); virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; + void GetDetail(JSContext* aCx, JS::MutableHandle<JS::Value> aRv); + virtual DOMHighResTimeStamp StartTime() const override { return mStartTime; diff --git a/dom/performance/PerformanceMeasure.cpp b/dom/performance/PerformanceMeasure.cpp index 2e429e6814..3c6e73230f 100644 --- a/dom/performance/PerformanceMeasure.cpp +++ b/dom/performance/PerformanceMeasure.cpp @@ -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); +} diff --git a/dom/performance/PerformanceMeasure.h b/dom/performance/PerformanceMeasure.h index 5e70420d96..83d2ba3406 100644 --- a/dom/performance/PerformanceMeasure.h +++ b/dom/performance/PerformanceMeasure.h @@ -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 diff --git a/dom/webidl/Performance.webidl b/dom/webidl/Performance.webidl index e811e1cee2..7cd6c67172 100644 --- a/dom/webidl/Performance.webidl +++ b/dom/webidl/Performance.webidl @@ -14,7 +14,7 @@ typedef double DOMHighResTimeStamp; typedef sequence <PerformanceEntry> PerformanceEntryList; [Exposed=(Window,Worker)] -interface Performance { +interface Performance : EventTarget { [DependsOn=DeviceState, Affects=Nothing] DOMHighResTimeStamp now(); @@ -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 { @@ -64,15 +72,23 @@ partial interface Performance { }; #endif +// https://w3c.github.io/user-timing/#extensions-performance-interface +dictionary PerformanceMarkOptions { + any detail; + DOMHighResTimeStamp startTime; +}; + // http://www.w3.org/TR/user-timing/ [Exposed=(Window,Worker)] partial interface Performance { [Func="Performance::IsEnabled", Throws] - void mark(DOMString markName); + PerformanceMark mark(DOMString markName, optional PerformanceMarkOptions markOptions); [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); }; diff --git a/dom/webidl/PerformanceMark.webidl b/dom/webidl/PerformanceMark.webidl index 20e9e92c02..2ef9fcd844 100644 --- a/dom/webidl/PerformanceMark.webidl +++ b/dom/webidl/PerformanceMark.webidl @@ -7,7 +7,8 @@ * http://www.w3.org/TR/user-timing/#performancemark */ -[Exposed=(Window,Worker)] +[Exposed=(Window,Worker), Constructor(DOMString markName, optional PerformanceMarkOptions markOptions)] interface PerformanceMark : PerformanceEntry { + readonly attribute any detail; }; diff --git a/dom/webidl/PerformanceMeasure.webidl b/dom/webidl/PerformanceMeasure.webidl index aa4e8cd256..a520778b88 100644 --- a/dom/webidl/PerformanceMeasure.webidl +++ b/dom/webidl/PerformanceMeasure.webidl @@ -10,4 +10,5 @@ [Exposed=(Window,Worker)] interface PerformanceMeasure : PerformanceEntry { + readonly attribute any detail; }; diff --git a/xpcom/base/ErrorList.h b/xpcom/base/ErrorList.h index 980171cb92..8ec4ec3d09 100644 --- a/xpcom/base/ErrorList.h +++ b/xpcom/base/ErrorList.h @@ -984,6 +984,16 @@ #undef MODULE /* ======================================================================= */ + /* 42: NS_ERROR_MODULE_DOM_USER_TIMING */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_DOM_USER_TIMING + ERROR(NS_ERROR_DOM_UT_UNKNOWN_MARK_NAME, FAILURE(1)), + ERROR(NS_ERROR_DOM_UT_INVALID_TIMING_ATTR, FAILURE(2)), + ERROR(NS_ERROR_DOM_UT_UNAVAILABLE_ATTR, FAILURE(3)), + ERROR(NS_ERROR_DOM_UT_UNAVAILABLE_GLOBAL_OBJECT, FAILURE(4)), +#undef MODULE + + /* ======================================================================= */ /* 51: NS_ERROR_MODULE_GENERAL */ /* ======================================================================= */ #define MODULE NS_ERROR_MODULE_GENERAL diff --git a/xpcom/base/nsError.h b/xpcom/base/nsError.h index 3743ecfbed..82d368e107 100644 --- a/xpcom/base/nsError.h +++ b/xpcom/base/nsError.h @@ -79,6 +79,7 @@ #define NS_ERROR_MODULE_DOM_ANIM 39 #define NS_ERROR_MODULE_DOM_PUSH 40 #define NS_ERROR_MODULE_DOM_MEDIA 41 +#define NS_ERROR_MODULE_DOM_USER_TIMING 42 /* NS_ERROR_MODULE_GENERAL should be used by modules that do not * care if return code values overlap. Callers of methods that |