summaryrefslogtreecommitdiffstats
path: root/dom/performance
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /dom/performance
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/performance')
-rw-r--r--dom/performance/Performance.cpp700
-rw-r--r--dom/performance/Performance.h189
-rw-r--r--dom/performance/PerformanceEntry.cpp39
-rw-r--r--dom/performance/PerformanceEntry.h93
-rw-r--r--dom/performance/PerformanceMainThread.cpp461
-rw-r--r--dom/performance/PerformanceMainThread.h106
-rw-r--r--dom/performance/PerformanceMark.cpp27
-rw-r--r--dom/performance/PerformanceMark.h37
-rw-r--r--dom/performance/PerformanceMeasure.cpp31
-rw-r--r--dom/performance/PerformanceMeasure.h41
-rw-r--r--dom/performance/PerformanceNavigation.cpp34
-rw-r--r--dom/performance/PerformanceNavigation.h52
-rw-r--r--dom/performance/PerformanceNavigationTiming.cpp145
-rw-r--r--dom/performance/PerformanceNavigationTiming.h88
-rw-r--r--dom/performance/PerformanceObserver.cpp356
-rw-r--r--dom/performance/PerformanceObserver.h91
-rw-r--r--dom/performance/PerformanceObserverEntryList.cpp100
-rw-r--r--dom/performance/PerformanceObserverEntryList.h57
-rw-r--r--dom/performance/PerformancePaintTiming.cpp53
-rw-r--r--dom/performance/PerformancePaintTiming.h52
-rw-r--r--dom/performance/PerformanceResourceTiming.cpp135
-rw-r--r--dom/performance/PerformanceResourceTiming.h190
-rw-r--r--dom/performance/PerformanceServerTiming.cpp71
-rw-r--r--dom/performance/PerformanceServerTiming.h54
-rw-r--r--dom/performance/PerformanceService.cpp42
-rw-r--r--dom/performance/PerformanceService.h45
-rw-r--r--dom/performance/PerformanceStorage.h37
-rw-r--r--dom/performance/PerformanceStorageWorker.cpp167
-rw-r--r--dom/performance/PerformanceStorageWorker.h51
-rw-r--r--dom/performance/PerformanceTiming.cpp617
-rw-r--r--dom/performance/PerformanceTiming.h595
-rw-r--r--dom/performance/PerformanceWorker.cpp53
-rw-r--r--dom/performance/PerformanceWorker.h87
-rw-r--r--dom/performance/moz.build54
-rw-r--r--dom/performance/tests/.eslintrc.js5
-rw-r--r--dom/performance/tests/empty.js1
-rw-r--r--dom/performance/tests/logo.pngbin0 -> 21901 bytes
-rw-r--r--dom/performance/tests/mochitest.ini37
-rw-r--r--dom/performance/tests/serverTiming.sjs32
-rw-r--r--dom/performance/tests/sharedworker_performance_user_timing.js38
-rw-r--r--dom/performance/tests/test_performance_navigation_timing.html104
-rw-r--r--dom/performance/tests/test_performance_observer.html142
-rw-r--r--dom/performance/tests/test_performance_observer.js286
-rw-r--r--dom/performance/tests/test_performance_paint_observer.html40
-rw-r--r--dom/performance/tests/test_performance_paint_observer_helper.html35
-rw-r--r--dom/performance/tests/test_performance_paint_timing.html38
-rw-r--r--dom/performance/tests/test_performance_paint_timing_helper.html65
-rw-r--r--dom/performance/tests/test_performance_server_timing.html58
-rw-r--r--dom/performance/tests/test_performance_server_timing_plain_http.html42
-rw-r--r--dom/performance/tests/test_performance_timing_json.html32
-rw-r--r--dom/performance/tests/test_performance_user_timing.html49
-rw-r--r--dom/performance/tests/test_performance_user_timing.js318
-rw-r--r--dom/performance/tests/test_sharedWorker_performance_user_timing.html30
-rw-r--r--dom/performance/tests/test_timeOrigin.html76
-rw-r--r--dom/performance/tests/test_worker_observer.html41
-rw-r--r--dom/performance/tests/test_worker_performance_entries.html39
-rw-r--r--dom/performance/tests/test_worker_performance_entries.js107
-rw-r--r--dom/performance/tests/test_worker_performance_entries.sjs12
-rw-r--r--dom/performance/tests/test_worker_performance_now.html31
-rw-r--r--dom/performance/tests/test_worker_performance_now.js68
-rw-r--r--dom/performance/tests/test_worker_user_timing.html30
-rw-r--r--dom/performance/tests/worker_performance_observer.js4
-rw-r--r--dom/performance/tests/worker_performance_user_timing.js32
63 files changed, 6742 insertions, 0 deletions
diff --git a/dom/performance/Performance.cpp b/dom/performance/Performance.cpp
new file mode 100644
index 0000000000..8d47c2ce3c
--- /dev/null
+++ b/dom/performance/Performance.cpp
@@ -0,0 +1,700 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "Performance.h"
+
+#include "GeckoProfiler.h"
+#include "nsRFPService.h"
+#include "PerformanceEntry.h"
+#include "PerformanceMainThread.h"
+#include "PerformanceMark.h"
+#include "PerformanceMeasure.h"
+#include "PerformanceObserver.h"
+#include "PerformanceResourceTiming.h"
+#include "PerformanceService.h"
+#include "PerformanceWorker.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/PerformanceBinding.h"
+#include "mozilla/dom/PerformanceEntryEvent.h"
+#include "mozilla/dom/PerformanceNavigationBinding.h"
+#include "mozilla/dom/PerformanceObserverBinding.h"
+#include "mozilla/dom/PerformanceNavigationTiming.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerRunnable.h"
+
+#define PERFLOG(msg, ...) printf_stderr(msg, ##__VA_ARGS__)
+
+namespace mozilla::dom {
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Performance)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(Performance, DOMEventTargetHelper,
+ mUserEntries, mResourceEntries,
+ mSecondaryResourceEntries, mObservers);
+
+NS_IMPL_ADDREF_INHERITED(Performance, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(Performance, DOMEventTargetHelper)
+
+/* static */
+already_AddRefed<Performance> Performance::CreateForMainThread(
+ nsPIDOMWindowInner* aWindow, nsIPrincipal* aPrincipal,
+ nsDOMNavigationTiming* aDOMTiming, nsITimedChannel* aChannel) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ MOZ_ASSERT(aWindow->AsGlobal());
+ RefPtr<Performance> performance = new PerformanceMainThread(
+ aWindow, aDOMTiming, aChannel, aPrincipal->IsSystemPrincipal());
+ return performance.forget();
+}
+
+/* static */
+already_AddRefed<Performance> Performance::CreateForWorker(
+ WorkerPrivate* aWorkerPrivate) {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ RefPtr<Performance> performance = new PerformanceWorker(aWorkerPrivate);
+ return performance.forget();
+}
+
+Performance::Performance(nsIGlobalObject* aGlobal, bool aSystemPrincipal)
+ : DOMEventTargetHelper(aGlobal),
+ mResourceTimingBufferSize(kDefaultResourceTimingBufferSize),
+ mPendingNotificationObserversTask(false),
+ mPendingResourceTimingBufferFullEvent(false),
+ mSystemPrincipal(aSystemPrincipal) {
+ MOZ_ASSERT(!NS_IsMainThread());
+}
+
+Performance::Performance(nsPIDOMWindowInner* aWindow, bool aSystemPrincipal)
+ : DOMEventTargetHelper(aWindow),
+ mResourceTimingBufferSize(kDefaultResourceTimingBufferSize),
+ mPendingNotificationObserversTask(false),
+ mPendingResourceTimingBufferFullEvent(false),
+ mSystemPrincipal(aSystemPrincipal) {
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+Performance::~Performance() = default;
+
+DOMHighResTimeStamp Performance::TimeStampToDOMHighResForRendering(
+ TimeStamp aTimeStamp) const {
+ DOMHighResTimeStamp stamp = GetDOMTiming()->TimeStampToDOMHighRes(aTimeStamp);
+ if (!IsSystemPrincipal()) {
+ // 0 is an inappropriate mixin for this this area; however CSS Animations
+ // needs to have it's Time Reduction Logic refactored, so it's currently
+ // only clamping for RFP mode. RFP mode gives a much lower time precision,
+ // so we accept the security leak here for now.
+ return nsRFPService::ReduceTimePrecisionAsMSecsRFPOnly(stamp, 0);
+ }
+ return stamp;
+}
+
+DOMHighResTimeStamp Performance::Now() {
+ DOMHighResTimeStamp rawTime = NowUnclamped();
+
+ // XXX: Remove this would cause functions in pkcs11f.h to fail.
+ // Bug 1628021 will find out the root cause.
+ if (mSystemPrincipal) {
+ return rawTime;
+ }
+
+ return nsRFPService::ReduceTimePrecisionAsMSecs(
+ rawTime, GetRandomTimelineSeed(), mSystemPrincipal,
+ CrossOriginIsolated());
+}
+
+DOMHighResTimeStamp Performance::NowUnclamped() const {
+ TimeDuration duration = TimeStamp::NowUnfuzzed() - CreationTimeStamp();
+ return duration.ToMilliseconds();
+}
+
+DOMHighResTimeStamp Performance::TimeOrigin() {
+ if (!mPerformanceService) {
+ mPerformanceService = PerformanceService::GetOrCreate();
+ }
+
+ MOZ_ASSERT(mPerformanceService);
+ DOMHighResTimeStamp rawTimeOrigin =
+ mPerformanceService->TimeOrigin(CreationTimeStamp());
+ // Time Origin is an absolute timestamp, so we supply a 0 context mix-in
+ return nsRFPService::ReduceTimePrecisionAsMSecs(
+ rawTimeOrigin, 0, mSystemPrincipal, CrossOriginIsolated());
+}
+
+JSObject* Performance::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return Performance_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void Performance::GetEntries(nsTArray<RefPtr<PerformanceEntry>>& aRetval) {
+ // We return an empty list when 'privacy.resistFingerprinting' is on.
+ if (nsContentUtils::ShouldResistFingerprinting()) {
+ aRetval.Clear();
+ return;
+ }
+
+ aRetval = mResourceEntries.Clone();
+ aRetval.AppendElements(mUserEntries);
+ aRetval.Sort(PerformanceEntryComparator());
+}
+
+void Performance::GetEntriesByType(
+ const nsAString& aEntryType, nsTArray<RefPtr<PerformanceEntry>>& aRetval) {
+ // We return an empty list when 'privacy.resistFingerprinting' is on.
+ if (nsContentUtils::ShouldResistFingerprinting()) {
+ aRetval.Clear();
+ return;
+ }
+
+ if (aEntryType.EqualsLiteral("resource")) {
+ aRetval = mResourceEntries.Clone();
+ return;
+ }
+
+ aRetval.Clear();
+
+ if (aEntryType.EqualsLiteral("mark") || aEntryType.EqualsLiteral("measure")) {
+ RefPtr<nsAtom> entryType = NS_Atomize(aEntryType);
+ for (PerformanceEntry* entry : mUserEntries) {
+ if (entry->GetEntryType() == entryType) {
+ aRetval.AppendElement(entry);
+ }
+ }
+ }
+}
+
+void Performance::GetEntriesByName(
+ const nsAString& aName, const Optional<nsAString>& aEntryType,
+ nsTArray<RefPtr<PerformanceEntry>>& aRetval) {
+ aRetval.Clear();
+
+ // We return an empty list when 'privacy.resistFingerprinting' is on.
+ if (nsContentUtils::ShouldResistFingerprinting()) {
+ return;
+ }
+
+ RefPtr<nsAtom> name = NS_Atomize(aName);
+ RefPtr<nsAtom> entryType =
+ aEntryType.WasPassed() ? NS_Atomize(aEntryType.Value()) : nullptr;
+
+ // ::Measure expects that results from this function are already
+ // passed through ReduceTimePrecision. mResourceEntries and mUserEntries
+ // are, so the invariant holds.
+ for (PerformanceEntry* entry : mResourceEntries) {
+ if (entry->GetName() == name &&
+ (!entryType || entry->GetEntryType() == entryType)) {
+ aRetval.AppendElement(entry);
+ }
+ }
+
+ for (PerformanceEntry* entry : mUserEntries) {
+ if (entry->GetName() == name &&
+ (!entryType || entry->GetEntryType() == entryType)) {
+ aRetval.AppendElement(entry);
+ }
+ }
+
+ aRetval.Sort(PerformanceEntryComparator());
+}
+
+void Performance::ClearUserEntries(const Optional<nsAString>& aEntryName,
+ const nsAString& aEntryType) {
+ MOZ_ASSERT(!aEntryType.IsEmpty());
+ RefPtr<nsAtom> name =
+ aEntryName.WasPassed() ? NS_Atomize(aEntryName.Value()) : nullptr;
+ RefPtr<nsAtom> entryType = NS_Atomize(aEntryType);
+ mUserEntries.RemoveElementsBy([name, entryType](const auto& entry) {
+ return (!name || entry->GetName() == name) &&
+ (entry->GetEntryType() == entryType);
+ });
+}
+
+void Performance::ClearResourceTimings() { mResourceEntries.Clear(); }
+
+#ifdef MOZ_GECKO_PROFILER
+struct UserTimingMarker {
+ static constexpr Span<const char> MarkerTypeName() {
+ return MakeStringSpan("UserTiming");
+ }
+ static void StreamJSONMarkerData(
+ baseprofiler::SpliceableJSONWriter& aWriter,
+ const ProfilerString16View& aName, bool aIsMeasure,
+ const Maybe<ProfilerString16View>& aStartMark,
+ const Maybe<ProfilerString16View>& aEndMark) {
+ aWriter.StringProperty("name", NS_ConvertUTF16toUTF8(aName.Data()));
+ if (aIsMeasure) {
+ aWriter.StringProperty("entryType", "measure");
+ } else {
+ aWriter.StringProperty("entryType", "mark");
+ }
+
+ if (aStartMark.isSome()) {
+ aWriter.StringProperty("startMark",
+ NS_ConvertUTF16toUTF8(aStartMark->Data()));
+ } else {
+ aWriter.NullProperty("startMark");
+ }
+ if (aEndMark.isSome()) {
+ aWriter.StringProperty("endMark",
+ NS_ConvertUTF16toUTF8(aEndMark->Data()));
+ } else {
+ aWriter.NullProperty("endMark");
+ }
+ }
+ static MarkerSchema MarkerTypeDisplay() {
+ using MS = MarkerSchema;
+ MS schema{MS::Location::markerChart, MS::Location::markerTable};
+ schema.SetAllLabels("{marker.data.name}");
+ schema.AddStaticLabelValue("Marker", "UserTiming");
+ schema.AddKeyLabelFormat("entryType", "Entry Type", MS::Format::string);
+ schema.AddKeyLabelFormat("name", "Name", MS::Format::string);
+ schema.AddKeyLabelFormat("startMark", "Start Mark", MS::Format::string);
+ schema.AddKeyLabelFormat("endMark", "End Mark", MS::Format::string);
+ schema.AddStaticLabelValue("Description",
+ "UserTimingMeasure is created using the DOM API "
+ "performance.measure().");
+ return schema;
+ }
+};
+#endif
+
+void Performance::Mark(const nsAString& aName, ErrorResult& aRv) {
+ // We add nothing when 'privacy.resistFingerprinting' is on.
+ if (nsContentUtils::ShouldResistFingerprinting()) {
+ return;
+ }
+
+ if (IsPerformanceTimingAttribute(aName)) {
+ aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+ return;
+ }
+
+ RefPtr<PerformanceMark> performanceMark =
+ new PerformanceMark(GetParentObject(), aName, Now());
+ InsertUserEntry(performanceMark);
+
+#ifdef MOZ_GECKO_PROFILER
+ if (profiler_can_accept_markers()) {
+ Maybe<uint64_t> innerWindowId;
+ if (GetOwner()) {
+ innerWindowId = Some(GetOwner()->WindowID());
+ }
+ profiler_add_marker("UserTiming", geckoprofiler::category::DOM,
+ MarkerInnerWindowId(innerWindowId), UserTimingMarker{},
+ aName, /* aIsMeasure */ false, Nothing{}, Nothing{});
+ }
+#endif
+}
+
+void Performance::ClearMarks(const Optional<nsAString>& aName) {
+ ClearUserEntries(aName, u"mark"_ns);
+}
+
+DOMHighResTimeStamp Performance::ResolveTimestampFromName(
+ const nsAString& aName, ErrorResult& aRv) {
+ AutoTArray<RefPtr<PerformanceEntry>, 1> arr;
+ Optional<nsAString> typeParam;
+ nsAutoString str;
+ str.AssignLiteral("mark");
+ typeParam = &str;
+ GetEntriesByName(aName, typeParam, arr);
+ if (!arr.IsEmpty()) {
+ return arr.LastElement()->StartTime();
+ }
+
+ if (!IsPerformanceTimingAttribute(aName)) {
+ aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+ return 0;
+ }
+
+ DOMHighResTimeStamp ts = GetPerformanceTimingFromString(aName);
+ if (!ts) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ return 0;
+ }
+
+ return ts - CreationTime();
+}
+
+void Performance::Measure(const nsAString& aName,
+ const Optional<nsAString>& aStartMark,
+ const Optional<nsAString>& aEndMark,
+ ErrorResult& aRv) {
+ // We add nothing when 'privacy.resistFingerprinting' is on.
+ if (nsContentUtils::ShouldResistFingerprinting()) {
+ return;
+ }
+
+ DOMHighResTimeStamp startTime;
+ DOMHighResTimeStamp endTime;
+
+ if (aStartMark.WasPassed()) {
+ startTime = ResolveTimestampFromName(aStartMark.Value(), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ } 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;
+ }
+ } else {
+ endTime = Now();
+ }
+
+ RefPtr<PerformanceMeasure> performanceMeasure =
+ new PerformanceMeasure(GetParentObject(), aName, startTime, endTime);
+ InsertUserEntry(performanceMeasure);
+
+#ifdef MOZ_GECKO_PROFILER
+ if (profiler_can_accept_markers()) {
+ TimeStamp startTimeStamp =
+ CreationTimeStamp() + TimeDuration::FromMilliseconds(startTime);
+ TimeStamp endTimeStamp =
+ CreationTimeStamp() + TimeDuration::FromMilliseconds(endTime);
+
+ // Convert to Maybe values so that Optional types do not need to be used in
+ // the profiler.
+ Maybe<nsString> startMark;
+ if (aStartMark.WasPassed()) {
+ startMark.emplace(aStartMark.Value());
+ }
+ Maybe<nsString> endMark;
+ if (aEndMark.WasPassed()) {
+ endMark.emplace(aEndMark.Value());
+ }
+
+ Maybe<uint64_t> innerWindowId;
+ if (GetOwner()) {
+ innerWindowId = Some(GetOwner()->WindowID());
+ }
+ profiler_add_marker("UserTiming", geckoprofiler::category::DOM,
+ {MarkerTiming::Interval(startTimeStamp, endTimeStamp),
+ MarkerInnerWindowId(innerWindowId)},
+ UserTimingMarker{}, aName, /* aIsMeasure */ true,
+ startMark, endMark);
+ }
+#endif
+}
+
+void Performance::ClearMeasures(const Optional<nsAString>& aName) {
+ ClearUserEntries(aName, u"measure"_ns);
+}
+
+void Performance::LogEntry(PerformanceEntry* aEntry,
+ const nsACString& aOwner) const {
+ PERFLOG("Performance Entry: %s|%s|%s|%f|%f|%" PRIu64 "\n",
+ aOwner.BeginReading(),
+ NS_ConvertUTF16toUTF8(aEntry->GetEntryType()->GetUTF16String()).get(),
+ NS_ConvertUTF16toUTF8(aEntry->GetName()->GetUTF16String()).get(),
+ aEntry->StartTime(), aEntry->Duration(),
+ static_cast<uint64_t>(PR_Now() / PR_USEC_PER_MSEC));
+}
+
+void Performance::TimingNotification(PerformanceEntry* aEntry,
+ const nsACString& aOwner,
+ uint64_t aEpoch) {
+ PerformanceEntryEventInit init;
+ init.mBubbles = false;
+ init.mCancelable = false;
+ aEntry->GetName(init.mName);
+ aEntry->GetEntryType(init.mEntryType);
+ init.mStartTime = aEntry->StartTime();
+ init.mDuration = aEntry->Duration();
+ init.mEpoch = aEpoch;
+ CopyUTF8toUTF16(aOwner, init.mOrigin);
+
+ RefPtr<PerformanceEntryEvent> perfEntryEvent =
+ PerformanceEntryEvent::Constructor(this, u"performanceentry"_ns, init);
+
+ nsCOMPtr<EventTarget> et = do_QueryInterface(GetOwner());
+ if (et) {
+ et->DispatchEvent(*perfEntryEvent);
+ }
+}
+
+void Performance::InsertUserEntry(PerformanceEntry* aEntry) {
+ mUserEntries.InsertElementSorted(aEntry, PerformanceEntryComparator());
+
+ QueueEntry(aEntry);
+}
+
+/*
+ * Steps are labeled according to the description found at
+ * https://w3c.github.io/resource-timing/#sec-extensions-performance-interface.
+ *
+ * Buffer Full Event
+ */
+void Performance::BufferEvent() {
+ /*
+ * While resource timing secondary buffer is not empty,
+ * run the following substeps:
+ */
+ while (!mSecondaryResourceEntries.IsEmpty()) {
+ uint32_t secondaryResourceEntriesBeforeCount = 0;
+ uint32_t secondaryResourceEntriesAfterCount = 0;
+
+ /*
+ * Let number of excess entries before be resource
+ * timing secondary buffer current size.
+ */
+ secondaryResourceEntriesBeforeCount = mSecondaryResourceEntries.Length();
+
+ /*
+ * If can add resource timing entry returns false,
+ * then fire an event named resourcetimingbufferfull
+ * at the Performance object.
+ */
+ if (!CanAddResourceTimingEntry()) {
+ DispatchBufferFullEvent();
+ }
+
+ /*
+ * Run copy secondary buffer.
+ *
+ * While resource timing secondary buffer is not
+ * empty and can add resource timing entry returns
+ * true ...
+ */
+ while (!mSecondaryResourceEntries.IsEmpty() &&
+ CanAddResourceTimingEntry()) {
+ /*
+ * Let entry be the oldest PerformanceResourceTiming
+ * in resource timing secondary buffer. Add entry to
+ * the end of performance entry buffer. Increment
+ * resource timing buffer current size by 1.
+ */
+ mResourceEntries.InsertElementSorted(
+ mSecondaryResourceEntries.ElementAt(0), PerformanceEntryComparator());
+ /*
+ * Remove entry from resource timing secondary buffer.
+ * Decrement resource timing secondary buffer current
+ * size by 1.
+ */
+ mSecondaryResourceEntries.RemoveElementAt(0);
+ }
+
+ /*
+ * Let number of excess entries after be resource
+ * timing secondary buffer current size.
+ */
+ secondaryResourceEntriesAfterCount = mSecondaryResourceEntries.Length();
+
+ /*
+ * If number of excess entries before is lower than
+ * or equals number of excess entries after, then
+ * remove all entries from resource timing secondary
+ * buffer, set resource timing secondary buffer current
+ * size to 0, and abort these steps.
+ */
+ if (secondaryResourceEntriesBeforeCount <=
+ secondaryResourceEntriesAfterCount) {
+ mSecondaryResourceEntries.Clear();
+ break;
+ }
+ }
+ /*
+ * Set resource timing buffer full event pending flag
+ * to false.
+ */
+ mPendingResourceTimingBufferFullEvent = false;
+}
+
+void Performance::SetResourceTimingBufferSize(uint64_t aMaxSize) {
+ mResourceTimingBufferSize = aMaxSize;
+}
+
+/*
+ * Steps are labeled according to the description found at
+ * https://w3c.github.io/resource-timing/#sec-extensions-performance-interface.
+ *
+ * Can Add Resource Timing Entry
+ */
+MOZ_ALWAYS_INLINE bool Performance::CanAddResourceTimingEntry() {
+ /*
+ * If resource timing buffer current size is smaller than resource timing
+ * buffer size limit, return true. [Otherwise,] [r]eturn false.
+ */
+ return mResourceEntries.Length() < mResourceTimingBufferSize;
+}
+
+/*
+ * Steps are labeled according to the description found at
+ * https://w3c.github.io/resource-timing/#sec-extensions-performance-interface.
+ *
+ * Add a PerformanceResourceTiming Entry
+ */
+void Performance::InsertResourceEntry(PerformanceEntry* aEntry) {
+ MOZ_ASSERT(aEntry);
+
+ if (nsContentUtils::ShouldResistFingerprinting()) {
+ return;
+ }
+
+ /*
+ * Let new entry be the input PerformanceEntry to be added.
+ *
+ * If can add resource timing entry returns true and resource
+ * timing buffer full event pending flag is false ...
+ */
+ if (CanAddResourceTimingEntry() && !mPendingResourceTimingBufferFullEvent) {
+ /*
+ * Add new entry to the performance entry buffer.
+ * Increase resource timing buffer current size by 1.
+ */
+ mResourceEntries.InsertElementSorted(aEntry, PerformanceEntryComparator());
+ QueueEntry(aEntry);
+ return;
+ }
+
+ /*
+ * If resource timing buffer full event pending flag is
+ * false ...
+ */
+ if (!mPendingResourceTimingBufferFullEvent) {
+ /*
+ * Set resource timing buffer full event pending flag
+ * to true.
+ */
+ mPendingResourceTimingBufferFullEvent = true;
+
+ /*
+ * Queue a task to run fire a buffer full event.
+ */
+ NS_DispatchToCurrentThread(NewCancelableRunnableMethod(
+ "Performance::BufferEvent", this, &Performance::BufferEvent));
+ }
+ /*
+ * Add new entry to the resource timing secondary buffer.
+ * Increase resource timing secondary buffer current size
+ * by 1.
+ */
+ mSecondaryResourceEntries.InsertElementSorted(aEntry,
+ PerformanceEntryComparator());
+}
+
+void Performance::AddObserver(PerformanceObserver* aObserver) {
+ mObservers.AppendElementUnlessExists(aObserver);
+}
+
+void Performance::RemoveObserver(PerformanceObserver* aObserver) {
+ mObservers.RemoveElement(aObserver);
+}
+
+void Performance::NotifyObservers() {
+ mPendingNotificationObserversTask = false;
+ NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mObservers, Notify, ());
+}
+
+void Performance::CancelNotificationObservers() {
+ mPendingNotificationObserversTask = false;
+}
+
+class NotifyObserversTask final : public CancelableRunnable {
+ public:
+ explicit NotifyObserversTask(Performance* aPerformance)
+ : CancelableRunnable("dom::NotifyObserversTask"),
+ mPerformance(aPerformance) {
+ MOZ_ASSERT(mPerformance);
+ }
+
+ // MOZ_CAN_RUN_SCRIPT_BOUNDARY for now until Runnable::Run is
+ // MOZ_CAN_RUN_SCRIPT.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(mPerformance);
+ RefPtr<Performance> performance(mPerformance);
+ performance->NotifyObservers();
+ return NS_OK;
+ }
+
+ nsresult Cancel() override {
+ mPerformance->CancelNotificationObservers();
+ mPerformance = nullptr;
+ return NS_OK;
+ }
+
+ private:
+ ~NotifyObserversTask() = default;
+
+ RefPtr<Performance> mPerformance;
+};
+
+void Performance::QueueNotificationObserversTask() {
+ if (!mPendingNotificationObserversTask) {
+ RunNotificationObserversTask();
+ }
+}
+
+void Performance::RunNotificationObserversTask() {
+ mPendingNotificationObserversTask = true;
+ nsCOMPtr<nsIRunnable> task = new NotifyObserversTask(this);
+ nsresult rv;
+ if (GetOwnerGlobal()) {
+ rv = GetOwnerGlobal()->Dispatch(TaskCategory::Other, task.forget());
+ } else {
+ rv = NS_DispatchToCurrentThread(task);
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mPendingNotificationObserversTask = false;
+ }
+}
+
+void Performance::QueueEntry(PerformanceEntry* aEntry) {
+ if (mObservers.IsEmpty()) {
+ return;
+ }
+
+ nsTObserverArray<PerformanceObserver*> interestedObservers;
+ const auto [begin, end] = mObservers.NonObservingRange();
+ std::copy_if(begin, end, MakeBackInserter(interestedObservers),
+ [aEntry](PerformanceObserver* observer) {
+ return observer->ObservesTypeOfEntry(aEntry);
+ });
+
+ if (interestedObservers.IsEmpty()) {
+ return;
+ }
+
+ NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(interestedObservers, QueueEntry,
+ (aEntry));
+
+ QueueNotificationObserversTask();
+}
+
+void Performance::MemoryPressure() { mUserEntries.Clear(); }
+
+size_t Performance::SizeOfUserEntries(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ size_t userEntries = 0;
+ for (const PerformanceEntry* entry : mUserEntries) {
+ userEntries += entry->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ return userEntries;
+}
+
+size_t Performance::SizeOfResourceEntries(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ size_t resourceEntries = 0;
+ for (const PerformanceEntry* entry : mResourceEntries) {
+ resourceEntries += entry->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ return resourceEntries;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/performance/Performance.h b/dom/performance/Performance.h
new file mode 100644
index 0000000000..0e1ed63f88
--- /dev/null
+++ b/dom/performance/Performance.h
@@ -0,0 +1,189 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_Performance_h
+#define mozilla_dom_Performance_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "nsCOMPtr.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsTObserverArray.h"
+
+class nsITimedChannel;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+class PerformanceEntry;
+class PerformanceNavigation;
+class PerformancePaintTiming;
+class PerformanceObserver;
+class PerformanceService;
+class PerformanceStorage;
+class PerformanceTiming;
+class WorkerPrivate;
+
+// Base class for main-thread and worker Performance API
+class Performance : public DOMEventTargetHelper {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(Performance, DOMEventTargetHelper)
+
+ static bool IsObserverEnabled(JSContext* aCx, JSObject* aGlobal);
+
+ static already_AddRefed<Performance> CreateForMainThread(
+ nsPIDOMWindowInner* aWindow, nsIPrincipal* aPrincipal,
+ nsDOMNavigationTiming* aDOMTiming, nsITimedChannel* aChannel);
+
+ static already_AddRefed<Performance> CreateForWorker(
+ WorkerPrivate* aWorkerPrivate);
+
+ JSObject* WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ virtual void GetEntries(nsTArray<RefPtr<PerformanceEntry>>& aRetval);
+
+ virtual void GetEntriesByType(const nsAString& aEntryType,
+ nsTArray<RefPtr<PerformanceEntry>>& aRetval);
+
+ virtual void GetEntriesByName(const nsAString& aName,
+ const Optional<nsAString>& aEntryType,
+ nsTArray<RefPtr<PerformanceEntry>>& aRetval);
+
+ virtual PerformanceStorage* AsPerformanceStorage() = 0;
+
+ void ClearResourceTimings();
+
+ DOMHighResTimeStamp Now();
+
+ DOMHighResTimeStamp NowUnclamped() const;
+
+ DOMHighResTimeStamp TimeOrigin();
+
+ void Mark(const nsAString& aName, ErrorResult& aRv);
+
+ void ClearMarks(const Optional<nsAString>& aName);
+
+ void Measure(const nsAString& aName, const Optional<nsAString>& aStartMark,
+ const Optional<nsAString>& aEndMark, ErrorResult& aRv);
+
+ void ClearMeasures(const Optional<nsAString>& aName);
+
+ void SetResourceTimingBufferSize(uint64_t aMaxSize);
+
+ void AddObserver(PerformanceObserver* aObserver);
+ void RemoveObserver(PerformanceObserver* aObserver);
+ MOZ_CAN_RUN_SCRIPT void NotifyObservers();
+ void CancelNotificationObservers();
+
+ virtual PerformanceTiming* Timing() = 0;
+
+ virtual PerformanceNavigation* Navigation() = 0;
+
+ virtual void SetFCPTimingEntry(PerformancePaintTiming* aEntry) = 0;
+
+ IMPL_EVENT_HANDLER(resourcetimingbufferfull)
+
+ virtual void GetMozMemory(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aObj) = 0;
+
+ virtual nsDOMNavigationTiming* GetDOMTiming() const = 0;
+
+ virtual nsITimedChannel* GetChannel() const = 0;
+
+ virtual TimeStamp CreationTimeStamp() const = 0;
+
+ bool IsSystemPrincipal() const { return mSystemPrincipal; }
+
+ DOMHighResTimeStamp TimeStampToDOMHighResForRendering(TimeStamp) const;
+
+ virtual uint64_t GetRandomTimelineSeed() = 0;
+
+ void MemoryPressure();
+
+ size_t SizeOfUserEntries(mozilla::MallocSizeOf aMallocSizeOf) const;
+ size_t SizeOfResourceEntries(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ void InsertResourceEntry(PerformanceEntry* aEntry);
+
+ virtual void QueueNavigationTimingEntry() = 0;
+
+ virtual void UpdateNavigationTimingEntry() = 0;
+
+ virtual bool CrossOriginIsolated() const = 0;
+
+ void QueueNotificationObserversTask();
+
+ protected:
+ Performance(nsIGlobalObject* aGlobal, bool aSystemPrincipal);
+ Performance(nsPIDOMWindowInner* aWindow, bool aSystemPrincipal);
+
+ virtual ~Performance();
+
+ virtual void InsertUserEntry(PerformanceEntry* aEntry);
+
+ void ClearUserEntries(const Optional<nsAString>& aEntryName,
+ const nsAString& aEntryType);
+
+ DOMHighResTimeStamp ResolveTimestampFromName(const nsAString& aName,
+ ErrorResult& aRv);
+
+ virtual void DispatchBufferFullEvent() = 0;
+
+ virtual DOMHighResTimeStamp CreationTime() const = 0;
+
+ virtual bool IsPerformanceTimingAttribute(const nsAString& aName) {
+ return false;
+ }
+
+ virtual DOMHighResTimeStamp GetPerformanceTimingFromString(
+ const nsAString& aTimingName) {
+ return 0;
+ }
+
+ void LogEntry(PerformanceEntry* aEntry, const nsACString& aOwner) const;
+ void TimingNotification(PerformanceEntry* aEntry, const nsACString& aOwner,
+ uint64_t epoch);
+
+ void RunNotificationObserversTask();
+ void QueueEntry(PerformanceEntry* aEntry);
+
+ nsTObserverArray<RefPtr<PerformanceObserver>> mObservers;
+
+ protected:
+ static const uint64_t kDefaultResourceTimingBufferSize = 250;
+
+ // When kDefaultResourceTimingBufferSize is increased or removed, these should
+ // be changed to use SegmentedVector
+ AutoTArray<RefPtr<PerformanceEntry>, kDefaultResourceTimingBufferSize>
+ mUserEntries;
+ AutoTArray<RefPtr<PerformanceEntry>, kDefaultResourceTimingBufferSize>
+ mResourceEntries;
+ AutoTArray<RefPtr<PerformanceEntry>, kDefaultResourceTimingBufferSize>
+ mSecondaryResourceEntries;
+
+ uint64_t mResourceTimingBufferSize;
+ bool mPendingNotificationObserversTask;
+
+ bool mPendingResourceTimingBufferFullEvent;
+
+ RefPtr<PerformanceService> mPerformanceService;
+
+ bool mSystemPrincipal;
+
+ private:
+ MOZ_ALWAYS_INLINE bool CanAddResourceTimingEntry();
+ void BufferEvent();
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_Performance_h
diff --git a/dom/performance/PerformanceEntry.cpp b/dom/performance/PerformanceEntry.cpp
new file mode 100644
index 0000000000..c40c6d4ff7
--- /dev/null
+++ b/dom/performance/PerformanceEntry.cpp
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PerformanceEntry.h"
+#include "MainThreadUtils.h"
+
+using namespace mozilla::dom;
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PerformanceEntry, mParent)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(PerformanceEntry)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(PerformanceEntry)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PerformanceEntry)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+PerformanceEntry::PerformanceEntry(nsISupports* aParent, const nsAString& aName,
+ const nsAString& aEntryType)
+ : mParent(aParent),
+ mName(NS_Atomize(aName)),
+ mEntryType(NS_Atomize(aEntryType)) {}
+
+PerformanceEntry::~PerformanceEntry() = default;
+
+size_t PerformanceEntry::SizeOfExcludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ // mName and mEntryType are considered to be owned by nsAtomTable.
+ return 0;
+}
+
+size_t PerformanceEntry::SizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
diff --git a/dom/performance/PerformanceEntry.h b/dom/performance/PerformanceEntry.h
new file mode 100644
index 0000000000..88bdc9888f
--- /dev/null
+++ b/dom/performance/PerformanceEntry.h
@@ -0,0 +1,93 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PerformanceEntry_h___
+#define mozilla_dom_PerformanceEntry_h___
+
+#include "nsDOMNavigationTiming.h"
+#include "nsString.h"
+#include "nsWrapperCache.h"
+#include "nsAtom.h"
+
+class nsISupports;
+
+namespace mozilla {
+namespace dom {
+class PerformanceResourceTiming;
+
+// http://www.w3.org/TR/performance-timeline/#performanceentry
+class PerformanceEntry : public nsISupports, public nsWrapperCache {
+ protected:
+ virtual ~PerformanceEntry();
+
+ public:
+ PerformanceEntry(nsISupports* aParent, const nsAString& aName,
+ const nsAString& aEntryType);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(PerformanceEntry)
+
+ nsISupports* GetParentObject() const { return mParent; }
+
+ void GetName(nsAString& aName) const {
+ if (mName) {
+ mName->ToString(aName);
+ }
+ }
+
+ const nsAtom* GetName() const { return mName; }
+
+ void GetEntryType(nsAString& aEntryType) const {
+ if (mEntryType) {
+ mEntryType->ToString(aEntryType);
+ }
+ }
+
+ const nsAtom* GetEntryType() { return mEntryType; }
+
+ void SetEntryType(const nsAString& aEntryType) {
+ mEntryType = NS_Atomize(aEntryType);
+ }
+
+ virtual DOMHighResTimeStamp StartTime() const { return 0; }
+
+ virtual DOMHighResTimeStamp Duration() const { return 0; }
+
+ virtual const PerformanceResourceTiming* ToResourceTiming() const {
+ return nullptr;
+ }
+
+ virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ protected:
+ virtual size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ private:
+ nsCOMPtr<nsISupports> mParent;
+ RefPtr<nsAtom> mName;
+ RefPtr<nsAtom> mEntryType;
+};
+
+// Helper classes
+class MOZ_STACK_CLASS PerformanceEntryComparator final {
+ public:
+ bool Equals(const PerformanceEntry* aElem1,
+ const PerformanceEntry* aElem2) const {
+ MOZ_ASSERT(aElem1 && aElem2, "Trying to compare null performance entries");
+ return aElem1->StartTime() == aElem2->StartTime();
+ }
+
+ bool LessThan(const PerformanceEntry* aElem1,
+ const PerformanceEntry* aElem2) const {
+ MOZ_ASSERT(aElem1 && aElem2, "Trying to compare null performance entries");
+ return aElem1->StartTime() < aElem2->StartTime();
+ }
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_PerformanceEntry_h___ */
diff --git a/dom/performance/PerformanceMainThread.cpp b/dom/performance/PerformanceMainThread.cpp
new file mode 100644
index 0000000000..5fb8d1ef58
--- /dev/null
+++ b/dom/performance/PerformanceMainThread.cpp
@@ -0,0 +1,461 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PerformanceMainThread.h"
+#include "PerformanceNavigation.h"
+#include "PerformancePaintTiming.h"
+#include "js/GCAPI.h"
+#include "jsapi.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/PerformanceNavigationTiming.h"
+#include "mozilla/dom/PerformanceResourceTiming.h"
+#include "mozilla/dom/PerformanceTiming.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/StaticPrefs_privacy.h"
+#include "nsIChannel.h"
+#include "nsIHttpChannel.h"
+
+namespace mozilla::dom {
+
+namespace {
+
+void GetURLSpecFromChannel(nsITimedChannel* aChannel, nsAString& aSpec) {
+ aSpec.AssignLiteral("document");
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aChannel);
+ if (!channel) {
+ return;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = channel->GetURI(getter_AddRefs(uri));
+ if (NS_WARN_IF(NS_FAILED(rv)) || !uri) {
+ return;
+ }
+
+ nsAutoCString spec;
+ rv = uri->GetSpec(spec);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ CopyUTF8toUTF16(spec, aSpec);
+}
+
+} // namespace
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(PerformanceMainThread)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(PerformanceMainThread,
+ Performance)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mTiming, mNavigation, mDocEntry, mFCPTiming)
+ tmp->mMozMemory = nullptr;
+ mozilla::DropJSObjects(this);
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PerformanceMainThread,
+ Performance)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTiming, mNavigation, mDocEntry, mFCPTiming)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(PerformanceMainThread,
+ Performance)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mMozMemory)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_ADDREF_INHERITED(PerformanceMainThread, Performance)
+NS_IMPL_RELEASE_INHERITED(PerformanceMainThread, Performance)
+
+// QueryInterface implementation for PerformanceMainThread
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PerformanceMainThread)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END_INHERITING(Performance)
+
+PerformanceMainThread::PerformanceMainThread(nsPIDOMWindowInner* aWindow,
+ nsDOMNavigationTiming* aDOMTiming,
+ nsITimedChannel* aChannel,
+ bool aPrincipal)
+ : Performance(aWindow, aPrincipal),
+ mDOMTiming(aDOMTiming),
+ mChannel(aChannel),
+ mCrossOriginIsolated(aWindow->AsGlobal()->CrossOriginIsolated()) {
+ MOZ_ASSERT(aWindow, "Parent window object should be provided");
+ CreateNavigationTimingEntry();
+}
+
+PerformanceMainThread::~PerformanceMainThread() {
+ mozilla::DropJSObjects(this);
+}
+
+void PerformanceMainThread::GetMozMemory(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aObj) {
+ if (!mMozMemory) {
+ JS::Rooted<JSObject*> mozMemoryObj(aCx, JS_NewPlainObject(aCx));
+ JS::Rooted<JSObject*> gcMemoryObj(aCx, js::gc::NewMemoryInfoObject(aCx));
+ if (!mozMemoryObj || !gcMemoryObj) {
+ MOZ_CRASH("out of memory creating performance.mozMemory");
+ }
+ if (!JS_DefineProperty(aCx, mozMemoryObj, "gc", gcMemoryObj,
+ JSPROP_ENUMERATE)) {
+ MOZ_CRASH("out of memory creating performance.mozMemory");
+ }
+ mMozMemory = mozMemoryObj;
+ mozilla::HoldJSObjects(this);
+ }
+
+ aObj.set(mMozMemory);
+}
+
+PerformanceTiming* PerformanceMainThread::Timing() {
+ if (!mTiming) {
+ // For navigation timing, the third argument (an nsIHttpChannel) is null
+ // since the cross-domain redirect were already checked. The last argument
+ // (zero time) for performance.timing is the navigation start value.
+ mTiming = new PerformanceTiming(this, mChannel, nullptr,
+ mDOMTiming->GetNavigationStart());
+ }
+
+ return mTiming;
+}
+
+void PerformanceMainThread::DispatchBufferFullEvent() {
+ RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
+ // it bubbles, and it isn't cancelable
+ event->InitEvent(u"resourcetimingbufferfull"_ns, true, false);
+ event->SetTrusted(true);
+ DispatchEvent(*event);
+}
+
+PerformanceNavigation* PerformanceMainThread::Navigation() {
+ if (!mNavigation) {
+ mNavigation = new PerformanceNavigation(this);
+ }
+
+ return mNavigation;
+}
+
+/**
+ * An entry should be added only after the resource is loaded.
+ * This method is not thread safe and can only be called on the main thread.
+ */
+void PerformanceMainThread::AddEntry(nsIHttpChannel* channel,
+ nsITimedChannel* timedChannel) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsAutoString initiatorType;
+ nsAutoString entryName;
+
+ UniquePtr<PerformanceTimingData> performanceTimingData(
+ PerformanceTimingData::Create(timedChannel, channel, 0, initiatorType,
+ entryName));
+ if (!performanceTimingData) {
+ return;
+ }
+ AddRawEntry(std::move(performanceTimingData), initiatorType, entryName);
+}
+
+void PerformanceMainThread::AddEntry(const nsString& entryName,
+ const nsString& initiatorType,
+ UniquePtr<PerformanceTimingData>&& aData) {
+ AddRawEntry(std::move(aData), initiatorType, entryName);
+}
+
+void PerformanceMainThread::AddRawEntry(UniquePtr<PerformanceTimingData> aData,
+ const nsAString& aInitiatorType,
+ const nsAString& aEntryName) {
+ // The PerformanceResourceTiming object will use the PerformanceTimingData
+ // object to get all the required timings.
+ auto entry =
+ MakeRefPtr<PerformanceResourceTiming>(std::move(aData), this, aEntryName);
+ entry->SetInitiatorType(aInitiatorType);
+ InsertResourceEntry(entry);
+}
+
+void PerformanceMainThread::SetFCPTimingEntry(PerformancePaintTiming* aEntry) {
+ MOZ_ASSERT(aEntry);
+ if (!mFCPTiming) {
+ mFCPTiming = aEntry;
+ QueueEntry(aEntry);
+ }
+}
+
+// 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) {
+ // ::Measure expects the values returned from this function to be passed
+ // through ReduceTimePrecision already.
+ if (!IsPerformanceTimingAttribute(aProperty)) {
+ return 0;
+ }
+ // Values from Timing() are already reduced
+ if (aProperty.EqualsLiteral("redirectStart")) {
+ return Timing()->RedirectStart();
+ }
+ if (aProperty.EqualsLiteral("redirectEnd")) {
+ return Timing()->RedirectEnd();
+ }
+ if (aProperty.EqualsLiteral("fetchStart")) {
+ return Timing()->FetchStart();
+ }
+ if (aProperty.EqualsLiteral("domainLookupStart")) {
+ return Timing()->DomainLookupStart();
+ }
+ if (aProperty.EqualsLiteral("domainLookupEnd")) {
+ return Timing()->DomainLookupEnd();
+ }
+ if (aProperty.EqualsLiteral("connectStart")) {
+ return Timing()->ConnectStart();
+ }
+ if (aProperty.EqualsLiteral("secureConnectionStart")) {
+ return Timing()->SecureConnectionStart();
+ }
+ if (aProperty.EqualsLiteral("connectEnd")) {
+ return Timing()->ConnectEnd();
+ }
+ if (aProperty.EqualsLiteral("requestStart")) {
+ return Timing()->RequestStart();
+ }
+ if (aProperty.EqualsLiteral("responseStart")) {
+ return Timing()->ResponseStart();
+ }
+ if (aProperty.EqualsLiteral("responseEnd")) {
+ return Timing()->ResponseEnd();
+ }
+ // Values from GetDOMTiming() are not.
+ DOMHighResTimeStamp retValue;
+ if (aProperty.EqualsLiteral("navigationStart")) {
+ // DOMHighResTimeStamp is in relation to navigationStart, so this will be
+ // zero.
+ retValue = GetDOMTiming()->GetNavigationStart();
+ } else if (aProperty.EqualsLiteral("unloadEventStart")) {
+ retValue = GetDOMTiming()->GetUnloadEventStart();
+ } else if (aProperty.EqualsLiteral("unloadEventEnd")) {
+ retValue = GetDOMTiming()->GetUnloadEventEnd();
+ } else if (aProperty.EqualsLiteral("domLoading")) {
+ retValue = GetDOMTiming()->GetDomLoading();
+ } else if (aProperty.EqualsLiteral("domInteractive")) {
+ retValue = GetDOMTiming()->GetDomInteractive();
+ } else if (aProperty.EqualsLiteral("domContentLoadedEventStart")) {
+ retValue = GetDOMTiming()->GetDomContentLoadedEventStart();
+ } else if (aProperty.EqualsLiteral("domContentLoadedEventEnd")) {
+ retValue = GetDOMTiming()->GetDomContentLoadedEventEnd();
+ } else if (aProperty.EqualsLiteral("domComplete")) {
+ retValue = GetDOMTiming()->GetDomComplete();
+ } else if (aProperty.EqualsLiteral("loadEventStart")) {
+ retValue = GetDOMTiming()->GetLoadEventStart();
+ } else if (aProperty.EqualsLiteral("loadEventEnd")) {
+ retValue = GetDOMTiming()->GetLoadEventEnd();
+ } else {
+ MOZ_CRASH(
+ "IsPerformanceTimingAttribute and GetPerformanceTimingFromString are "
+ "out "
+ "of sync");
+ }
+ return nsRFPService::ReduceTimePrecisionAsMSecs(
+ retValue, GetRandomTimelineSeed(), /* aIsSystemPrinciapl */ false,
+ CrossOriginIsolated());
+}
+
+void PerformanceMainThread::InsertUserEntry(PerformanceEntry* aEntry) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsAutoCString uri;
+ uint64_t markCreationEpoch = 0;
+
+ if (StaticPrefs::dom_performance_enable_user_timing_logging() ||
+ StaticPrefs::dom_performance_enable_notify_performance_timing()) {
+ nsresult rv = NS_ERROR_FAILURE;
+ nsCOMPtr<nsPIDOMWindowInner> owner = GetOwner();
+ if (owner && owner->GetDocumentURI()) {
+ rv = owner->GetDocumentURI()->GetHost(uri);
+ }
+
+ if (NS_FAILED(rv)) {
+ // If we have no URI, just put in "none".
+ uri.AssignLiteral("none");
+ }
+ markCreationEpoch = static_cast<uint64_t>(PR_Now() / PR_USEC_PER_MSEC);
+
+ if (StaticPrefs::dom_performance_enable_user_timing_logging()) {
+ Performance::LogEntry(aEntry, uri);
+ }
+ }
+
+ if (StaticPrefs::dom_performance_enable_notify_performance_timing()) {
+ TimingNotification(aEntry, uri, markCreationEpoch);
+ }
+
+ Performance::InsertUserEntry(aEntry);
+}
+
+TimeStamp PerformanceMainThread::CreationTimeStamp() const {
+ return GetDOMTiming()->GetNavigationStartTimeStamp();
+}
+
+DOMHighResTimeStamp PerformanceMainThread::CreationTime() const {
+ return GetDOMTiming()->GetNavigationStart();
+}
+
+void PerformanceMainThread::CreateNavigationTimingEntry() {
+ MOZ_ASSERT(!mDocEntry, "mDocEntry should be null.");
+
+ if (!StaticPrefs::dom_enable_performance_navigation_timing() ||
+ StaticPrefs::privacy_resistFingerprinting()) {
+ return;
+ }
+
+ nsAutoString name;
+ GetURLSpecFromChannel(mChannel, name);
+
+ UniquePtr<PerformanceTimingData> timing(
+ new PerformanceTimingData(mChannel, nullptr, 0));
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
+ if (httpChannel) {
+ timing->SetPropertiesFromHttpChannel(httpChannel, mChannel);
+ }
+
+ mDocEntry = new PerformanceNavigationTiming(std::move(timing), this, name);
+}
+
+void PerformanceMainThread::UpdateNavigationTimingEntry() {
+ if (!mDocEntry) {
+ return;
+ }
+
+ // Let's update some values.
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
+ if (httpChannel) {
+ mDocEntry->UpdatePropertiesFromHttpChannel(httpChannel, mChannel);
+ }
+}
+
+void PerformanceMainThread::QueueNavigationTimingEntry() {
+ if (!mDocEntry) {
+ return;
+ }
+
+ UpdateNavigationTimingEntry();
+
+ QueueEntry(mDocEntry);
+}
+
+bool PerformanceMainThread::CrossOriginIsolated() const {
+ return mCrossOriginIsolated;
+}
+
+void PerformanceMainThread::GetEntries(
+ nsTArray<RefPtr<PerformanceEntry>>& aRetval) {
+ // We return an empty list when 'privacy.resistFingerprinting' is on.
+ if (nsContentUtils::ShouldResistFingerprinting()) {
+ aRetval.Clear();
+ return;
+ }
+
+ aRetval = mResourceEntries.Clone();
+ aRetval.AppendElements(mUserEntries);
+
+ if (mDocEntry) {
+ aRetval.AppendElement(mDocEntry);
+ }
+
+ if (mFCPTiming) {
+ aRetval.AppendElement(mFCPTiming);
+ }
+ aRetval.Sort(PerformanceEntryComparator());
+}
+
+void PerformanceMainThread::GetEntriesByType(
+ const nsAString& aEntryType, nsTArray<RefPtr<PerformanceEntry>>& aRetval) {
+ // We return an empty list when 'privacy.resistFingerprinting' is on.
+ if (nsContentUtils::ShouldResistFingerprinting()) {
+ aRetval.Clear();
+ return;
+ }
+
+ if (aEntryType.EqualsLiteral("navigation")) {
+ aRetval.Clear();
+
+ if (mDocEntry) {
+ aRetval.AppendElement(mDocEntry);
+ }
+ return;
+ }
+
+ if (aEntryType.EqualsLiteral("paint")) {
+ if (mFCPTiming) {
+ aRetval.AppendElement(mFCPTiming);
+ return;
+ }
+ }
+
+ Performance::GetEntriesByType(aEntryType, aRetval);
+}
+
+void PerformanceMainThread::GetEntriesByName(
+ const nsAString& aName, const Optional<nsAString>& aEntryType,
+ nsTArray<RefPtr<PerformanceEntry>>& aRetval) {
+ // We return an empty list when 'privacy.resistFingerprinting' is on.
+ if (nsContentUtils::ShouldResistFingerprinting()) {
+ aRetval.Clear();
+ return;
+ }
+
+ Performance::GetEntriesByName(aName, aEntryType, aRetval);
+
+ if (mFCPTiming && mFCPTiming->GetName()->Equals(aName) &&
+ (!aEntryType.WasPassed() ||
+ mFCPTiming->GetEntryType()->Equals(aEntryType.Value()))) {
+ aRetval.AppendElement(mFCPTiming);
+ return;
+ }
+
+ // The navigation entry is the first one. If it exists and the name matches,
+ // let put it in front.
+ if (mDocEntry && mDocEntry->GetName()->Equals(aName)) {
+ aRetval.InsertElementAt(0, mDocEntry);
+ return;
+ }
+}
+
+} // namespace mozilla::dom
diff --git a/dom/performance/PerformanceMainThread.h b/dom/performance/PerformanceMainThread.h
new file mode 100644
index 0000000000..6eae82f2af
--- /dev/null
+++ b/dom/performance/PerformanceMainThread.h
@@ -0,0 +1,106 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PerformanceMainThread_h
+#define mozilla_dom_PerformanceMainThread_h
+
+#include "Performance.h"
+#include "PerformanceStorage.h"
+
+namespace mozilla {
+namespace dom {
+
+class PerformanceNavigationTiming;
+
+class PerformanceMainThread final : public Performance,
+ public PerformanceStorage {
+ public:
+ PerformanceMainThread(nsPIDOMWindowInner* aWindow,
+ nsDOMNavigationTiming* aDOMTiming,
+ nsITimedChannel* aChannel, bool aPrincipal);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(PerformanceMainThread,
+ Performance)
+
+ PerformanceStorage* AsPerformanceStorage() override { return this; }
+
+ virtual PerformanceTiming* Timing() override;
+
+ virtual PerformanceNavigation* Navigation() override;
+
+ virtual void AddEntry(nsIHttpChannel* channel,
+ nsITimedChannel* timedChannel) override;
+ virtual void AddEntry(const nsString& entryName,
+ const nsString& initiatorType,
+ UniquePtr<PerformanceTimingData>&& aData) override;
+
+ void AddRawEntry(UniquePtr<PerformanceTimingData>,
+ const nsAString& aInitiatorType,
+ const nsAString& aEntryName);
+ virtual void SetFCPTimingEntry(PerformancePaintTiming* aEntry) override;
+
+ TimeStamp CreationTimeStamp() const override;
+
+ DOMHighResTimeStamp CreationTime() const override;
+
+ virtual void GetMozMemory(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aObj) override;
+
+ virtual nsDOMNavigationTiming* GetDOMTiming() const override {
+ return mDOMTiming;
+ }
+
+ virtual uint64_t GetRandomTimelineSeed() override {
+ return GetDOMTiming()->GetRandomTimelineSeed();
+ }
+
+ virtual nsITimedChannel* GetChannel() const override { return mChannel; }
+
+ // The GetEntries* methods need to be overriden in order to add the
+ // the document entry of type navigation.
+ virtual void GetEntries(nsTArray<RefPtr<PerformanceEntry>>& aRetval) override;
+ virtual void GetEntriesByType(
+ const nsAString& aEntryType,
+ nsTArray<RefPtr<PerformanceEntry>>& aRetval) override;
+ virtual void GetEntriesByName(
+ const nsAString& aName, const Optional<nsAString>& aEntryType,
+ nsTArray<RefPtr<PerformanceEntry>>& aRetval) override;
+
+ void UpdateNavigationTimingEntry() override;
+ void QueueNavigationTimingEntry() override;
+
+ bool CrossOriginIsolated() const override;
+
+ protected:
+ ~PerformanceMainThread();
+
+ void CreateNavigationTimingEntry();
+
+ void InsertUserEntry(PerformanceEntry* aEntry) override;
+
+ bool IsPerformanceTimingAttribute(const nsAString& aName) override;
+
+ DOMHighResTimeStamp GetPerformanceTimingFromString(
+ const nsAString& aTimingName) override;
+
+ void DispatchBufferFullEvent() override;
+
+ RefPtr<PerformanceNavigationTiming> mDocEntry;
+ RefPtr<nsDOMNavigationTiming> mDOMTiming;
+ nsCOMPtr<nsITimedChannel> mChannel;
+ RefPtr<PerformanceTiming> mTiming;
+ RefPtr<PerformanceNavigation> mNavigation;
+ RefPtr<PerformancePaintTiming> mFCPTiming;
+ JS::Heap<JSObject*> mMozMemory;
+
+ const bool mCrossOriginIsolated;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PerformanceMainThread_h
diff --git a/dom/performance/PerformanceMark.cpp b/dom/performance/PerformanceMark.cpp
new file mode 100644
index 0000000000..3cd3ecf2fc
--- /dev/null
+++ b/dom/performance/PerformanceMark.cpp
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PerformanceMark.h"
+#include "MainThreadUtils.h"
+#include "mozilla/dom/PerformanceMarkBinding.h"
+
+using namespace mozilla::dom;
+
+PerformanceMark::PerformanceMark(nsISupports* aParent, const nsAString& aName,
+ DOMHighResTimeStamp aStartTime)
+ : PerformanceEntry(aParent, aName, u"mark"_ns), mStartTime(aStartTime) {}
+
+PerformanceMark::~PerformanceMark() = default;
+
+JSObject* PerformanceMark::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return PerformanceMark_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+size_t PerformanceMark::SizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
diff --git a/dom/performance/PerformanceMark.h b/dom/performance/PerformanceMark.h
new file mode 100644
index 0000000000..c3ae6a6baf
--- /dev/null
+++ b/dom/performance/PerformanceMark.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_performancemark_h___
+#define mozilla_dom_performancemark_h___
+
+#include "mozilla/dom/PerformanceEntry.h"
+
+namespace mozilla {
+namespace dom {
+
+// http://www.w3.org/TR/user-timing/#performancemark
+class PerformanceMark final : public PerformanceEntry {
+ public:
+ PerformanceMark(nsISupports* aParent, const nsAString& aName,
+ DOMHighResTimeStamp aStartTime);
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ virtual DOMHighResTimeStamp StartTime() const override { return mStartTime; }
+
+ size_t SizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const override;
+
+ protected:
+ virtual ~PerformanceMark();
+ DOMHighResTimeStamp mStartTime;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_performancemark_h___ */
diff --git a/dom/performance/PerformanceMeasure.cpp b/dom/performance/PerformanceMeasure.cpp
new file mode 100644
index 0000000000..81f2999fac
--- /dev/null
+++ b/dom/performance/PerformanceMeasure.cpp
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PerformanceMeasure.h"
+#include "MainThreadUtils.h"
+#include "mozilla/dom/PerformanceMeasureBinding.h"
+
+using namespace mozilla::dom;
+
+PerformanceMeasure::PerformanceMeasure(nsISupports* aParent,
+ const nsAString& aName,
+ DOMHighResTimeStamp aStartTime,
+ DOMHighResTimeStamp aEndTime)
+ : PerformanceEntry(aParent, aName, u"measure"_ns),
+ mStartTime(aStartTime),
+ mDuration(aEndTime - aStartTime) {}
+
+PerformanceMeasure::~PerformanceMeasure() = default;
+
+JSObject* PerformanceMeasure::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return PerformanceMeasure_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+size_t PerformanceMeasure::SizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
diff --git a/dom/performance/PerformanceMeasure.h b/dom/performance/PerformanceMeasure.h
new file mode 100644
index 0000000000..59fd3e1d8b
--- /dev/null
+++ b/dom/performance/PerformanceMeasure.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_performancemeasure_h___
+#define mozilla_dom_performancemeasure_h___
+
+#include "mozilla/dom/PerformanceEntry.h"
+
+namespace mozilla {
+namespace dom {
+
+// http://www.w3.org/TR/user-timing/#performancemeasure
+class PerformanceMeasure final : public PerformanceEntry {
+ public:
+ PerformanceMeasure(nsISupports* aParent, const nsAString& aName,
+ DOMHighResTimeStamp aStartTime,
+ DOMHighResTimeStamp aEndTime);
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ virtual DOMHighResTimeStamp StartTime() const override { return mStartTime; }
+
+ virtual DOMHighResTimeStamp Duration() const override { return mDuration; }
+
+ size_t SizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const override;
+
+ protected:
+ virtual ~PerformanceMeasure();
+ DOMHighResTimeStamp mStartTime;
+ DOMHighResTimeStamp mDuration;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_performancemeasure_h___ */
diff --git a/dom/performance/PerformanceNavigation.cpp b/dom/performance/PerformanceNavigation.cpp
new file mode 100644
index 0000000000..4b9f1dd921
--- /dev/null
+++ b/dom/performance/PerformanceNavigation.cpp
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PerformanceNavigation.h"
+#include "PerformanceTiming.h"
+#include "mozilla/dom/PerformanceNavigationBinding.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PerformanceNavigation, mPerformance)
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(PerformanceNavigation, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(PerformanceNavigation, Release)
+
+PerformanceNavigation::PerformanceNavigation(Performance* aPerformance)
+ : mPerformance(aPerformance) {
+ MOZ_ASSERT(aPerformance, "Parent performance object should be provided");
+}
+
+PerformanceNavigation::~PerformanceNavigation() = default;
+
+JSObject* PerformanceNavigation::WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return PerformanceNavigation_Binding::Wrap(cx, this, aGivenProto);
+}
+
+uint16_t PerformanceNavigation::RedirectCount() const {
+ return GetPerformanceTiming()->Data()->GetRedirectCount();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/performance/PerformanceNavigation.h b/dom/performance/PerformanceNavigation.h
new file mode 100644
index 0000000000..7b1c055c72
--- /dev/null
+++ b/dom/performance/PerformanceNavigation.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PerformanceNavigation_h
+#define mozilla_dom_PerformanceNavigation_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/Performance.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+namespace dom {
+
+// Script "performance.navigation" object
+class PerformanceNavigation final : public nsWrapperCache {
+ public:
+ explicit PerformanceNavigation(Performance* aPerformance);
+
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(PerformanceNavigation)
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(PerformanceNavigation)
+
+ nsDOMNavigationTiming* GetDOMTiming() const {
+ return mPerformance->GetDOMTiming();
+ }
+
+ PerformanceTiming* GetPerformanceTiming() const {
+ return mPerformance->Timing();
+ }
+
+ Performance* GetParentObject() const { return mPerformance; }
+
+ virtual JSObject* WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // PerformanceNavigation WebIDL methods
+ uint16_t Type() const { return GetDOMTiming()->GetType(); }
+
+ uint16_t RedirectCount() const;
+
+ private:
+ ~PerformanceNavigation();
+ RefPtr<Performance> mPerformance;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PerformanceNavigation_h
diff --git a/dom/performance/PerformanceNavigationTiming.cpp b/dom/performance/PerformanceNavigationTiming.cpp
new file mode 100644
index 0000000000..328e39b511
--- /dev/null
+++ b/dom/performance/PerformanceNavigationTiming.cpp
@@ -0,0 +1,145 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/PerformanceNavigationTiming.h"
+#include "mozilla/dom/PerformanceNavigationTimingBinding.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/StaticPrefs_privacy.h"
+
+using namespace mozilla::dom;
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PerformanceNavigationTiming)
+NS_INTERFACE_MAP_END_INHERITING(PerformanceResourceTiming)
+
+NS_IMPL_ADDREF_INHERITED(PerformanceNavigationTiming, PerformanceResourceTiming)
+NS_IMPL_RELEASE_INHERITED(PerformanceNavigationTiming,
+ PerformanceResourceTiming)
+
+JSObject* PerformanceNavigationTiming::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return PerformanceNavigationTiming_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+#define REDUCE_TIME_PRECISION \
+ return nsRFPService::ReduceTimePrecisionAsMSecs( \
+ rawValue, mPerformance->GetRandomTimelineSeed(), \
+ mPerformance->IsSystemPrincipal(), mPerformance->CrossOriginIsolated())
+
+DOMHighResTimeStamp PerformanceNavigationTiming::UnloadEventStart() const {
+ DOMHighResTimeStamp rawValue = 0;
+ /*
+ * Per Navigation Timing Level 2, the value is 0 if
+ * a. there is no previous document or
+ * b. the same-origin-check fails.
+ *
+ * The same-origin-check is defined as:
+ * 1. If the previous document exists and its origin is not same
+ * origin as the current document's origin, return "fail".
+ * 2. Let request be the current document's request.
+ * 3. If request's redirect count is not zero, and all of request's
+ * HTTP redirects have the same origin as the current document,
+ * return "pass".
+ * 4. Otherwise, return "fail".
+ */
+ if (mTimingData->AllRedirectsSameOrigin()) { // same-origin-check:2/3
+ /*
+ * GetUnloadEventStartHighRes returns 0 if
+ * 1. there is no previous document (a, above) or
+ * 2. the current URI does not have the same origin as
+ * the previous document's URI. (same-origin-check:1)
+ */
+ rawValue = mPerformance->GetDOMTiming()->GetUnloadEventStartHighRes();
+ }
+
+ REDUCE_TIME_PRECISION;
+}
+
+DOMHighResTimeStamp PerformanceNavigationTiming::UnloadEventEnd() const {
+ DOMHighResTimeStamp rawValue = 0;
+
+ // See comments in PerformanceNavigationTiming::UnloadEventEnd().
+ if (mTimingData->AllRedirectsSameOrigin()) {
+ rawValue = mPerformance->GetDOMTiming()->GetUnloadEventEndHighRes();
+ }
+
+ REDUCE_TIME_PRECISION;
+}
+
+DOMHighResTimeStamp PerformanceNavigationTiming::DomInteractive() const {
+ DOMHighResTimeStamp rawValue =
+ mPerformance->GetDOMTiming()->GetDomInteractiveHighRes();
+
+ REDUCE_TIME_PRECISION;
+}
+
+DOMHighResTimeStamp PerformanceNavigationTiming::DomContentLoadedEventStart()
+ const {
+ DOMHighResTimeStamp rawValue =
+ mPerformance->GetDOMTiming()->GetDomContentLoadedEventStartHighRes();
+
+ REDUCE_TIME_PRECISION;
+}
+
+DOMHighResTimeStamp PerformanceNavigationTiming::DomContentLoadedEventEnd()
+ const {
+ DOMHighResTimeStamp rawValue =
+ mPerformance->GetDOMTiming()->GetDomContentLoadedEventEndHighRes();
+
+ REDUCE_TIME_PRECISION;
+}
+
+DOMHighResTimeStamp PerformanceNavigationTiming::DomComplete() const {
+ DOMHighResTimeStamp rawValue =
+ mPerformance->GetDOMTiming()->GetDomCompleteHighRes();
+
+ REDUCE_TIME_PRECISION;
+}
+
+DOMHighResTimeStamp PerformanceNavigationTiming::LoadEventStart() const {
+ DOMHighResTimeStamp rawValue =
+ mPerformance->GetDOMTiming()->GetLoadEventStartHighRes();
+
+ REDUCE_TIME_PRECISION;
+}
+
+DOMHighResTimeStamp PerformanceNavigationTiming::LoadEventEnd() const {
+ DOMHighResTimeStamp rawValue =
+ mPerformance->GetDOMTiming()->GetLoadEventEndHighRes();
+
+ REDUCE_TIME_PRECISION;
+}
+
+NavigationType PerformanceNavigationTiming::Type() const {
+ switch (mPerformance->GetDOMTiming()->GetType()) {
+ case nsDOMNavigationTiming::TYPE_NAVIGATE:
+ return NavigationType::Navigate;
+ break;
+ case nsDOMNavigationTiming::TYPE_RELOAD:
+ return NavigationType::Reload;
+ break;
+ case nsDOMNavigationTiming::TYPE_BACK_FORWARD:
+ return NavigationType::Back_forward;
+ break;
+ default:
+ // The type is TYPE_RESERVED or some other value that was later added.
+ // We fallback to the default of Navigate.
+ return NavigationType::Navigate;
+ }
+}
+
+uint16_t PerformanceNavigationTiming::RedirectCount() const {
+ return mTimingData->GetRedirectCount();
+}
+
+void PerformanceNavigationTiming::UpdatePropertiesFromHttpChannel(
+ nsIHttpChannel* aHttpChannel, nsITimedChannel* aChannel) {
+ mTimingData->SetPropertiesFromHttpChannel(aHttpChannel, aChannel);
+}
+
+bool PerformanceNavigationTiming::Enabled(JSContext* aCx, JSObject* aGlobal) {
+ return (StaticPrefs::dom_enable_performance_navigation_timing() &&
+ !StaticPrefs::privacy_resistFingerprinting());
+}
diff --git a/dom/performance/PerformanceNavigationTiming.h b/dom/performance/PerformanceNavigationTiming.h
new file mode 100644
index 0000000000..afef52c925
--- /dev/null
+++ b/dom/performance/PerformanceNavigationTiming.h
@@ -0,0 +1,88 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PerformanceNavigationTiming_h___
+#define mozilla_dom_PerformanceNavigationTiming_h___
+
+#include <stdint.h>
+#include <utility>
+#include "js/RootingAPI.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/dom/PerformanceNavigationTimingBinding.h"
+#include "mozilla/dom/PerformanceResourceTiming.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsISupports.h"
+#include "nsLiteralString.h"
+#include "nsString.h"
+#include "nsTLiteralString.h"
+
+class JSObject;
+class nsIHttpChannel;
+class nsITimedChannel;
+struct JSContext;
+
+namespace mozilla {
+namespace dom {
+
+class Performance;
+class PerformanceTimingData;
+
+// https://www.w3.org/TR/navigation-timing-2/#sec-PerformanceNavigationTiming
+class PerformanceNavigationTiming final : public PerformanceResourceTiming {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // Note that aPerformanceTiming must be initalized with zeroTime = 0
+ // so that timestamps are relative to startTime, as opposed to the
+ // performance.timing object for which timestamps are absolute and has a
+ // zeroTime initialized to navigationStart
+ PerformanceNavigationTiming(
+ UniquePtr<PerformanceTimingData>&& aPerformanceTiming,
+ Performance* aPerformance, const nsAString& aName)
+ : PerformanceResourceTiming(std::move(aPerformanceTiming), aPerformance,
+ aName) {
+ SetEntryType(u"navigation"_ns);
+ SetInitiatorType(u"navigation"_ns);
+ }
+
+ DOMHighResTimeStamp Duration() const override {
+ return LoadEventEnd() - StartTime();
+ }
+
+ DOMHighResTimeStamp StartTime() const override { return 0; }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ DOMHighResTimeStamp UnloadEventStart() const;
+ DOMHighResTimeStamp UnloadEventEnd() const;
+
+ DOMHighResTimeStamp DomInteractive() const;
+ DOMHighResTimeStamp DomContentLoadedEventStart() const;
+ DOMHighResTimeStamp DomContentLoadedEventEnd() const;
+ DOMHighResTimeStamp DomComplete() const;
+ DOMHighResTimeStamp LoadEventStart() const;
+ DOMHighResTimeStamp LoadEventEnd() const;
+ NavigationType Type() const;
+ uint16_t RedirectCount() const;
+
+ void UpdatePropertiesFromHttpChannel(nsIHttpChannel* aHttpChannel,
+ nsITimedChannel* aChannel);
+
+ /*
+ * For use with the WebIDL Func attribute to determine whether
+ * window.PerformanceNavigationTiming is exposed.
+ */
+ static bool Enabled(JSContext* aCx, JSObject* aGlobal);
+
+ private:
+ ~PerformanceNavigationTiming() = default;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PerformanceNavigationTiming_h___
diff --git a/dom/performance/PerformanceObserver.cpp b/dom/performance/PerformanceObserver.cpp
new file mode 100644
index 0000000000..fbbb7ec272
--- /dev/null
+++ b/dom/performance/PerformanceObserver.cpp
@@ -0,0 +1,356 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PerformanceObserver.h"
+
+#include "mozilla/dom/Performance.h"
+#include "mozilla/dom/PerformanceBinding.h"
+#include "mozilla/dom/PerformanceEntryBinding.h"
+#include "mozilla/dom/PerformanceObserverBinding.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerScope.h"
+#include "nsIScriptError.h"
+#include "nsPIDOMWindow.h"
+#include "nsQueryObject.h"
+#include "nsString.h"
+#include "PerformanceEntry.h"
+#include "PerformanceObserverEntryList.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(PerformanceObserver)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(PerformanceObserver)
+ tmp->Disconnect();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mCallback)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPerformance)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mQueuedEntries)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(PerformanceObserver)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallback)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPerformance)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mQueuedEntries)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(PerformanceObserver)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(PerformanceObserver)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(PerformanceObserver)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PerformanceObserver)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+const char UnsupportedEntryTypesIgnoredMsgId[] = "UnsupportedEntryTypesIgnored";
+const char AllEntryTypesIgnoredMsgId[] = "AllEntryTypesIgnored";
+
+PerformanceObserver::PerformanceObserver(nsPIDOMWindowInner* aOwner,
+ PerformanceObserverCallback& aCb)
+ : mOwner(aOwner),
+ mCallback(&aCb),
+ mObserverType(ObserverTypeUndefined),
+ mConnected(false) {
+ MOZ_ASSERT(mOwner);
+ mPerformance = aOwner->GetPerformance();
+}
+
+PerformanceObserver::PerformanceObserver(WorkerPrivate* aWorkerPrivate,
+ PerformanceObserverCallback& aCb)
+ : mCallback(&aCb), mObserverType(ObserverTypeUndefined), mConnected(false) {
+ MOZ_ASSERT(aWorkerPrivate);
+ mPerformance = aWorkerPrivate->GlobalScope()->GetPerformance();
+}
+
+PerformanceObserver::~PerformanceObserver() {
+ Disconnect();
+ MOZ_ASSERT(!mConnected);
+}
+
+// static
+already_AddRefed<PerformanceObserver> PerformanceObserver::Constructor(
+ const GlobalObject& aGlobal, PerformanceObserverCallback& aCb,
+ ErrorResult& aRv) {
+ if (NS_IsMainThread()) {
+ nsCOMPtr<nsPIDOMWindowInner> ownerWindow =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!ownerWindow) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ RefPtr<PerformanceObserver> observer =
+ new PerformanceObserver(ownerWindow, aCb);
+ return observer.forget();
+ }
+
+ JSContext* cx = aGlobal.Context();
+ WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx);
+ MOZ_ASSERT(workerPrivate);
+
+ RefPtr<PerformanceObserver> observer =
+ new PerformanceObserver(workerPrivate, aCb);
+ return observer.forget();
+}
+
+JSObject* PerformanceObserver::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return PerformanceObserver_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void PerformanceObserver::Notify() {
+ if (mQueuedEntries.IsEmpty()) {
+ return;
+ }
+ RefPtr<PerformanceObserverEntryList> list =
+ new PerformanceObserverEntryList(this, mQueuedEntries);
+
+ mQueuedEntries.Clear();
+
+ ErrorResult rv;
+ RefPtr<PerformanceObserverCallback> callback(mCallback);
+ callback->Call(this, *list, *this, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ }
+}
+
+void PerformanceObserver::QueueEntry(PerformanceEntry* aEntry) {
+ MOZ_ASSERT(aEntry);
+
+ if (!ObservesTypeOfEntry(aEntry)) {
+ return;
+ }
+ mQueuedEntries.AppendElement(aEntry);
+}
+
+/*
+ * Keep this list in alphabetical order.
+ * https://w3c.github.io/performance-timeline/#supportedentrytypes-attribute
+ */
+static const char16_t* const sValidTypeNames[5] = {
+ u"mark", u"measure", u"navigation", u"paint", u"resource",
+};
+
+void PerformanceObserver::ReportUnsupportedTypesErrorToConsole(
+ bool aIsMainThread, const char* msgId, const nsString& aInvalidTypes) {
+ if (!aIsMainThread) {
+ nsTArray<nsString> params;
+ params.AppendElement(aInvalidTypes);
+ WorkerPrivate::ReportErrorToConsole(msgId, params);
+ } else {
+ nsCOMPtr<nsPIDOMWindowInner> ownerWindow = do_QueryInterface(mOwner);
+ Document* document = ownerWindow->GetExtantDoc();
+ AutoTArray<nsString, 1> params = {aInvalidTypes};
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns,
+ document, nsContentUtils::eDOM_PROPERTIES,
+ msgId, params);
+ }
+}
+
+void PerformanceObserver::Observe(const PerformanceObserverInit& aOptions,
+ ErrorResult& aRv) {
+ const Optional<Sequence<nsString>>& maybeEntryTypes = aOptions.mEntryTypes;
+ const Optional<nsString>& maybeType = aOptions.mType;
+ const Optional<bool>& maybeBuffered = aOptions.mBuffered;
+
+ if (!mPerformance) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ if (!maybeEntryTypes.WasPassed() && !maybeType.WasPassed()) {
+ /* Per spec (3.3.1.2), this should be a syntax error. */
+ aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+ return;
+ }
+
+ if (maybeEntryTypes.WasPassed() &&
+ (maybeType.WasPassed() || maybeBuffered.WasPassed())) {
+ /* Per spec (3.3.1.3), this, too, should be a syntax error. */
+ aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+ return;
+ }
+
+ /* 3.3.1.4.1 */
+ if (mObserverType == ObserverTypeUndefined) {
+ if (maybeEntryTypes.WasPassed()) {
+ mObserverType = ObserverTypeMultiple;
+ } else {
+ mObserverType = ObserverTypeSingle;
+ }
+ }
+
+ /* 3.3.1.4.2 */
+ if (mObserverType == ObserverTypeSingle && maybeEntryTypes.WasPassed()) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_MODIFICATION_ERR);
+ return;
+ }
+ /* 3.3.1.4.3 */
+ if (mObserverType == ObserverTypeMultiple && maybeType.WasPassed()) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_MODIFICATION_ERR);
+ return;
+ }
+
+ bool needQueueNotificationObserverTask = false;
+ /* 3.3.1.5 */
+ if (mObserverType == ObserverTypeMultiple) {
+ const Sequence<nsString>& entryTypes = maybeEntryTypes.Value();
+
+ if (entryTypes.IsEmpty()) {
+ return;
+ }
+
+ /* 3.3.1.5.2 */
+ nsTArray<nsString> validEntryTypes;
+ for (const char16_t* name : sValidTypeNames) {
+ nsDependentString validTypeName(name);
+ if (entryTypes.Contains<nsString>(validTypeName) &&
+ !validEntryTypes.Contains<nsString>(validTypeName)) {
+ validEntryTypes.AppendElement(validTypeName);
+ }
+ }
+
+ nsAutoString invalidTypesJoined;
+ bool addComma = false;
+ for (const auto& type : entryTypes) {
+ if (!validEntryTypes.Contains<nsString>(type)) {
+ if (addComma) {
+ invalidTypesJoined.AppendLiteral(", ");
+ }
+ addComma = true;
+ invalidTypesJoined.Append(type);
+ }
+ }
+
+ if (!invalidTypesJoined.IsEmpty()) {
+ ReportUnsupportedTypesErrorToConsole(NS_IsMainThread(),
+ UnsupportedEntryTypesIgnoredMsgId,
+ invalidTypesJoined);
+ }
+
+ /* 3.3.1.5.3 */
+ if (validEntryTypes.IsEmpty()) {
+ nsString errorString;
+ ReportUnsupportedTypesErrorToConsole(
+ NS_IsMainThread(), AllEntryTypesIgnoredMsgId, errorString);
+ return;
+ }
+
+ /*
+ * Registered or not, we clear out the list of options, and start fresh
+ * with the one that we are using here. (3.3.1.5.4,5)
+ */
+ mOptions.Clear();
+ mOptions.AppendElement(aOptions);
+
+ } else {
+ MOZ_ASSERT(mObserverType == ObserverTypeSingle);
+ bool typeValid = false;
+ nsString type = maybeType.Value();
+
+ /* 3.3.1.6.2 */
+ for (const char16_t* name : sValidTypeNames) {
+ nsDependentString validTypeName(name);
+ if (type == validTypeName) {
+ typeValid = true;
+ break;
+ }
+ }
+
+ if (!typeValid) {
+ ReportUnsupportedTypesErrorToConsole(
+ NS_IsMainThread(), UnsupportedEntryTypesIgnoredMsgId, type);
+ return;
+ }
+
+ /* 3.3.1.6.4, 3.3.1.6.4 */
+ bool didUpdateOptionsList = false;
+ nsTArray<PerformanceObserverInit> updatedOptionsList;
+ for (auto& option : mOptions) {
+ if (option.mType.WasPassed() && option.mType.Value() == type) {
+ updatedOptionsList.AppendElement(aOptions);
+ didUpdateOptionsList = true;
+ } else {
+ updatedOptionsList.AppendElement(option);
+ }
+ }
+ if (!didUpdateOptionsList) {
+ updatedOptionsList.AppendElement(aOptions);
+ }
+ mOptions = std::move(updatedOptionsList);
+
+ /* 3.3.1.6.5 */
+ if (maybeBuffered.WasPassed() && maybeBuffered.Value()) {
+ nsTArray<RefPtr<PerformanceEntry>> existingEntries;
+ mPerformance->GetEntriesByType(type, existingEntries);
+ if (!existingEntries.IsEmpty()) {
+ mQueuedEntries.AppendElements(existingEntries);
+ needQueueNotificationObserverTask = true;
+ }
+ }
+ }
+ /* Add ourselves to the list of registered performance
+ * observers, if necessary. (3.3.1.5.4,5; 3.3.1.6.4)
+ */
+ mPerformance->AddObserver(this);
+
+ if (needQueueNotificationObserverTask) {
+ mPerformance->QueueNotificationObserversTask();
+ }
+ mConnected = true;
+}
+
+void PerformanceObserver::GetSupportedEntryTypes(
+ const GlobalObject& aGlobal, JS::MutableHandle<JSObject*> aObject) {
+ nsTArray<nsString> validTypes;
+ JS::Rooted<JS::Value> val(aGlobal.Context());
+
+ for (const char16_t* name : sValidTypeNames) {
+ nsString validTypeName(name);
+ validTypes.AppendElement(validTypeName);
+ }
+
+ if (!ToJSValue(aGlobal.Context(), validTypes, &val)) {
+ /*
+ * If this conversion fails, we don't set a result.
+ * The spec does not allow us to throw an exception.
+ */
+ return;
+ }
+ aObject.set(&val.toObject());
+}
+
+bool PerformanceObserver::ObservesTypeOfEntry(PerformanceEntry* aEntry) {
+ for (auto& option : mOptions) {
+ if (option.mType.WasPassed()) {
+ if (aEntry->GetEntryType()->Equals(option.mType.Value())) {
+ return true;
+ }
+ } else {
+ if (option.mEntryTypes.Value().Contains(
+ nsDependentAtomString(aEntry->GetEntryType()))) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+void PerformanceObserver::Disconnect() {
+ if (mConnected) {
+ MOZ_ASSERT(mPerformance);
+ mPerformance->RemoveObserver(this);
+ mConnected = false;
+ }
+}
+
+void PerformanceObserver::TakeRecords(
+ nsTArray<RefPtr<PerformanceEntry>>& aRetval) {
+ MOZ_ASSERT(aRetval.IsEmpty());
+ aRetval = std::move(mQueuedEntries);
+}
diff --git a/dom/performance/PerformanceObserver.h b/dom/performance/PerformanceObserver.h
new file mode 100644
index 0000000000..664ae6c121
--- /dev/null
+++ b/dom/performance/PerformanceObserver.h
@@ -0,0 +1,91 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PerformanceObserver_h__
+#define mozilla_dom_PerformanceObserver_h__
+
+#include "nsCOMPtr.h"
+#include "nsISupports.h"
+#include "mozilla/dom/PerformanceObserverBinding.h"
+#include "mozilla/RefPtr.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsWrapperCache.h"
+
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+class GlobalObject;
+class Performance;
+class PerformanceEntry;
+class PerformanceObserverCallback;
+class WorkerPrivate;
+
+class PerformanceObserver final : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(PerformanceObserver)
+
+ static already_AddRefed<PerformanceObserver> Constructor(
+ const GlobalObject& aGlobal, PerformanceObserverCallback& aCb,
+ ErrorResult& aRv);
+
+ PerformanceObserver(nsPIDOMWindowInner* aOwner,
+ PerformanceObserverCallback& aCb);
+
+ PerformanceObserver(WorkerPrivate* aWorkerPrivate,
+ PerformanceObserverCallback& aCb);
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ nsISupports* GetParentObject() const { return mOwner; }
+
+ void Observe(const PerformanceObserverInit& aOptions, ErrorResult& aRv);
+ static void GetSupportedEntryTypes(const GlobalObject& aGlobal,
+ JS::MutableHandle<JSObject*> aObject);
+
+ void Disconnect();
+
+ void TakeRecords(nsTArray<RefPtr<PerformanceEntry>>& aRetval);
+
+ MOZ_CAN_RUN_SCRIPT void Notify();
+ void QueueEntry(PerformanceEntry* aEntry);
+
+ bool ObservesTypeOfEntry(PerformanceEntry* aEntry);
+
+ private:
+ void ReportUnsupportedTypesErrorToConsole(bool aIsMainThread,
+ const char* msgId,
+ const nsString& aInvalidTypes);
+ ~PerformanceObserver();
+
+ nsCOMPtr<nsISupports> mOwner;
+ RefPtr<PerformanceObserverCallback> mCallback;
+ RefPtr<Performance> mPerformance;
+ nsTArray<nsString> mEntryTypes;
+ nsTArray<PerformanceObserverInit> mOptions;
+ enum {
+ ObserverTypeUndefined,
+ ObserverTypeSingle,
+ ObserverTypeMultiple,
+ } mObserverType;
+ /*
+ * This is also known as registered, in the spec.
+ */
+ bool mConnected;
+ nsTArray<RefPtr<PerformanceEntry>> mQueuedEntries;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/performance/PerformanceObserverEntryList.cpp b/dom/performance/PerformanceObserverEntryList.cpp
new file mode 100644
index 0000000000..af4d867cd7
--- /dev/null
+++ b/dom/performance/PerformanceObserverEntryList.cpp
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PerformanceObserverEntryList.h"
+
+#include "mozilla/dom/Performance.h"
+#include "mozilla/dom/PerformanceObserverEntryListBinding.h"
+#include "nsString.h"
+#include "PerformanceResourceTiming.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PerformanceObserverEntryList, mOwner,
+ mEntries)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(PerformanceObserverEntryList)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(PerformanceObserverEntryList)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PerformanceObserverEntryList)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+PerformanceObserverEntryList::~PerformanceObserverEntryList() = default;
+
+JSObject* PerformanceObserverEntryList::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return PerformanceObserverEntryList_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void PerformanceObserverEntryList::GetEntries(
+ const PerformanceEntryFilterOptions& aFilter,
+ nsTArray<RefPtr<PerformanceEntry>>& aRetval) {
+ aRetval.Clear();
+ RefPtr<nsAtom> name =
+ aFilter.mName.WasPassed() ? NS_Atomize(aFilter.mName.Value()) : nullptr;
+ RefPtr<nsAtom> entryType = aFilter.mEntryType.WasPassed()
+ ? NS_Atomize(aFilter.mEntryType.Value())
+ : nullptr;
+ for (const RefPtr<PerformanceEntry>& entry : mEntries) {
+ if (aFilter.mInitiatorType.WasPassed()) {
+ const PerformanceResourceTiming* resourceEntry =
+ entry->ToResourceTiming();
+ if (!resourceEntry) {
+ continue;
+ }
+ nsAutoString initiatorType;
+ resourceEntry->GetInitiatorType(initiatorType);
+ if (!initiatorType.Equals(aFilter.mInitiatorType.Value())) {
+ continue;
+ }
+ }
+ if (name && entry->GetName() != name) {
+ continue;
+ }
+ if (entryType && entry->GetEntryType() != entryType) {
+ continue;
+ }
+
+ aRetval.AppendElement(entry);
+ }
+ aRetval.Sort(PerformanceEntryComparator());
+}
+
+void PerformanceObserverEntryList::GetEntriesByType(
+ const nsAString& aEntryType, nsTArray<RefPtr<PerformanceEntry>>& aRetval) {
+ aRetval.Clear();
+ RefPtr<nsAtom> entryType = NS_Atomize(aEntryType);
+ for (const RefPtr<PerformanceEntry>& entry : mEntries) {
+ if (entry->GetEntryType() == entryType) {
+ aRetval.AppendElement(entry);
+ }
+ }
+ aRetval.Sort(PerformanceEntryComparator());
+}
+
+void PerformanceObserverEntryList::GetEntriesByName(
+ const nsAString& aName, const Optional<nsAString>& aEntryType,
+ nsTArray<RefPtr<PerformanceEntry>>& aRetval) {
+ aRetval.Clear();
+ RefPtr<nsAtom> name = NS_Atomize(aName);
+ RefPtr<nsAtom> entryType =
+ aEntryType.WasPassed() ? NS_Atomize(aEntryType.Value()) : nullptr;
+ for (const RefPtr<PerformanceEntry>& entry : mEntries) {
+ if (entry->GetName() != name) {
+ continue;
+ }
+
+ if (entryType && entry->GetEntryType() != entryType) {
+ continue;
+ }
+
+ aRetval.AppendElement(entry);
+ }
+ aRetval.Sort(PerformanceEntryComparator());
+}
diff --git a/dom/performance/PerformanceObserverEntryList.h b/dom/performance/PerformanceObserverEntryList.h
new file mode 100644
index 0000000000..9f2aa2b7dd
--- /dev/null
+++ b/dom/performance/PerformanceObserverEntryList.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PerformanceObserverEntryList_h__
+#define mozilla_dom_PerformanceObserverEntryList_h__
+
+#include "nsCOMPtr.h"
+#include "nsISupports.h"
+#include "nsTArray.h"
+#include "nsWrapperCache.h"
+#include "mozilla/dom/PerformanceEntryBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+struct PerformanceEntryFilterOptions;
+class PerformanceEntry;
+template <typename T>
+class Optional;
+
+class PerformanceObserverEntryList final : public nsISupports,
+ public nsWrapperCache {
+ ~PerformanceObserverEntryList();
+
+ public:
+ PerformanceObserverEntryList(
+ nsISupports* aOwner, const nsTArray<RefPtr<PerformanceEntry>>& aEntries)
+ : mOwner(aOwner), mEntries(aEntries.Clone()) {}
+
+ nsISupports* GetParentObject() const { return mOwner; }
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(PerformanceObserverEntryList)
+
+ void GetEntries(const PerformanceEntryFilterOptions& aFilter,
+ nsTArray<RefPtr<PerformanceEntry>>& aRetval);
+ void GetEntriesByType(const nsAString& aEntryType,
+ nsTArray<RefPtr<PerformanceEntry>>& aRetval);
+ void GetEntriesByName(const nsAString& aName,
+ const Optional<nsAString>& aEntryType,
+ nsTArray<RefPtr<PerformanceEntry>>& aRetval);
+
+ private:
+ nsCOMPtr<nsISupports> mOwner;
+ nsTArray<RefPtr<PerformanceEntry>> mEntries;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/performance/PerformancePaintTiming.cpp b/dom/performance/PerformancePaintTiming.cpp
new file mode 100644
index 0000000000..b4fe2ebba4
--- /dev/null
+++ b/dom/performance/PerformancePaintTiming.cpp
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PerformancePaintTiming.h"
+#include "Performance.h"
+#include "MainThreadUtils.h"
+#include "mozilla/dom/PerformanceMeasureBinding.h"
+#include "nsRFPService.h"
+
+using namespace mozilla::dom;
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(PerformancePaintTiming, PerformanceEntry,
+ mPerformance)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PerformancePaintTiming)
+NS_INTERFACE_MAP_END_INHERITING(PerformanceEntry)
+
+NS_IMPL_ADDREF_INHERITED(PerformancePaintTiming, PerformanceEntry)
+NS_IMPL_RELEASE_INHERITED(PerformancePaintTiming, PerformanceEntry)
+
+PerformancePaintTiming::PerformancePaintTiming(Performance* aPerformance,
+ const nsAString& aName,
+ const TimeStamp& aStartTime)
+ : PerformanceEntry(aPerformance->GetParentObject(), aName, u"paint"_ns),
+ mPerformance(aPerformance),
+ mRawStarTime(aStartTime) {}
+
+PerformancePaintTiming::~PerformancePaintTiming() = default;
+
+JSObject* PerformancePaintTiming::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return PerformancePaintTiming_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+DOMHighResTimeStamp PerformancePaintTiming::StartTime() const {
+ if (mCachedStartTime.isNothing()) {
+ DOMHighResTimeStamp rawValue =
+ mPerformance->GetDOMTiming()->TimeStampToDOMHighRes(mRawStarTime);
+ mCachedStartTime.emplace(nsRFPService::ReduceTimePrecisionAsMSecs(
+ rawValue, mPerformance->GetRandomTimelineSeed(),
+ mPerformance->IsSystemPrincipal(),
+ mPerformance->CrossOriginIsolated()));
+ }
+ return mCachedStartTime.value();
+}
+
+size_t PerformancePaintTiming::SizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
diff --git a/dom/performance/PerformancePaintTiming.h b/dom/performance/PerformancePaintTiming.h
new file mode 100644
index 0000000000..dc4dc7f632
--- /dev/null
+++ b/dom/performance/PerformancePaintTiming.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PerformancePaintTiming_h___
+#define mozilla_dom_PerformancePaintTiming_h___
+
+#include "mozilla/dom/PerformanceEntry.h"
+#include "mozilla/dom/PerformancePaintTimingBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+class Performance;
+
+// https://w3c.github.io/paint-timing/#sec-PerformancePaintTiming
+// Unlike timeToContentfulPaint, this timing is generated during
+// displaylist building, when a frame is contentful, we collect
+// the timestamp. Whereas, timeToContentfulPaint uses a compositor-side
+// timestamp.
+class PerformancePaintTiming final : public PerformanceEntry {
+ public:
+ PerformancePaintTiming(Performance* aPerformance, const nsAString& aName,
+ const TimeStamp& aStartTime);
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(PerformancePaintTiming,
+ PerformanceEntry)
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ DOMHighResTimeStamp StartTime() const override;
+
+ size_t SizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const override;
+
+ private:
+ ~PerformancePaintTiming();
+ RefPtr<Performance> mPerformance;
+
+ const TimeStamp mRawStarTime;
+ mutable Maybe<DOMHighResTimeStamp> mCachedStartTime;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_PerformanacePaintTiming_h___ */
diff --git a/dom/performance/PerformanceResourceTiming.cpp b/dom/performance/PerformanceResourceTiming.cpp
new file mode 100644
index 0000000000..b35608fade
--- /dev/null
+++ b/dom/performance/PerformanceResourceTiming.cpp
@@ -0,0 +1,135 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PerformanceResourceTiming.h"
+#include "mozilla/dom/PerformanceResourceTimingBinding.h"
+#include "nsNetUtil.h"
+#include "nsArrayUtils.h"
+
+using namespace mozilla::dom;
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(PerformanceResourceTiming, PerformanceEntry,
+ mPerformance)
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(PerformanceResourceTiming,
+ PerformanceEntry)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PerformanceResourceTiming)
+NS_INTERFACE_MAP_END_INHERITING(PerformanceEntry)
+
+NS_IMPL_ADDREF_INHERITED(PerformanceResourceTiming, PerformanceEntry)
+NS_IMPL_RELEASE_INHERITED(PerformanceResourceTiming, PerformanceEntry)
+
+PerformanceResourceTiming::PerformanceResourceTiming(
+ UniquePtr<PerformanceTimingData>&& aPerformanceTiming,
+ Performance* aPerformance, const nsAString& aName)
+ : PerformanceEntry(aPerformance->GetParentObject(), aName, u"resource"_ns),
+ mTimingData(std::move(aPerformanceTiming)),
+ mPerformance(aPerformance) {
+ MOZ_ASSERT(aPerformance, "Parent performance object should be provided");
+ if (NS_IsMainThread()) {
+ // Used to check if an addon content script has access to this timing.
+ // We don't need it in workers, and ignore mOriginalURI if null.
+ NS_NewURI(getter_AddRefs(mOriginalURI), aName);
+ }
+}
+
+PerformanceResourceTiming::~PerformanceResourceTiming() = default;
+
+DOMHighResTimeStamp PerformanceResourceTiming::StartTime() const {
+ // Force the start time to be the earliest of:
+ // - RedirectStart
+ // - WorkerStart
+ // - AsyncOpen
+ // Ignore zero values. The RedirectStart and WorkerStart values
+ // can come from earlier redirected channels prior to the AsyncOpen
+ // time being recorded.
+ if (mCachedStartTime.isNothing()) {
+ DOMHighResTimeStamp redirect =
+ mTimingData->RedirectStartHighRes(mPerformance);
+ redirect = redirect ? redirect : DBL_MAX;
+
+ DOMHighResTimeStamp worker = mTimingData->WorkerStartHighRes(mPerformance);
+ worker = worker ? worker : DBL_MAX;
+
+ DOMHighResTimeStamp asyncOpen = mTimingData->AsyncOpenHighRes(mPerformance);
+
+ mCachedStartTime.emplace(std::min(asyncOpen, std::min(redirect, worker)));
+ }
+ return mCachedStartTime.value();
+}
+
+JSObject* PerformanceResourceTiming::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return PerformanceResourceTiming_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+size_t PerformanceResourceTiming::SizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+size_t PerformanceResourceTiming::SizeOfExcludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ return PerformanceEntry::SizeOfExcludingThis(aMallocSizeOf) +
+ mInitiatorType.SizeOfExcludingThisIfUnshared(aMallocSizeOf) +
+ (mTimingData
+ ? mTimingData->NextHopProtocol().SizeOfExcludingThisIfUnshared(
+ aMallocSizeOf)
+ : 0);
+}
+
+void PerformanceResourceTiming::GetServerTiming(
+ nsTArray<RefPtr<PerformanceServerTiming>>& aRetval,
+ Maybe<nsIPrincipal*>& aSubjectPrincipal) {
+ aRetval.Clear();
+ if (!TimingAllowedForCaller(aSubjectPrincipal)) {
+ return;
+ }
+
+ nsTArray<nsCOMPtr<nsIServerTiming>> serverTimingArray =
+ mTimingData->GetServerTiming();
+ uint32_t length = serverTimingArray.Length();
+ for (uint32_t index = 0; index < length; ++index) {
+ nsCOMPtr<nsIServerTiming> serverTiming = serverTimingArray.ElementAt(index);
+ MOZ_ASSERT(serverTiming);
+
+ aRetval.AppendElement(
+ new PerformanceServerTiming(GetParentObject(), serverTiming));
+ }
+}
+
+bool PerformanceResourceTiming::TimingAllowedForCaller(
+ Maybe<nsIPrincipal*>& aCaller) const {
+ if (!mTimingData) {
+ return false;
+ }
+
+ if (mTimingData->TimingAllowed()) {
+ return true;
+ }
+
+ // Check if the addon has permission to access the cross-origin resource.
+ return mOriginalURI && aCaller.isSome() &&
+ BasePrincipal::Cast(aCaller.value())->AddonAllowsLoad(mOriginalURI);
+}
+
+bool PerformanceResourceTiming::ReportRedirectForCaller(
+ Maybe<nsIPrincipal*>& aCaller) const {
+ if (!mTimingData) {
+ return false;
+ }
+
+ if (mTimingData->ShouldReportCrossOriginRedirect()) {
+ return true;
+ }
+
+ // Only report cross-origin redirect if the addon has <all_urls> permission.
+ return aCaller.isSome() &&
+ BasePrincipal::Cast(aCaller.value())
+ ->AddonHasPermission(nsGkAtoms::all_urlsPermission);
+}
diff --git a/dom/performance/PerformanceResourceTiming.h b/dom/performance/PerformanceResourceTiming.h
new file mode 100644
index 0000000000..973fbf7ef3
--- /dev/null
+++ b/dom/performance/PerformanceResourceTiming.h
@@ -0,0 +1,190 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PerformanceResourceTiming_h___
+#define mozilla_dom_PerformanceResourceTiming_h___
+
+#include "mozilla/UniquePtr.h"
+#include "nsCOMPtr.h"
+#include "Performance.h"
+#include "PerformanceEntry.h"
+#include "PerformanceServerTiming.h"
+#include "PerformanceTiming.h"
+
+namespace mozilla {
+namespace dom {
+
+// http://www.w3.org/TR/resource-timing/#performanceresourcetiming
+class PerformanceResourceTiming : public PerformanceEntry {
+ public:
+ typedef mozilla::TimeStamp TimeStamp;
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(
+ PerformanceResourceTiming, PerformanceEntry)
+
+ PerformanceResourceTiming(
+ UniquePtr<PerformanceTimingData>&& aPerformanceTimingData,
+ Performance* aPerformance, const nsAString& aName);
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ virtual DOMHighResTimeStamp StartTime() const override;
+
+ virtual DOMHighResTimeStamp Duration() const override {
+ return ResponseEnd() - StartTime();
+ }
+
+ void GetInitiatorType(nsAString& aInitiatorType) const {
+ aInitiatorType = mInitiatorType;
+ }
+
+ void SetInitiatorType(const nsAString& aInitiatorType) {
+ mInitiatorType = aInitiatorType;
+ }
+
+ void GetNextHopProtocol(nsAString& aNextHopProtocol) const {
+ if (mTimingData) {
+ aNextHopProtocol = mTimingData->NextHopProtocol();
+ }
+ }
+
+ DOMHighResTimeStamp WorkerStart() const {
+ return mTimingData ? mTimingData->WorkerStartHighRes(mPerformance) : 0;
+ }
+
+ DOMHighResTimeStamp FetchStart() const {
+ return mTimingData ? mTimingData->FetchStartHighRes(mPerformance) : 0;
+ }
+
+ DOMHighResTimeStamp RedirectStart(
+ Maybe<nsIPrincipal*>& aSubjectPrincipal) const {
+ // We have to check if all the redirect URIs had the same origin (since
+ // there is no check in RedirectStartHighRes())
+ return ReportRedirectForCaller(aSubjectPrincipal)
+ ? mTimingData->RedirectStartHighRes(mPerformance)
+ : 0;
+ }
+
+ DOMHighResTimeStamp RedirectEnd(
+ Maybe<nsIPrincipal*>& aSubjectPrincipal) const {
+ // We have to check if all the redirect URIs had the same origin (since
+ // there is no check in RedirectEndHighRes())
+ return ReportRedirectForCaller(aSubjectPrincipal)
+ ? mTimingData->RedirectEndHighRes(mPerformance)
+ : 0;
+ }
+
+ DOMHighResTimeStamp DomainLookupStart(
+ Maybe<nsIPrincipal*>& aSubjectPrincipal) const {
+ return TimingAllowedForCaller(aSubjectPrincipal)
+ ? mTimingData->DomainLookupStartHighRes(mPerformance)
+ : 0;
+ }
+
+ DOMHighResTimeStamp DomainLookupEnd(
+ Maybe<nsIPrincipal*>& aSubjectPrincipal) const {
+ return TimingAllowedForCaller(aSubjectPrincipal)
+ ? mTimingData->DomainLookupEndHighRes(mPerformance)
+ : 0;
+ }
+
+ DOMHighResTimeStamp ConnectStart(
+ Maybe<nsIPrincipal*>& aSubjectPrincipal) const {
+ return TimingAllowedForCaller(aSubjectPrincipal)
+ ? mTimingData->ConnectStartHighRes(mPerformance)
+ : 0;
+ }
+
+ DOMHighResTimeStamp ConnectEnd(
+ Maybe<nsIPrincipal*>& aSubjectPrincipal) const {
+ return TimingAllowedForCaller(aSubjectPrincipal)
+ ? mTimingData->ConnectEndHighRes(mPerformance)
+ : 0;
+ }
+
+ DOMHighResTimeStamp RequestStart(
+ Maybe<nsIPrincipal*>& aSubjectPrincipal) const {
+ return TimingAllowedForCaller(aSubjectPrincipal)
+ ? mTimingData->RequestStartHighRes(mPerformance)
+ : 0;
+ }
+
+ DOMHighResTimeStamp ResponseStart(
+ Maybe<nsIPrincipal*>& aSubjectPrincipal) const {
+ return TimingAllowedForCaller(aSubjectPrincipal)
+ ? mTimingData->ResponseStartHighRes(mPerformance)
+ : 0;
+ }
+
+ DOMHighResTimeStamp ResponseEnd() const {
+ return mTimingData ? mTimingData->ResponseEndHighRes(mPerformance) : 0;
+ }
+
+ DOMHighResTimeStamp SecureConnectionStart(
+ Maybe<nsIPrincipal*>& aSubjectPrincipal) const {
+ return TimingAllowedForCaller(aSubjectPrincipal)
+ ? mTimingData->SecureConnectionStartHighRes(mPerformance)
+ : 0;
+ }
+
+ virtual const PerformanceResourceTiming* ToResourceTiming() const override {
+ return this;
+ }
+
+ uint64_t TransferSize(Maybe<nsIPrincipal*>& aSubjectPrincipal) const {
+ return TimingAllowedForCaller(aSubjectPrincipal)
+ ? mTimingData->TransferSize()
+ : 0;
+ }
+
+ uint64_t EncodedBodySize(Maybe<nsIPrincipal*>& aSubjectPrincipal) const {
+ return TimingAllowedForCaller(aSubjectPrincipal)
+ ? mTimingData->EncodedBodySize()
+ : 0;
+ }
+
+ uint64_t DecodedBodySize(Maybe<nsIPrincipal*>& aSubjectPrincipal) const {
+ return TimingAllowedForCaller(aSubjectPrincipal)
+ ? mTimingData->DecodedBodySize()
+ : 0;
+ }
+
+ void GetServerTiming(nsTArray<RefPtr<PerformanceServerTiming>>& aRetval,
+ Maybe<nsIPrincipal*>& aSubjectPrincipal);
+
+ size_t SizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const override;
+
+ protected:
+ virtual ~PerformanceResourceTiming();
+
+ size_t SizeOfExcludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const override;
+
+ // Check if caller has access to cross-origin timings, either by the rules
+ // from the spec, or based on addon permissions.
+ bool TimingAllowedForCaller(Maybe<nsIPrincipal*>& aCaller) const;
+
+ // Check if cross-origin redirects should be reported to the caller.
+ bool ReportRedirectForCaller(Maybe<nsIPrincipal*>& aCaller) const;
+
+ nsString mInitiatorType;
+ UniquePtr<PerformanceTimingData> mTimingData;
+ RefPtr<Performance> mPerformance;
+
+ // The same initial requested URI as the `name` attribute.
+ nsCOMPtr<nsIURI> mOriginalURI;
+
+ private:
+ mutable Maybe<DOMHighResTimeStamp> mCachedStartTime;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_PerformanceResourceTiming_h___ */
diff --git a/dom/performance/PerformanceServerTiming.cpp b/dom/performance/PerformanceServerTiming.cpp
new file mode 100644
index 0000000000..e5f056652d
--- /dev/null
+++ b/dom/performance/PerformanceServerTiming.cpp
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PerformanceServerTiming.h"
+#include "nsITimedChannel.h"
+
+#include "mozilla/dom/PerformanceServerTimingBinding.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PerformanceServerTiming, mParent)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(PerformanceServerTiming)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(PerformanceServerTiming)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PerformanceServerTiming)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+JSObject* PerformanceServerTiming::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return mozilla::dom::PerformanceServerTiming_Binding::Wrap(aCx, this,
+ aGivenProto);
+}
+
+void PerformanceServerTiming::GetName(nsAString& aName) const {
+ aName.Truncate();
+
+ if (!mServerTiming) {
+ return;
+ }
+
+ nsAutoCString name;
+ if (NS_WARN_IF(NS_FAILED(mServerTiming->GetName(name)))) {
+ return;
+ }
+
+ aName.Assign(NS_ConvertUTF8toUTF16(name));
+}
+
+DOMHighResTimeStamp PerformanceServerTiming::Duration() const {
+ if (!mServerTiming) {
+ return 0;
+ }
+
+ double duration = 0;
+ if (NS_WARN_IF(NS_FAILED(mServerTiming->GetDuration(&duration)))) {
+ return 0;
+ }
+
+ return duration;
+}
+
+void PerformanceServerTiming::GetDescription(nsAString& aDescription) const {
+ if (!mServerTiming) {
+ return;
+ }
+
+ nsAutoCString description;
+ if (NS_WARN_IF(NS_FAILED(mServerTiming->GetDescription(description)))) {
+ return;
+ }
+
+ aDescription.Assign(NS_ConvertUTF8toUTF16(description));
+}
+
+} // namespace mozilla::dom
diff --git a/dom/performance/PerformanceServerTiming.h b/dom/performance/PerformanceServerTiming.h
new file mode 100644
index 0000000000..90a4b64dbd
--- /dev/null
+++ b/dom/performance/PerformanceServerTiming.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PerformanceServerTiming_h
+#define mozilla_dom_PerformanceServerTiming_h
+
+#include "mozilla/Attributes.h"
+#include "nsCOMPtr.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsWrapperCache.h"
+#include "nsString.h"
+
+class nsIServerTiming;
+class nsISupports;
+
+namespace mozilla {
+namespace dom {
+
+class PerformanceServerTiming final : public nsISupports,
+ public nsWrapperCache {
+ public:
+ PerformanceServerTiming(nsISupports* aParent, nsIServerTiming* aServerTiming)
+ : mParent(aParent), mServerTiming(aServerTiming) {
+ MOZ_ASSERT(mServerTiming);
+ }
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(PerformanceServerTiming)
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ nsISupports* GetParentObject() const { return mParent; }
+
+ void GetName(nsAString& aName) const;
+
+ DOMHighResTimeStamp Duration() const;
+
+ void GetDescription(nsAString& aDescription) const;
+
+ private:
+ ~PerformanceServerTiming() = default;
+
+ nsCOMPtr<nsISupports> mParent;
+ nsCOMPtr<nsIServerTiming> mServerTiming;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PerformanceServerTiming_h
diff --git a/dom/performance/PerformanceService.cpp b/dom/performance/PerformanceService.cpp
new file mode 100644
index 0000000000..43eed2ed4e
--- /dev/null
+++ b/dom/performance/PerformanceService.cpp
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PerformanceService.h"
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/StaticPtr.h"
+#include "prtime.h"
+
+namespace mozilla::dom {
+
+static StaticRefPtr<PerformanceService> gPerformanceService;
+static StaticMutex gPerformanceServiceMutex;
+
+/* static */
+PerformanceService* PerformanceService::GetOrCreate() {
+ StaticMutexAutoLock al(gPerformanceServiceMutex);
+
+ if (!gPerformanceService) {
+ gPerformanceService = new PerformanceService();
+ ClearOnShutdown(&gPerformanceService);
+ }
+
+ return gPerformanceService;
+}
+
+DOMHighResTimeStamp PerformanceService::TimeOrigin(
+ const TimeStamp& aCreationTimeStamp) const {
+ return (aCreationTimeStamp - mCreationTimeStamp).ToMilliseconds() +
+ (mCreationEpochTime / PR_USEC_PER_MSEC);
+}
+
+PerformanceService::PerformanceService() {
+ mCreationTimeStamp = TimeStamp::Now();
+ mCreationEpochTime = PR_Now();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/performance/PerformanceService.h b/dom/performance/PerformanceService.h
new file mode 100644
index 0000000000..21d19a0e75
--- /dev/null
+++ b/dom/performance/PerformanceService.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef dom_performance_PerformanceService_h
+#define dom_performance_PerformanceService_h
+
+#include "mozilla/TimeStamp.h"
+#include "nsCOMPtr.h"
+#include "nsDOMNavigationTiming.h"
+
+namespace mozilla {
+namespace dom {
+
+// This class is thread-safe.
+
+// We use this singleton for having the correct value of performance.timeOrigin.
+// This value must be calculated on top of the pair:
+// - mCreationTimeStamp (monotonic clock)
+// - mCreationEpochTime (unix epoch time)
+// These 2 values must be taken "at the same time" in order to be used
+// correctly.
+
+class PerformanceService {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PerformanceService)
+
+ static PerformanceService* GetOrCreate();
+
+ DOMHighResTimeStamp TimeOrigin(const TimeStamp& aCreationTimeStamp) const;
+
+ private:
+ PerformanceService();
+ ~PerformanceService() = default;
+
+ TimeStamp mCreationTimeStamp;
+ PRTime mCreationEpochTime;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // dom_performance_PerformanceService_h
diff --git a/dom/performance/PerformanceStorage.h b/dom/performance/PerformanceStorage.h
new file mode 100644
index 0000000000..269dce6b0b
--- /dev/null
+++ b/dom/performance/PerformanceStorage.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PerformanceStorage_h
+#define mozilla_dom_PerformanceStorage_h
+
+#include "nsISupportsImpl.h"
+
+class nsIHttpChannel;
+class nsITimedChannel;
+
+namespace mozilla {
+namespace dom {
+
+class PerformanceTimingData;
+
+class PerformanceStorage {
+ public:
+ NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
+
+ virtual void AddEntry(nsIHttpChannel* aChannel,
+ nsITimedChannel* aTimedChannel) = 0;
+ virtual void AddEntry(const nsString& entryName,
+ const nsString& initiatorType,
+ UniquePtr<PerformanceTimingData>&& aData) = 0;
+
+ protected:
+ virtual ~PerformanceStorage() = default;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PerformanceStorage_h
diff --git a/dom/performance/PerformanceStorageWorker.cpp b/dom/performance/PerformanceStorageWorker.cpp
new file mode 100644
index 0000000000..1e63912573
--- /dev/null
+++ b/dom/performance/PerformanceStorageWorker.cpp
@@ -0,0 +1,167 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PerformanceStorageWorker.h"
+#include "Performance.h"
+#include "PerformanceResourceTiming.h"
+#include "PerformanceTiming.h"
+#include "mozilla/dom/WorkerRef.h"
+#include "mozilla/dom/WorkerRunnable.h"
+#include "mozilla/dom/WorkerPrivate.h"
+
+namespace mozilla::dom {
+
+class PerformanceProxyData {
+ public:
+ PerformanceProxyData(UniquePtr<PerformanceTimingData>&& aData,
+ const nsAString& aInitiatorType,
+ const nsAString& aEntryName)
+ : mData(std::move(aData)),
+ mInitiatorType(aInitiatorType),
+ mEntryName(aEntryName) {}
+
+ UniquePtr<PerformanceTimingData> mData;
+ nsString mInitiatorType;
+ nsString mEntryName;
+};
+
+namespace {
+
+// Here we use control runnable because this code must be executed also when in
+// a sync event loop
+class PerformanceEntryAdder final : public WorkerControlRunnable {
+ public:
+ PerformanceEntryAdder(WorkerPrivate* aWorkerPrivate,
+ PerformanceStorageWorker* aStorage,
+ UniquePtr<PerformanceProxyData>&& aData)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
+ mStorage(aStorage),
+ mData(std::move(aData)) {}
+
+ bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
+ mStorage->AddEntryOnWorker(std::move(mData));
+ return true;
+ }
+
+ nsresult Cancel() override {
+ mStorage->ShutdownOnWorker();
+ return WorkerRunnable::Cancel();
+ }
+
+ bool PreDispatch(WorkerPrivate* aWorkerPrivate) override { return true; }
+
+ void PostDispatch(WorkerPrivate* aWorkerPrivate,
+ bool aDispatchResult) override {}
+
+ private:
+ RefPtr<PerformanceStorageWorker> mStorage;
+ UniquePtr<PerformanceProxyData> mData;
+};
+
+} // namespace
+
+/* static */
+already_AddRefed<PerformanceStorageWorker> PerformanceStorageWorker::Create(
+ WorkerPrivate* aWorkerPrivate) {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ RefPtr<PerformanceStorageWorker> storage = new PerformanceStorageWorker();
+
+ storage->mWorkerRef = WeakWorkerRef::Create(
+ aWorkerPrivate, [storage]() { storage->ShutdownOnWorker(); });
+
+ // PerformanceStorageWorker is created at the creation time of the worker.
+ MOZ_ASSERT(storage->mWorkerRef);
+
+ return storage.forget();
+}
+
+PerformanceStorageWorker::PerformanceStorageWorker()
+ : mMutex("PerformanceStorageWorker::mMutex") {}
+
+PerformanceStorageWorker::~PerformanceStorageWorker() = default;
+
+void PerformanceStorageWorker::AddEntry(nsIHttpChannel* aChannel,
+ nsITimedChannel* aTimedChannel) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ MutexAutoLock lock(mMutex);
+
+ if (!mWorkerRef) {
+ return;
+ }
+
+ // If we have mWorkerRef, we haven't received the WorkerRef notification and
+ // we haven't yet call ShutdownOnWorker, which uses the mutex.
+ WorkerPrivate* workerPrivate = mWorkerRef->GetUnsafePrivate();
+ MOZ_ASSERT(workerPrivate);
+
+ nsAutoString initiatorType;
+ nsAutoString entryName;
+
+ UniquePtr<PerformanceTimingData> performanceTimingData(
+ PerformanceTimingData::Create(aTimedChannel, aChannel, 0, initiatorType,
+ entryName));
+ if (!performanceTimingData) {
+ return;
+ }
+
+ UniquePtr<PerformanceProxyData> data(new PerformanceProxyData(
+ std::move(performanceTimingData), initiatorType, entryName));
+
+ RefPtr<PerformanceEntryAdder> r =
+ new PerformanceEntryAdder(workerPrivate, this, std::move(data));
+ Unused << NS_WARN_IF(!r->Dispatch());
+}
+
+void PerformanceStorageWorker::ShutdownOnWorker() {
+ MutexAutoLock lock(mMutex);
+
+ if (!mWorkerRef) {
+ return;
+ }
+
+ MOZ_ASSERT(IsCurrentThreadRunningWorker());
+
+ mWorkerRef = nullptr;
+}
+
+void PerformanceStorageWorker::AddEntryOnWorker(
+ UniquePtr<PerformanceProxyData>&& aData) {
+ RefPtr<Performance> performance;
+ UniquePtr<PerformanceProxyData> data = std::move(aData);
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (!mWorkerRef) {
+ return;
+ }
+
+ // We must have the workerPrivate because it is available until a
+ // notification is received by WorkerRef and we use mutex to make the code
+ // protected.
+ WorkerPrivate* workerPrivate = mWorkerRef->GetPrivate();
+ MOZ_ASSERT(workerPrivate);
+
+ WorkerGlobalScope* scope = workerPrivate->GlobalScope();
+ performance = scope->GetPerformance();
+ }
+
+ if (NS_WARN_IF(!performance)) {
+ return;
+ }
+
+ RefPtr<PerformanceResourceTiming> performanceEntry =
+ new PerformanceResourceTiming(std::move(data->mData), performance,
+ data->mEntryName);
+ performanceEntry->SetInitiatorType(data->mInitiatorType);
+
+ performance->InsertResourceEntry(performanceEntry);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/performance/PerformanceStorageWorker.h b/dom/performance/PerformanceStorageWorker.h
new file mode 100644
index 0000000000..4f0ba6042c
--- /dev/null
+++ b/dom/performance/PerformanceStorageWorker.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PerformanceStorageWorker_h
+#define mozilla_dom_PerformanceStorageWorker_h
+
+#include "mozilla/Mutex.h"
+#include "PerformanceStorage.h"
+
+namespace mozilla {
+namespace dom {
+
+class WeakWorkerRef;
+class WorkerPrivate;
+
+class PerformanceProxyData;
+
+class PerformanceStorageWorker final : public PerformanceStorage {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PerformanceStorageWorker, override)
+
+ static already_AddRefed<PerformanceStorageWorker> Create(
+ WorkerPrivate* aWorkerPrivate);
+
+ void ShutdownOnWorker();
+
+ void AddEntry(nsIHttpChannel* aChannel,
+ nsITimedChannel* aTimedChannel) override;
+ virtual void AddEntry(const nsString& entryName,
+ const nsString& initiatorType,
+ UniquePtr<PerformanceTimingData>&& aData) override {}
+ void AddEntryOnWorker(UniquePtr<PerformanceProxyData>&& aData);
+
+ private:
+ PerformanceStorageWorker();
+ ~PerformanceStorageWorker();
+
+ Mutex mMutex;
+
+ // Protected by mutex.
+ // Created and released on worker-thread. Used also on main-thread.
+ RefPtr<WeakWorkerRef> mWorkerRef;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PerformanceStorageWorker_h
diff --git a/dom/performance/PerformanceTiming.cpp b/dom/performance/PerformanceTiming.cpp
new file mode 100644
index 0000000000..cd434b22a4
--- /dev/null
+++ b/dom/performance/PerformanceTiming.cpp
@@ -0,0 +1,617 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PerformanceTiming.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/dom/PerformanceTimingBinding.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/Telemetry.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIHttpChannel.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/Document.h"
+#include "nsITimedChannel.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PerformanceTiming, mPerformance)
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(PerformanceTiming, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(PerformanceTiming, Release)
+
+/* static */
+PerformanceTimingData* PerformanceTimingData::Create(
+ nsITimedChannel* aTimedChannel, nsIHttpChannel* aChannel,
+ DOMHighResTimeStamp aZeroTime, nsAString& aInitiatorType,
+ nsAString& aEntryName) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Check if resource timing is prefed off.
+ if (!StaticPrefs::dom_enable_resource_timing()) {
+ return nullptr;
+ }
+
+ if (!aChannel || !aTimedChannel) {
+ return nullptr;
+ }
+
+ bool reportTiming = true;
+ aTimedChannel->GetReportResourceTiming(&reportTiming);
+
+ if (!reportTiming) {
+ return nullptr;
+ }
+
+ aTimedChannel->GetInitiatorType(aInitiatorType);
+
+ // If the initiator type had no valid value, then set it to the default
+ // ("other") value.
+ if (aInitiatorType.IsEmpty()) {
+ aInitiatorType = u"other"_ns;
+ }
+
+ // According to the spec, "The name attribute must return the resolved URL
+ // of the requested resource. This attribute must not change even if the
+ // fetch redirected to a different URL."
+ nsCOMPtr<nsIURI> originalURI;
+ aChannel->GetOriginalURI(getter_AddRefs(originalURI));
+
+ nsAutoCString name;
+ originalURI->GetSpec(name);
+ CopyUTF8toUTF16(name, aEntryName);
+
+ // The nsITimedChannel argument will be used to gather all the timings.
+ // The nsIHttpChannel argument will be used to check if any cross-origin
+ // redirects occurred.
+ // The last argument is the "zero time" (offset). Since we don't want
+ // any offset for the resource timing, this will be set to "0" - the
+ // resource timing returns a relative timing (no offset).
+ return new PerformanceTimingData(aTimedChannel, aChannel, 0);
+}
+
+PerformanceTiming::PerformanceTiming(Performance* aPerformance,
+ nsITimedChannel* aChannel,
+ nsIHttpChannel* aHttpChannel,
+ DOMHighResTimeStamp aZeroTime)
+ : mPerformance(aPerformance) {
+ MOZ_ASSERT(aPerformance, "Parent performance object should be provided");
+
+ mTimingData.reset(new PerformanceTimingData(
+ aChannel, aHttpChannel,
+ nsRFPService::ReduceTimePrecisionAsMSecs(
+ aZeroTime, aPerformance->GetRandomTimelineSeed(),
+ aPerformance->IsSystemPrincipal(),
+ aPerformance->CrossOriginIsolated())));
+
+ // Non-null aHttpChannel implies that this PerformanceTiming object is being
+ // used for subresources, which is irrelevant to this probe.
+ if (!aHttpChannel && StaticPrefs::dom_enable_performance() &&
+ IsTopLevelContentDocument()) {
+ Telemetry::Accumulate(Telemetry::TIME_TO_RESPONSE_START_MS,
+ mTimingData->ResponseStartHighRes(aPerformance) -
+ mTimingData->ZeroTime());
+ }
+}
+
+// Copy the timing info from the channel so we don't need to keep the channel
+// alive just to get the timestamps.
+PerformanceTimingData::PerformanceTimingData(nsITimedChannel* aChannel,
+ nsIHttpChannel* aHttpChannel,
+ DOMHighResTimeStamp aZeroTime)
+ : mZeroTime(0.0),
+ mFetchStart(0.0),
+ mEncodedBodySize(0),
+ mTransferSize(0),
+ mDecodedBodySize(0),
+ mRedirectCount(0),
+ mAllRedirectsSameOrigin(true),
+ mReportCrossOriginRedirect(true),
+ mSecureConnection(false),
+ mTimingAllowed(true),
+ mInitialized(false) {
+ mInitialized = !!aChannel;
+ mZeroTime = aZeroTime;
+
+ if (!StaticPrefs::dom_enable_performance() ||
+ nsContentUtils::ShouldResistFingerprinting()) {
+ mZeroTime = 0;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ if (aHttpChannel) {
+ aHttpChannel->GetURI(getter_AddRefs(uri));
+ } else {
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
+ if (httpChannel) {
+ httpChannel->GetURI(getter_AddRefs(uri));
+ }
+ }
+
+ if (uri) {
+ mSecureConnection = uri->SchemeIs("https");
+ }
+
+ if (aChannel) {
+ aChannel->GetAsyncOpen(&mAsyncOpen);
+ aChannel->GetAllRedirectsSameOrigin(&mAllRedirectsSameOrigin);
+ aChannel->GetRedirectCount(&mRedirectCount);
+ aChannel->GetRedirectStart(&mRedirectStart);
+ aChannel->GetRedirectEnd(&mRedirectEnd);
+ aChannel->GetDomainLookupStart(&mDomainLookupStart);
+ aChannel->GetDomainLookupEnd(&mDomainLookupEnd);
+ aChannel->GetConnectStart(&mConnectStart);
+ aChannel->GetSecureConnectionStart(&mSecureConnectionStart);
+ aChannel->GetConnectEnd(&mConnectEnd);
+ aChannel->GetRequestStart(&mRequestStart);
+ aChannel->GetResponseStart(&mResponseStart);
+ aChannel->GetCacheReadStart(&mCacheReadStart);
+ aChannel->GetResponseEnd(&mResponseEnd);
+ aChannel->GetCacheReadEnd(&mCacheReadEnd);
+
+ aChannel->GetDispatchFetchEventStart(&mWorkerStart);
+ aChannel->GetHandleFetchEventStart(&mWorkerRequestStart);
+ // TODO: Track when FetchEvent.respondWith() promise resolves as
+ // ServiceWorker interception responseStart?
+ aChannel->GetHandleFetchEventEnd(&mWorkerResponseEnd);
+
+ // The performance timing api essentially requires that the event timestamps
+ // have a strict relation with each other. The truth, however, is the
+ // browser engages in a number of speculative activities that sometimes mean
+ // connections and lookups begin at different times. Workaround that here by
+ // clamping these values to what we expect FetchStart to be. This means the
+ // later of AsyncOpen or WorkerStart times.
+ if (!mAsyncOpen.IsNull()) {
+ // We want to clamp to the expected FetchStart value. This is later of
+ // the AsyncOpen and WorkerStart values.
+ const TimeStamp* clampTime = &mAsyncOpen;
+ if (!mWorkerStart.IsNull() && mWorkerStart > mAsyncOpen) {
+ clampTime = &mWorkerStart;
+ }
+
+ if (!mDomainLookupStart.IsNull() && mDomainLookupStart < *clampTime) {
+ mDomainLookupStart = *clampTime;
+ }
+
+ if (!mDomainLookupEnd.IsNull() && mDomainLookupEnd < *clampTime) {
+ mDomainLookupEnd = *clampTime;
+ }
+
+ if (!mConnectStart.IsNull() && mConnectStart < *clampTime) {
+ mConnectStart = *clampTime;
+ }
+
+ if (mSecureConnection && !mSecureConnectionStart.IsNull() &&
+ mSecureConnectionStart < *clampTime) {
+ mSecureConnectionStart = *clampTime;
+ }
+
+ if (!mConnectEnd.IsNull() && mConnectEnd < *clampTime) {
+ mConnectEnd = *clampTime;
+ }
+ }
+ }
+
+ // The aHttpChannel argument is null if this PerformanceTiming object is
+ // being used for navigation timing (which is only relevant for documents).
+ // It has a non-null value if this PerformanceTiming object is being used
+ // for resource timing, which can include document loads, both toplevel and
+ // in subframes, and resources linked from a document.
+ if (aHttpChannel) {
+ SetPropertiesFromHttpChannel(aHttpChannel, aChannel);
+ }
+}
+
+void PerformanceTimingData::SetPropertiesFromHttpChannel(
+ nsIHttpChannel* aHttpChannel, nsITimedChannel* aChannel) {
+ MOZ_ASSERT(aHttpChannel);
+
+ nsAutoCString protocol;
+ Unused << aHttpChannel->GetProtocolVersion(protocol);
+ CopyUTF8toUTF16(protocol, mNextHopProtocol);
+
+ Unused << aHttpChannel->GetEncodedBodySize(&mEncodedBodySize);
+ Unused << aHttpChannel->GetTransferSize(&mTransferSize);
+ Unused << aHttpChannel->GetDecodedBodySize(&mDecodedBodySize);
+ if (mDecodedBodySize == 0) {
+ mDecodedBodySize = mEncodedBodySize;
+ }
+
+ mTimingAllowed = CheckAllowedOrigin(aHttpChannel, aChannel);
+ bool redirectsPassCheck = false;
+ aChannel->GetAllRedirectsPassTimingAllowCheck(&redirectsPassCheck);
+ mReportCrossOriginRedirect = mTimingAllowed && redirectsPassCheck;
+
+ aChannel->GetNativeServerTiming(mServerTiming);
+}
+
+PerformanceTiming::~PerformanceTiming() = default;
+
+DOMHighResTimeStamp PerformanceTimingData::FetchStartHighRes(
+ Performance* aPerformance) {
+ MOZ_ASSERT(aPerformance);
+
+ if (!mFetchStart) {
+ if (!StaticPrefs::dom_enable_performance() || !IsInitialized()) {
+ return mZeroTime;
+ }
+ MOZ_ASSERT(!mAsyncOpen.IsNull(),
+ "The fetch start time stamp should always be "
+ "valid if the performance timing is enabled");
+ if (!mAsyncOpen.IsNull()) {
+ if (!mWorkerRequestStart.IsNull() && mWorkerRequestStart > mAsyncOpen) {
+ mFetchStart = TimeStampToDOMHighRes(aPerformance, mWorkerRequestStart);
+ } else {
+ mFetchStart = TimeStampToDOMHighRes(aPerformance, mAsyncOpen);
+ }
+ }
+ }
+ return nsRFPService::ReduceTimePrecisionAsMSecs(
+ mFetchStart, aPerformance->GetRandomTimelineSeed(),
+ aPerformance->IsSystemPrincipal(), aPerformance->CrossOriginIsolated());
+}
+
+DOMTimeMilliSec PerformanceTiming::FetchStart() {
+ return static_cast<int64_t>(mTimingData->FetchStartHighRes(mPerformance));
+}
+
+bool PerformanceTimingData::CheckAllowedOrigin(nsIHttpChannel* aResourceChannel,
+ nsITimedChannel* aChannel) {
+ if (!IsInitialized()) {
+ return false;
+ }
+
+ // Check that the current document passes the ckeck.
+ nsCOMPtr<nsILoadInfo> loadInfo = aResourceChannel->LoadInfo();
+
+ // TYPE_DOCUMENT loads have no loadingPrincipal.
+ if (loadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_DOCUMENT) {
+ return true;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = loadInfo->GetLoadingPrincipal();
+
+ // Check if the resource is either same origin as the page that started
+ // the load, or if the response contains the proper Timing-Allow-Origin
+ // header with the domain of the page that started the load.
+ return aChannel->TimingAllowCheck(principal);
+}
+
+uint8_t PerformanceTimingData::GetRedirectCount() const {
+ if (!StaticPrefs::dom_enable_performance() || !IsInitialized() ||
+ nsContentUtils::ShouldResistFingerprinting()) {
+ return 0;
+ }
+ if (!mAllRedirectsSameOrigin) {
+ return 0;
+ }
+ return mRedirectCount;
+}
+
+bool PerformanceTimingData::ShouldReportCrossOriginRedirect() const {
+ if (!StaticPrefs::dom_enable_performance() || !IsInitialized() ||
+ nsContentUtils::ShouldResistFingerprinting()) {
+ return false;
+ }
+
+ // If the redirect count is 0, or if one of the cross-origin
+ // redirects doesn't have the proper Timing-Allow-Origin header,
+ // then RedirectStart and RedirectEnd will be set to zero
+ return (mRedirectCount != 0) && mReportCrossOriginRedirect;
+}
+
+DOMHighResTimeStamp PerformanceTimingData::AsyncOpenHighRes(
+ Performance* aPerformance) {
+ MOZ_ASSERT(aPerformance);
+
+ if (!StaticPrefs::dom_enable_performance() || !IsInitialized() ||
+ mAsyncOpen.IsNull()) {
+ return mZeroTime;
+ }
+ DOMHighResTimeStamp rawValue =
+ TimeStampToDOMHighRes(aPerformance, mAsyncOpen);
+ return nsRFPService::ReduceTimePrecisionAsMSecs(
+ rawValue, aPerformance->GetRandomTimelineSeed(),
+ aPerformance->IsSystemPrincipal(), aPerformance->CrossOriginIsolated());
+}
+
+DOMHighResTimeStamp PerformanceTimingData::WorkerStartHighRes(
+ Performance* aPerformance) {
+ MOZ_ASSERT(aPerformance);
+
+ if (!StaticPrefs::dom_enable_performance() || !IsInitialized() ||
+ mWorkerStart.IsNull()) {
+ return mZeroTime;
+ }
+ DOMHighResTimeStamp rawValue =
+ TimeStampToDOMHighRes(aPerformance, mWorkerStart);
+ return nsRFPService::ReduceTimePrecisionAsMSecs(
+ rawValue, aPerformance->GetRandomTimelineSeed(),
+ aPerformance->IsSystemPrincipal(), aPerformance->CrossOriginIsolated());
+}
+
+/**
+ * RedirectStartHighRes() is used by both the navigation timing and the
+ * resource timing. Since, navigation timing and resource timing check and
+ * interpret cross-domain redirects in a different manner,
+ * RedirectStartHighRes() will make no checks for cross-domain redirect.
+ * It's up to the consumers of this method (PerformanceTiming::RedirectStart()
+ * and PerformanceResourceTiming::RedirectStart() to make such verifications.
+ *
+ * @return a valid timing if the Performance Timing is enabled
+ */
+DOMHighResTimeStamp PerformanceTimingData::RedirectStartHighRes(
+ Performance* aPerformance) {
+ MOZ_ASSERT(aPerformance);
+
+ if (!StaticPrefs::dom_enable_performance() || !IsInitialized()) {
+ return mZeroTime;
+ }
+ return TimeStampToReducedDOMHighResOrFetchStart(aPerformance, mRedirectStart);
+}
+
+DOMTimeMilliSec PerformanceTiming::RedirectStart() {
+ if (!mTimingData->IsInitialized()) {
+ return 0;
+ }
+ // We have to check if all the redirect URIs had the same origin (since there
+ // is no check in RedirectStartHighRes())
+ if (mTimingData->AllRedirectsSameOrigin() &&
+ mTimingData->RedirectCountReal()) {
+ return static_cast<int64_t>(
+ mTimingData->RedirectStartHighRes(mPerformance));
+ }
+ return 0;
+}
+
+/**
+ * RedirectEndHighRes() is used by both the navigation timing and the resource
+ * timing. Since, navigation timing and resource timing check and interpret
+ * cross-domain redirects in a different manner, RedirectEndHighRes() will make
+ * no checks for cross-domain redirect. It's up to the consumers of this method
+ * (PerformanceTiming::RedirectEnd() and
+ * PerformanceResourceTiming::RedirectEnd() to make such verifications.
+ *
+ * @return a valid timing if the Performance Timing is enabled
+ */
+DOMHighResTimeStamp PerformanceTimingData::RedirectEndHighRes(
+ Performance* aPerformance) {
+ MOZ_ASSERT(aPerformance);
+
+ if (!StaticPrefs::dom_enable_performance() || !IsInitialized()) {
+ return mZeroTime;
+ }
+ return TimeStampToReducedDOMHighResOrFetchStart(aPerformance, mRedirectEnd);
+}
+
+DOMTimeMilliSec PerformanceTiming::RedirectEnd() {
+ if (!mTimingData->IsInitialized()) {
+ return 0;
+ }
+ // We have to check if all the redirect URIs had the same origin (since there
+ // is no check in RedirectEndHighRes())
+ if (mTimingData->AllRedirectsSameOrigin() &&
+ mTimingData->RedirectCountReal()) {
+ return static_cast<int64_t>(mTimingData->RedirectEndHighRes(mPerformance));
+ }
+ return 0;
+}
+
+DOMHighResTimeStamp PerformanceTimingData::DomainLookupStartHighRes(
+ Performance* aPerformance) {
+ MOZ_ASSERT(aPerformance);
+
+ if (!StaticPrefs::dom_enable_performance() || !IsInitialized()) {
+ return mZeroTime;
+ }
+ // Bug 1637985 - DomainLookup information may be useful for fingerprinting.
+ if (nsContentUtils::ShouldResistFingerprinting()) {
+ return FetchStartHighRes(aPerformance);
+ }
+ return TimeStampToReducedDOMHighResOrFetchStart(aPerformance,
+ mDomainLookupStart);
+}
+
+DOMTimeMilliSec PerformanceTiming::DomainLookupStart() {
+ return static_cast<int64_t>(
+ mTimingData->DomainLookupStartHighRes(mPerformance));
+}
+
+DOMHighResTimeStamp PerformanceTimingData::DomainLookupEndHighRes(
+ Performance* aPerformance) {
+ MOZ_ASSERT(aPerformance);
+
+ if (!StaticPrefs::dom_enable_performance() || !IsInitialized()) {
+ return mZeroTime;
+ }
+ // Bug 1637985 - DomainLookup information may be useful for fingerprinting.
+ if (nsContentUtils::ShouldResistFingerprinting()) {
+ return FetchStartHighRes(aPerformance);
+ }
+ // Bug 1155008 - nsHttpTransaction is racy. Return DomainLookupStart when null
+ if (mDomainLookupEnd.IsNull()) {
+ return DomainLookupStartHighRes(aPerformance);
+ }
+ DOMHighResTimeStamp rawValue =
+ TimeStampToDOMHighRes(aPerformance, mDomainLookupEnd);
+ return nsRFPService::ReduceTimePrecisionAsMSecs(
+ rawValue, aPerformance->GetRandomTimelineSeed(),
+ aPerformance->IsSystemPrincipal(), aPerformance->CrossOriginIsolated());
+}
+
+DOMTimeMilliSec PerformanceTiming::DomainLookupEnd() {
+ return static_cast<int64_t>(
+ mTimingData->DomainLookupEndHighRes(mPerformance));
+}
+
+DOMHighResTimeStamp PerformanceTimingData::ConnectStartHighRes(
+ Performance* aPerformance) {
+ MOZ_ASSERT(aPerformance);
+
+ if (!StaticPrefs::dom_enable_performance() || !IsInitialized()) {
+ return mZeroTime;
+ }
+ if (mConnectStart.IsNull()) {
+ return DomainLookupEndHighRes(aPerformance);
+ }
+ DOMHighResTimeStamp rawValue =
+ TimeStampToDOMHighRes(aPerformance, mConnectStart);
+ return nsRFPService::ReduceTimePrecisionAsMSecs(
+ rawValue, aPerformance->GetRandomTimelineSeed(),
+ aPerformance->IsSystemPrincipal(), aPerformance->CrossOriginIsolated());
+}
+
+DOMTimeMilliSec PerformanceTiming::ConnectStart() {
+ return static_cast<int64_t>(mTimingData->ConnectStartHighRes(mPerformance));
+}
+
+DOMHighResTimeStamp PerformanceTimingData::SecureConnectionStartHighRes(
+ Performance* aPerformance) {
+ MOZ_ASSERT(aPerformance);
+
+ if (!StaticPrefs::dom_enable_performance() || !IsInitialized()) {
+ return mZeroTime;
+ }
+ if (!mSecureConnection) {
+ return 0; // We use 0 here, because mZeroTime is sometimes set to the
+ // navigation start time.
+ }
+ if (mSecureConnectionStart.IsNull()) {
+ return ConnectStartHighRes(aPerformance);
+ }
+ DOMHighResTimeStamp rawValue =
+ TimeStampToDOMHighRes(aPerformance, mSecureConnectionStart);
+ return nsRFPService::ReduceTimePrecisionAsMSecs(
+ rawValue, aPerformance->GetRandomTimelineSeed(),
+ aPerformance->IsSystemPrincipal(), aPerformance->CrossOriginIsolated());
+}
+
+DOMTimeMilliSec PerformanceTiming::SecureConnectionStart() {
+ return static_cast<int64_t>(
+ mTimingData->SecureConnectionStartHighRes(mPerformance));
+}
+
+DOMHighResTimeStamp PerformanceTimingData::ConnectEndHighRes(
+ Performance* aPerformance) {
+ MOZ_ASSERT(aPerformance);
+
+ if (!StaticPrefs::dom_enable_performance() || !IsInitialized()) {
+ return mZeroTime;
+ }
+ // Bug 1155008 - nsHttpTransaction is racy. Return ConnectStart when null
+ if (mConnectEnd.IsNull()) {
+ return ConnectStartHighRes(aPerformance);
+ }
+ DOMHighResTimeStamp rawValue =
+ TimeStampToDOMHighRes(aPerformance, mConnectEnd);
+ return nsRFPService::ReduceTimePrecisionAsMSecs(
+ rawValue, aPerformance->GetRandomTimelineSeed(),
+ aPerformance->IsSystemPrincipal(), aPerformance->CrossOriginIsolated());
+}
+
+DOMTimeMilliSec PerformanceTiming::ConnectEnd() {
+ return static_cast<int64_t>(mTimingData->ConnectEndHighRes(mPerformance));
+}
+
+DOMHighResTimeStamp PerformanceTimingData::RequestStartHighRes(
+ Performance* aPerformance) {
+ MOZ_ASSERT(aPerformance);
+
+ if (!StaticPrefs::dom_enable_performance() || !IsInitialized()) {
+ return mZeroTime;
+ }
+
+ if (mRequestStart.IsNull()) {
+ mRequestStart = mWorkerRequestStart;
+ }
+
+ return TimeStampToReducedDOMHighResOrFetchStart(aPerformance, mRequestStart);
+}
+
+DOMTimeMilliSec PerformanceTiming::RequestStart() {
+ return static_cast<int64_t>(mTimingData->RequestStartHighRes(mPerformance));
+}
+
+DOMHighResTimeStamp PerformanceTimingData::ResponseStartHighRes(
+ Performance* aPerformance) {
+ MOZ_ASSERT(aPerformance);
+
+ if (!StaticPrefs::dom_enable_performance() || !IsInitialized()) {
+ return mZeroTime;
+ }
+ if (mResponseStart.IsNull() ||
+ (!mCacheReadStart.IsNull() && mCacheReadStart < mResponseStart)) {
+ mResponseStart = mCacheReadStart;
+ }
+
+ if (mResponseStart.IsNull() ||
+ (!mRequestStart.IsNull() && mResponseStart < mRequestStart)) {
+ mResponseStart = mRequestStart;
+ }
+ return TimeStampToReducedDOMHighResOrFetchStart(aPerformance, mResponseStart);
+}
+
+DOMTimeMilliSec PerformanceTiming::ResponseStart() {
+ return static_cast<int64_t>(mTimingData->ResponseStartHighRes(mPerformance));
+}
+
+DOMHighResTimeStamp PerformanceTimingData::ResponseEndHighRes(
+ Performance* aPerformance) {
+ MOZ_ASSERT(aPerformance);
+
+ if (!StaticPrefs::dom_enable_performance() || !IsInitialized()) {
+ return mZeroTime;
+ }
+ if (mResponseEnd.IsNull() ||
+ (!mCacheReadEnd.IsNull() && mCacheReadEnd < mResponseEnd)) {
+ mResponseEnd = mCacheReadEnd;
+ }
+ if (mResponseEnd.IsNull()) {
+ mResponseEnd = mWorkerResponseEnd;
+ }
+ // Bug 1155008 - nsHttpTransaction is racy. Return ResponseStart when null
+ if (mResponseEnd.IsNull()) {
+ return ResponseStartHighRes(aPerformance);
+ }
+ DOMHighResTimeStamp rawValue =
+ TimeStampToDOMHighRes(aPerformance, mResponseEnd);
+ return nsRFPService::ReduceTimePrecisionAsMSecs(
+ rawValue, aPerformance->GetRandomTimelineSeed(),
+ aPerformance->IsSystemPrincipal(), aPerformance->CrossOriginIsolated());
+}
+
+DOMTimeMilliSec PerformanceTiming::ResponseEnd() {
+ return static_cast<int64_t>(mTimingData->ResponseEndHighRes(mPerformance));
+}
+
+JSObject* PerformanceTiming::WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return PerformanceTiming_Binding::Wrap(cx, this, aGivenProto);
+}
+
+bool PerformanceTiming::IsTopLevelContentDocument() const {
+ nsCOMPtr<Document> document = mPerformance->GetDocumentIfCurrent();
+ if (!document) {
+ return false;
+ }
+
+ if (BrowsingContext* bc = document->GetBrowsingContext()) {
+ return bc->IsTopContent();
+ }
+ return false;
+}
+
+nsTArray<nsCOMPtr<nsIServerTiming>> PerformanceTimingData::GetServerTiming() {
+ if (!StaticPrefs::dom_enable_performance() || !IsInitialized() ||
+ !TimingAllowed()) {
+ return nsTArray<nsCOMPtr<nsIServerTiming>>();
+ }
+
+ return mServerTiming.Clone();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/performance/PerformanceTiming.h b/dom/performance/PerformanceTiming.h
new file mode 100644
index 0000000000..4f832cca78
--- /dev/null
+++ b/dom/performance/PerformanceTiming.h
@@ -0,0 +1,595 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PerformanceTiming_h
+#define mozilla_dom_PerformanceTiming_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "nsContentUtils.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsRFPService.h"
+#include "nsWrapperCache.h"
+#include "Performance.h"
+#include "nsITimedChannel.h"
+#include "mozilla/ipc/IPDLParamTraits.h"
+#include "ipc/IPCMessageUtils.h"
+#include "ipc/IPCMessageUtilsSpecializations.h"
+#include "mozilla/net/nsServerTiming.h"
+
+class nsIHttpChannel;
+
+namespace mozilla {
+namespace dom {
+
+class PerformanceTiming;
+
+class PerformanceTimingData final {
+ friend class PerformanceTiming;
+ friend struct mozilla::ipc::IPDLParamTraits<
+ mozilla::dom::PerformanceTimingData>;
+
+ public:
+ PerformanceTimingData() = default; // For deserialization
+ // This can return null.
+ static PerformanceTimingData* Create(nsITimedChannel* aChannel,
+ nsIHttpChannel* aHttpChannel,
+ DOMHighResTimeStamp aZeroTime,
+ nsAString& aInitiatorType,
+ nsAString& aEntryName);
+
+ PerformanceTimingData(nsITimedChannel* aChannel, nsIHttpChannel* aHttpChannel,
+ DOMHighResTimeStamp aZeroTime);
+
+ void SetPropertiesFromHttpChannel(nsIHttpChannel* aHttpChannel,
+ nsITimedChannel* aChannel);
+
+ bool IsInitialized() const { return mInitialized; }
+
+ const nsString& NextHopProtocol() const { return mNextHopProtocol; }
+
+ uint64_t TransferSize() const { return mTransferSize; }
+
+ uint64_t EncodedBodySize() const { return mEncodedBodySize; }
+
+ uint64_t DecodedBodySize() const { return mDecodedBodySize; }
+
+ /**
+ * @param aStamp
+ * The TimeStamp recorded for a specific event. This TimeStamp can
+ * be null.
+ * @return the duration of an event with a given TimeStamp, relative to the
+ * navigationStart TimeStamp (the moment the user landed on the
+ * page), if the given TimeStamp is valid. Otherwise, it will return
+ * the FetchStart timing value.
+ */
+ inline DOMHighResTimeStamp TimeStampToReducedDOMHighResOrFetchStart(
+ Performance* aPerformance, TimeStamp aStamp) {
+ MOZ_ASSERT(aPerformance);
+
+ if (aStamp.IsNull()) {
+ return FetchStartHighRes(aPerformance);
+ }
+
+ DOMHighResTimeStamp rawTimestamp =
+ TimeStampToDOMHighRes(aPerformance, aStamp);
+
+ return nsRFPService::ReduceTimePrecisionAsMSecs(
+ rawTimestamp, aPerformance->GetRandomTimelineSeed(),
+ aPerformance->IsSystemPrincipal(), aPerformance->CrossOriginIsolated());
+ }
+
+ /**
+ * The nsITimedChannel records an absolute timestamp for each event.
+ * The nsDOMNavigationTiming will record the moment when the user landed on
+ * the page. This is a window.performance unique timestamp, so it can be used
+ * for all the events (navigation timing and resource timing events).
+ *
+ * The algorithm operates in 2 steps:
+ * 1. The first step is to subtract the two timestamps: the argument (the
+ * event's timestamp) and the navigation start timestamp. This will result in
+ * a relative timestamp of the event (relative to the navigation start -
+ * window.performance.timing.navigationStart).
+ * 2. The second step is to add any required offset (the mZeroTime). For now,
+ * this offset value is either 0 (for the resource timing), or equal to
+ * "performance.navigationStart" (for navigation timing).
+ * For the resource timing, mZeroTime is set to 0, causing the result to be a
+ * relative time.
+ * For the navigation timing, mZeroTime is set to
+ * "performance.navigationStart" causing the result be an absolute time.
+ *
+ * @param aStamp
+ * The TimeStamp recorded for a specific event. This TimeStamp can't
+ * be null.
+ * @return number of milliseconds value as one of:
+ * - relative to the navigation start time, time the user has landed on the
+ * page
+ * - an absolute wall clock time since the unix epoch
+ */
+ inline DOMHighResTimeStamp TimeStampToDOMHighRes(Performance* aPerformance,
+ TimeStamp aStamp) const {
+ MOZ_ASSERT(aPerformance);
+ MOZ_ASSERT(!aStamp.IsNull());
+
+ TimeDuration duration = aStamp - aPerformance->CreationTimeStamp();
+ return duration.ToMilliseconds() + mZeroTime;
+ }
+
+ // The last channel's AsyncOpen time. This may occur before the FetchStart
+ // in some cases.
+ DOMHighResTimeStamp AsyncOpenHighRes(Performance* aPerformance);
+
+ // High resolution (used by resource timing)
+ DOMHighResTimeStamp WorkerStartHighRes(Performance* aPerformance);
+ DOMHighResTimeStamp FetchStartHighRes(Performance* aPerformance);
+ DOMHighResTimeStamp RedirectStartHighRes(Performance* aPerformance);
+ DOMHighResTimeStamp RedirectEndHighRes(Performance* aPerformance);
+ DOMHighResTimeStamp DomainLookupStartHighRes(Performance* aPerformance);
+ DOMHighResTimeStamp DomainLookupEndHighRes(Performance* aPerformance);
+ DOMHighResTimeStamp ConnectStartHighRes(Performance* aPerformance);
+ DOMHighResTimeStamp SecureConnectionStartHighRes(Performance* aPerformance);
+ DOMHighResTimeStamp ConnectEndHighRes(Performance* aPerformance);
+ DOMHighResTimeStamp RequestStartHighRes(Performance* aPerformance);
+ DOMHighResTimeStamp ResponseStartHighRes(Performance* aPerformance);
+ DOMHighResTimeStamp ResponseEndHighRes(Performance* aPerformance);
+
+ DOMHighResTimeStamp ZeroTime() const { return mZeroTime; }
+
+ uint8_t RedirectCountReal() const { return mRedirectCount; }
+ uint8_t GetRedirectCount() const;
+
+ bool AllRedirectsSameOrigin() const { return mAllRedirectsSameOrigin; }
+
+ // If this is false the values of redirectStart/End will be 0 This is false if
+ // no redirects occured, or if any of the responses failed the
+ // timing-allow-origin check in HttpBaseChannel::TimingAllowCheck
+ bool ShouldReportCrossOriginRedirect() const;
+
+ // Cached result of CheckAllowedOrigin. If false, security sensitive
+ // attributes of the resourceTiming object will be set to 0
+ bool TimingAllowed() const { return mTimingAllowed; }
+
+ nsTArray<nsCOMPtr<nsIServerTiming>> GetServerTiming();
+
+ private:
+ // Checks if the resource is either same origin as the page that started
+ // the load, or if the response contains the Timing-Allow-Origin header
+ // with a value of * or matching the domain of the loading Principal
+ bool CheckAllowedOrigin(nsIHttpChannel* aResourceChannel,
+ nsITimedChannel* aChannel);
+
+ nsTArray<nsCOMPtr<nsIServerTiming>> mServerTiming;
+ nsString mNextHopProtocol;
+
+ TimeStamp mAsyncOpen;
+ TimeStamp mRedirectStart;
+ TimeStamp mRedirectEnd;
+ TimeStamp mDomainLookupStart;
+ TimeStamp mDomainLookupEnd;
+ TimeStamp mConnectStart;
+ TimeStamp mSecureConnectionStart;
+ TimeStamp mConnectEnd;
+ TimeStamp mRequestStart;
+ TimeStamp mResponseStart;
+ TimeStamp mCacheReadStart;
+ TimeStamp mResponseEnd;
+ TimeStamp mCacheReadEnd;
+
+ // ServiceWorker interception timing information
+ TimeStamp mWorkerStart;
+ TimeStamp mWorkerRequestStart;
+ TimeStamp mWorkerResponseEnd;
+
+ // This is an offset that will be added to each timing ([ms] resolution).
+ // There are only 2 possible values: (1) logicaly equal to navigationStart
+ // TimeStamp (results are absolute timstamps - wallclock); (2) "0" (results
+ // are relative to the navigation start).
+ DOMHighResTimeStamp mZeroTime = 0;
+
+ DOMHighResTimeStamp mFetchStart = 0;
+
+ uint64_t mEncodedBodySize = 0;
+ uint64_t mTransferSize = 0;
+ uint64_t mDecodedBodySize = 0;
+
+ uint8_t mRedirectCount = 0;
+
+ bool mAllRedirectsSameOrigin = false;
+
+ // If the resourceTiming object should have non-zero redirectStart and
+ // redirectEnd attributes. It is false if there were no redirects, or if any
+ // of the responses didn't pass the timing-allow-check
+ bool mReportCrossOriginRedirect = false;
+
+ bool mSecureConnection = false;
+
+ bool mTimingAllowed = false;
+
+ bool mInitialized = false;
+};
+
+// Script "performance.timing" object
+class PerformanceTiming final : public nsWrapperCache {
+ public:
+ /**
+ * @param aPerformance
+ * The performance object (the JS parent).
+ * This will allow access to "window.performance.timing" attribute
+ * for the navigation timing (can't be null).
+ * @param aChannel
+ * An nsITimedChannel used to gather all the networking timings by
+ * both the navigation timing and the resource timing (can't be null).
+ * @param aHttpChannel
+ * An nsIHttpChannel (the resource's http channel).
+ * This will be used by the resource timing cross-domain check
+ * algorithm.
+ * Argument is null for the navigation timing (navigation timing uses
+ * another algorithm for the cross-domain redirects).
+ * @param aZeroTime
+ * The offset that will be added to the timestamp of each event. This
+ * argument should be equal to performance.navigationStart for
+ * navigation timing and "0" for the resource timing.
+ */
+ PerformanceTiming(Performance* aPerformance, nsITimedChannel* aChannel,
+ nsIHttpChannel* aHttpChannel,
+ DOMHighResTimeStamp aZeroTime);
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(PerformanceTiming)
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(PerformanceTiming)
+
+ nsDOMNavigationTiming* GetDOMTiming() const {
+ return mPerformance->GetDOMTiming();
+ }
+
+ Performance* GetParentObject() const { return mPerformance; }
+
+ virtual JSObject* WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // PerformanceNavigation WebIDL methods
+ DOMTimeMilliSec NavigationStart() const {
+ if (!StaticPrefs::dom_enable_performance()) {
+ return 0;
+ }
+ return nsRFPService::ReduceTimePrecisionAsMSecs(
+ GetDOMTiming()->GetNavigationStart(),
+ mPerformance->GetRandomTimelineSeed(),
+ mPerformance->IsSystemPrincipal(), mPerformance->CrossOriginIsolated());
+ }
+
+ DOMTimeMilliSec UnloadEventStart() {
+ if (!StaticPrefs::dom_enable_performance()) {
+ return 0;
+ }
+ return nsRFPService::ReduceTimePrecisionAsMSecs(
+ GetDOMTiming()->GetUnloadEventStart(),
+ mPerformance->GetRandomTimelineSeed(),
+ mPerformance->IsSystemPrincipal(), mPerformance->CrossOriginIsolated());
+ }
+
+ DOMTimeMilliSec UnloadEventEnd() {
+ if (!StaticPrefs::dom_enable_performance()) {
+ return 0;
+ }
+ return nsRFPService::ReduceTimePrecisionAsMSecs(
+ GetDOMTiming()->GetUnloadEventEnd(),
+ mPerformance->GetRandomTimelineSeed(),
+ mPerformance->IsSystemPrincipal(), mPerformance->CrossOriginIsolated());
+ }
+
+ // Low resolution (used by navigation timing)
+ DOMTimeMilliSec FetchStart();
+ DOMTimeMilliSec RedirectStart();
+ DOMTimeMilliSec RedirectEnd();
+ DOMTimeMilliSec DomainLookupStart();
+ DOMTimeMilliSec DomainLookupEnd();
+ DOMTimeMilliSec ConnectStart();
+ DOMTimeMilliSec SecureConnectionStart();
+ DOMTimeMilliSec ConnectEnd();
+ DOMTimeMilliSec RequestStart();
+ DOMTimeMilliSec ResponseStart();
+ DOMTimeMilliSec ResponseEnd();
+
+ DOMTimeMilliSec DomLoading() {
+ if (!StaticPrefs::dom_enable_performance()) {
+ return 0;
+ }
+ return nsRFPService::ReduceTimePrecisionAsMSecs(
+ GetDOMTiming()->GetDomLoading(), mPerformance->GetRandomTimelineSeed(),
+ mPerformance->IsSystemPrincipal(), mPerformance->CrossOriginIsolated());
+ }
+
+ DOMTimeMilliSec DomInteractive() const {
+ if (!StaticPrefs::dom_enable_performance()) {
+ return 0;
+ }
+ return nsRFPService::ReduceTimePrecisionAsMSecs(
+ GetDOMTiming()->GetDomInteractive(),
+ mPerformance->GetRandomTimelineSeed(),
+ mPerformance->IsSystemPrincipal(), mPerformance->CrossOriginIsolated());
+ }
+
+ DOMTimeMilliSec DomContentLoadedEventStart() const {
+ if (!StaticPrefs::dom_enable_performance()) {
+ return 0;
+ }
+ return nsRFPService::ReduceTimePrecisionAsMSecs(
+ GetDOMTiming()->GetDomContentLoadedEventStart(),
+ mPerformance->GetRandomTimelineSeed(),
+ mPerformance->IsSystemPrincipal(), mPerformance->CrossOriginIsolated());
+ }
+
+ DOMTimeMilliSec DomContentLoadedEventEnd() const {
+ if (!StaticPrefs::dom_enable_performance()) {
+ return 0;
+ }
+ return nsRFPService::ReduceTimePrecisionAsMSecs(
+ GetDOMTiming()->GetDomContentLoadedEventEnd(),
+ mPerformance->GetRandomTimelineSeed(),
+ mPerformance->IsSystemPrincipal(), mPerformance->CrossOriginIsolated());
+ }
+
+ DOMTimeMilliSec DomComplete() const {
+ if (!StaticPrefs::dom_enable_performance()) {
+ return 0;
+ }
+ return nsRFPService::ReduceTimePrecisionAsMSecs(
+ GetDOMTiming()->GetDomComplete(), mPerformance->GetRandomTimelineSeed(),
+ mPerformance->IsSystemPrincipal(), mPerformance->CrossOriginIsolated());
+ }
+
+ DOMTimeMilliSec LoadEventStart() const {
+ if (!StaticPrefs::dom_enable_performance()) {
+ return 0;
+ }
+ return nsRFPService::ReduceTimePrecisionAsMSecs(
+ GetDOMTiming()->GetLoadEventStart(),
+ mPerformance->GetRandomTimelineSeed(),
+ mPerformance->IsSystemPrincipal(), mPerformance->CrossOriginIsolated());
+ }
+
+ DOMTimeMilliSec LoadEventEnd() const {
+ if (!StaticPrefs::dom_enable_performance()) {
+ return 0;
+ }
+ return nsRFPService::ReduceTimePrecisionAsMSecs(
+ GetDOMTiming()->GetLoadEventEnd(),
+ mPerformance->GetRandomTimelineSeed(),
+ mPerformance->IsSystemPrincipal(), mPerformance->CrossOriginIsolated());
+ }
+
+ DOMTimeMilliSec TimeToNonBlankPaint() const {
+ if (!StaticPrefs::dom_enable_performance()) {
+ return 0;
+ }
+ return nsRFPService::ReduceTimePrecisionAsMSecs(
+ GetDOMTiming()->GetTimeToNonBlankPaint(),
+ mPerformance->GetRandomTimelineSeed(),
+ mPerformance->IsSystemPrincipal(), mPerformance->CrossOriginIsolated());
+ }
+
+ DOMTimeMilliSec TimeToContentfulPaint() const {
+ if (!StaticPrefs::dom_enable_performance()) {
+ return 0;
+ }
+ return nsRFPService::ReduceTimePrecisionAsMSecs(
+ GetDOMTiming()->GetTimeToContentfulPaint(),
+ mPerformance->GetRandomTimelineSeed(),
+ mPerformance->IsSystemPrincipal(), mPerformance->CrossOriginIsolated());
+ }
+
+ DOMTimeMilliSec TimeToDOMContentFlushed() const {
+ if (!StaticPrefs::dom_enable_performance()) {
+ return 0;
+ }
+ return nsRFPService::ReduceTimePrecisionAsMSecs(
+ GetDOMTiming()->GetTimeToDOMContentFlushed(),
+ mPerformance->GetRandomTimelineSeed(),
+ mPerformance->IsSystemPrincipal(), mPerformance->CrossOriginIsolated());
+ }
+
+ DOMTimeMilliSec TimeToFirstInteractive() const {
+ if (!StaticPrefs::dom_enable_performance()) {
+ return 0;
+ }
+ return nsRFPService::ReduceTimePrecisionAsMSecs(
+ GetDOMTiming()->GetTimeToTTFI(), mPerformance->GetRandomTimelineSeed(),
+ mPerformance->IsSystemPrincipal(), mPerformance->CrossOriginIsolated());
+ }
+
+ PerformanceTimingData* Data() const { return mTimingData.get(); }
+
+ private:
+ ~PerformanceTiming();
+
+ bool IsTopLevelContentDocument() const;
+
+ RefPtr<Performance> mPerformance;
+
+ UniquePtr<PerformanceTimingData> mTimingData;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+namespace mozilla {
+namespace ipc {
+
+template <>
+struct IPDLParamTraits<mozilla::dom::PerformanceTimingData> {
+ typedef mozilla::dom::PerformanceTimingData paramType;
+ static void Write(IPC::Message* aMsg, IProtocol* aActor,
+ const paramType& aParam) {
+ WriteIPDLParam(aMsg, aActor, aParam.mServerTiming);
+ WriteIPDLParam(aMsg, aActor, aParam.mNextHopProtocol);
+ WriteIPDLParam(aMsg, aActor, aParam.mAsyncOpen);
+ WriteIPDLParam(aMsg, aActor, aParam.mRedirectStart);
+ WriteIPDLParam(aMsg, aActor, aParam.mRedirectEnd);
+ WriteIPDLParam(aMsg, aActor, aParam.mDomainLookupStart);
+ WriteIPDLParam(aMsg, aActor, aParam.mDomainLookupEnd);
+ WriteIPDLParam(aMsg, aActor, aParam.mConnectStart);
+ WriteIPDLParam(aMsg, aActor, aParam.mSecureConnectionStart);
+ WriteIPDLParam(aMsg, aActor, aParam.mConnectEnd);
+ WriteIPDLParam(aMsg, aActor, aParam.mRequestStart);
+ WriteIPDLParam(aMsg, aActor, aParam.mResponseStart);
+ WriteIPDLParam(aMsg, aActor, aParam.mCacheReadStart);
+ WriteIPDLParam(aMsg, aActor, aParam.mResponseEnd);
+ WriteIPDLParam(aMsg, aActor, aParam.mCacheReadEnd);
+ WriteIPDLParam(aMsg, aActor, aParam.mWorkerStart);
+ WriteIPDLParam(aMsg, aActor, aParam.mWorkerRequestStart);
+ WriteIPDLParam(aMsg, aActor, aParam.mWorkerResponseEnd);
+ WriteIPDLParam(aMsg, aActor, aParam.mZeroTime);
+ WriteIPDLParam(aMsg, aActor, aParam.mFetchStart);
+ WriteIPDLParam(aMsg, aActor, aParam.mEncodedBodySize);
+ WriteIPDLParam(aMsg, aActor, aParam.mTransferSize);
+ WriteIPDLParam(aMsg, aActor, aParam.mDecodedBodySize);
+ WriteIPDLParam(aMsg, aActor, aParam.mRedirectCount);
+ WriteIPDLParam(aMsg, aActor, aParam.mAllRedirectsSameOrigin);
+ WriteIPDLParam(aMsg, aActor, aParam.mReportCrossOriginRedirect);
+ WriteIPDLParam(aMsg, aActor, aParam.mSecureConnection);
+ WriteIPDLParam(aMsg, aActor, aParam.mTimingAllowed);
+ WriteIPDLParam(aMsg, aActor, aParam.mInitialized);
+ }
+
+ static bool Read(const IPC::Message* aMsg, PickleIterator* aIter,
+ IProtocol* aActor, paramType* aResult) {
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mServerTiming)) {
+ return false;
+ }
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mNextHopProtocol)) {
+ return false;
+ }
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mAsyncOpen)) {
+ return false;
+ }
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mRedirectStart)) {
+ return false;
+ }
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mRedirectEnd)) {
+ return false;
+ }
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mDomainLookupStart)) {
+ return false;
+ }
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mDomainLookupEnd)) {
+ return false;
+ }
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mConnectStart)) {
+ return false;
+ }
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mSecureConnectionStart)) {
+ return false;
+ }
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mConnectEnd)) {
+ return false;
+ }
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mRequestStart)) {
+ return false;
+ }
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mResponseStart)) {
+ return false;
+ }
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mCacheReadStart)) {
+ return false;
+ }
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mResponseEnd)) {
+ return false;
+ }
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mCacheReadEnd)) {
+ return false;
+ }
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mWorkerStart)) {
+ return false;
+ }
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mWorkerRequestStart)) {
+ return false;
+ }
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mWorkerResponseEnd)) {
+ return false;
+ }
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mZeroTime)) {
+ return false;
+ }
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mFetchStart)) {
+ return false;
+ }
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mEncodedBodySize)) {
+ return false;
+ }
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mTransferSize)) {
+ return false;
+ }
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mDecodedBodySize)) {
+ return false;
+ }
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mRedirectCount)) {
+ return false;
+ }
+ if (!ReadIPDLParam(aMsg, aIter, aActor,
+ &aResult->mAllRedirectsSameOrigin)) {
+ return false;
+ }
+ if (!ReadIPDLParam(aMsg, aIter, aActor,
+ &aResult->mReportCrossOriginRedirect)) {
+ return false;
+ }
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mSecureConnection)) {
+ return false;
+ }
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mTimingAllowed)) {
+ return false;
+ }
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mInitialized)) {
+ return false;
+ }
+ return true;
+ }
+};
+
+template <>
+struct IPDLParamTraits<nsCOMPtr<nsIServerTiming>> {
+ typedef nsCOMPtr<nsIServerTiming> paramType;
+ static void Write(IPC::Message* aMsg, IProtocol* aActor,
+ const paramType& aParam) {
+ nsAutoCString name;
+ Unused << aParam->GetName(name);
+ double duration = 0;
+ Unused << aParam->GetDuration(&duration);
+ nsAutoCString description;
+ Unused << aParam->GetDescription(description);
+ WriteIPDLParam(aMsg, aActor, name);
+ WriteIPDLParam(aMsg, aActor, duration);
+ WriteIPDLParam(aMsg, aActor, description);
+ }
+
+ static bool Read(const IPC::Message* aMsg, PickleIterator* aIter,
+ IProtocol* aActor, paramType* aResult) {
+ nsAutoCString name;
+ double duration;
+ nsAutoCString description;
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &name)) {
+ return false;
+ }
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &duration)) {
+ return false;
+ }
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &description)) {
+ return false;
+ }
+
+ RefPtr<nsServerTiming> timing = new nsServerTiming();
+ timing->SetName(name);
+ timing->SetDuration(duration);
+ timing->SetDescription(description);
+ *aResult = timing;
+ return true;
+ }
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // mozilla_dom_PerformanceTiming_h
diff --git a/dom/performance/PerformanceWorker.cpp b/dom/performance/PerformanceWorker.cpp
new file mode 100644
index 0000000000..496fc3e492
--- /dev/null
+++ b/dom/performance/PerformanceWorker.cpp
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PerformanceWorker.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/StaticPrefs_dom.h"
+
+namespace mozilla::dom {
+
+PerformanceWorker::PerformanceWorker(WorkerPrivate* aWorkerPrivate)
+ : Performance(aWorkerPrivate->GlobalScope(),
+ aWorkerPrivate->UsesSystemPrincipal()),
+ mWorkerPrivate(aWorkerPrivate) {
+ mWorkerPrivate->AssertIsOnWorkerThread();
+}
+
+PerformanceWorker::~PerformanceWorker() {
+ mWorkerPrivate->AssertIsOnWorkerThread();
+}
+
+void PerformanceWorker::InsertUserEntry(PerformanceEntry* aEntry) {
+ if (StaticPrefs::dom_performance_enable_user_timing_logging()) {
+ nsAutoCString uri;
+ nsCOMPtr<nsIURI> scriptURI = mWorkerPrivate->GetResolvedScriptURI();
+ if (!scriptURI || NS_FAILED(scriptURI->GetHost(uri))) {
+ // If we have no URI, just put in "none".
+ uri.AssignLiteral("none");
+ }
+ Performance::LogEntry(aEntry, uri);
+ }
+ Performance::InsertUserEntry(aEntry);
+}
+
+TimeStamp PerformanceWorker::CreationTimeStamp() const {
+ return mWorkerPrivate->CreationTimeStamp();
+}
+
+DOMHighResTimeStamp PerformanceWorker::CreationTime() const {
+ return mWorkerPrivate->CreationTime();
+}
+
+uint64_t PerformanceWorker::GetRandomTimelineSeed() {
+ return mWorkerPrivate->GetRandomTimelineSeed();
+}
+
+bool PerformanceWorker::CrossOriginIsolated() const {
+ return mWorkerPrivate->CrossOriginIsolated();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/performance/PerformanceWorker.h b/dom/performance/PerformanceWorker.h
new file mode 100644
index 0000000000..99f7fdaa25
--- /dev/null
+++ b/dom/performance/PerformanceWorker.h
@@ -0,0 +1,87 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PerformanceWorker_h
+#define mozilla_dom_PerformanceWorker_h
+
+#include "Performance.h"
+
+namespace mozilla {
+namespace dom {
+
+class WorkerPrivate;
+
+class PerformanceWorker final : public Performance {
+ public:
+ explicit PerformanceWorker(WorkerPrivate* aWorkerPrivate);
+
+ PerformanceStorage* AsPerformanceStorage() override {
+ MOZ_CRASH("This should not be called on workers.");
+ return nullptr;
+ }
+
+ virtual PerformanceTiming* Timing() override {
+ MOZ_CRASH("This should not be called on workers.");
+ return nullptr;
+ }
+
+ virtual PerformanceNavigation* Navigation() override {
+ MOZ_CRASH("This should not be called on workers.");
+ return nullptr;
+ }
+
+ virtual void SetFCPTimingEntry(PerformancePaintTiming* aEntry) override {
+ MOZ_CRASH("This should not be called on workers.");
+ }
+
+ TimeStamp CreationTimeStamp() const override;
+
+ DOMHighResTimeStamp CreationTime() const override;
+
+ virtual void GetMozMemory(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aObj) override {
+ MOZ_CRASH("This should not be called on workers.");
+ }
+
+ virtual nsDOMNavigationTiming* GetDOMTiming() const override {
+ MOZ_CRASH("This should not be called on workers.");
+ return nullptr;
+ }
+
+ virtual uint64_t GetRandomTimelineSeed() override;
+
+ virtual nsITimedChannel* GetChannel() const override {
+ MOZ_CRASH("This should not be called on workers.");
+ return nullptr;
+ }
+
+ void QueueNavigationTimingEntry() override {
+ MOZ_CRASH("This should not be called on workers.");
+ }
+
+ void UpdateNavigationTimingEntry() override {
+ MOZ_CRASH("This should not be called on workers.");
+ }
+
+ bool CrossOriginIsolated() const override;
+
+ protected:
+ ~PerformanceWorker();
+
+ void InsertUserEntry(PerformanceEntry* aEntry) override;
+
+ void DispatchBufferFullEvent() override {
+ // Nothing to do here. See bug 1432758.
+ }
+
+ private:
+ WorkerPrivate* mWorkerPrivate;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PerformanceWorker_h
diff --git a/dom/performance/moz.build b/dom/performance/moz.build
new file mode 100644
index 0000000000..63d2ab978e
--- /dev/null
+++ b/dom/performance/moz.build
@@ -0,0 +1,54 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Performance")
+
+EXPORTS.mozilla.dom += [
+ "Performance.h",
+ "PerformanceEntry.h",
+ "PerformanceMainThread.h",
+ "PerformanceMark.h",
+ "PerformanceMeasure.h",
+ "PerformanceNavigation.h",
+ "PerformanceNavigationTiming.h",
+ "PerformanceObserver.h",
+ "PerformanceObserverEntryList.h",
+ "PerformancePaintTiming.h",
+ "PerformanceResourceTiming.h",
+ "PerformanceServerTiming.h",
+ "PerformanceService.h",
+ "PerformanceStorage.h",
+ "PerformanceStorageWorker.h",
+ "PerformanceTiming.h",
+]
+
+UNIFIED_SOURCES += [
+ "Performance.cpp",
+ "PerformanceEntry.cpp",
+ "PerformanceMainThread.cpp",
+ "PerformanceMark.cpp",
+ "PerformanceMeasure.cpp",
+ "PerformanceNavigation.cpp",
+ "PerformanceNavigationTiming.cpp",
+ "PerformanceObserver.cpp",
+ "PerformanceObserverEntryList.cpp",
+ "PerformancePaintTiming.cpp",
+ "PerformanceResourceTiming.cpp",
+ "PerformanceServerTiming.cpp",
+ "PerformanceService.cpp",
+ "PerformanceStorageWorker.cpp",
+ "PerformanceTiming.cpp",
+ "PerformanceWorker.cpp",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+MOCHITEST_MANIFESTS += ["tests/mochitest.ini"]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
diff --git a/dom/performance/tests/.eslintrc.js b/dom/performance/tests/.eslintrc.js
new file mode 100644
index 0000000000..845ed3f013
--- /dev/null
+++ b/dom/performance/tests/.eslintrc.js
@@ -0,0 +1,5 @@
+"use strict";
+
+module.exports = {
+ extends: ["plugin:mozilla/mochitest-test"],
+};
diff --git a/dom/performance/tests/empty.js b/dom/performance/tests/empty.js
new file mode 100644
index 0000000000..3b44754e30
--- /dev/null
+++ b/dom/performance/tests/empty.js
@@ -0,0 +1 @@
+/* Nothing here */
diff --git a/dom/performance/tests/logo.png b/dom/performance/tests/logo.png
new file mode 100644
index 0000000000..a05926bcd7
--- /dev/null
+++ b/dom/performance/tests/logo.png
Binary files differ
diff --git a/dom/performance/tests/mochitest.ini b/dom/performance/tests/mochitest.ini
new file mode 100644
index 0000000000..a43158f771
--- /dev/null
+++ b/dom/performance/tests/mochitest.ini
@@ -0,0 +1,37 @@
+[DEFAULT]
+support-files =
+ test_performance_observer.js
+ test_performance_user_timing.js
+ test_worker_performance_now.js
+ worker_performance_user_timing.js
+ worker_performance_observer.js
+ sharedworker_performance_user_timing.js
+ test_worker_performance_entries.js
+ test_worker_performance_entries.sjs
+ empty.js
+ serverTiming.sjs
+
+[test_performance_observer.html]
+[test_performance_user_timing.html]
+[test_performance_navigation_timing.html]
+[test_performance_paint_timing.html]
+support-files =
+ test_performance_paint_timing_helper.html
+ logo.png
+[test_performance_paint_observer.html]
+support-files =
+ test_performance_paint_observer_helper.html
+ logo.png
+[test_worker_user_timing.html]
+[test_worker_observer.html]
+[test_sharedWorker_performance_user_timing.html]
+skip-if = true # Bug 1571904
+[test_worker_performance_now.html]
+[test_timeOrigin.html]
+skip-if = toolkit == 'android' && !is_fennec # Bug 1525959
+[test_worker_performance_entries.html]
+skip-if = toolkit == 'android' && !is_fennec # Bug 1525959
+[test_performance_timing_json.html]
+[test_performance_server_timing.html]
+scheme = https
+[test_performance_server_timing_plain_http.html]
diff --git a/dom/performance/tests/serverTiming.sjs b/dom/performance/tests/serverTiming.sjs
new file mode 100644
index 0000000000..73028a004d
--- /dev/null
+++ b/dom/performance/tests/serverTiming.sjs
@@ -0,0 +1,32 @@
+
+var responseServerTiming = [{metric:"metric1", duration:"123.4", description:"description1"},
+ {metric:"metric2", duration:"456.78", description:"description2"}];
+var trailerServerTiming = [{metric:"metric3", duration:"789.11", description:"description3"},
+ {metric:"metric4", duration:"1112.13", description:"description4"}];
+
+function createServerTimingHeader(headerData) {
+ var header = "";
+ for (var i = 0; i < headerData.length; i++) {
+ header += "Server-Timing:" + headerData[i].metric + ";" +
+ "dur=" + headerData[i].duration + ";" +
+ "desc=" + headerData[i].description + "\r\n";
+ }
+ return header;
+}
+
+function handleRequest(request, response)
+{
+ var body = "c\r\ndata reached\r\n3\r\nhej\r\n0\r\n";
+
+ response.seizePower();
+ response.write("HTTP/1.1 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write(createServerTimingHeader(responseServerTiming));
+
+ response.write("Transfer-Encoding: chunked\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.write(createServerTimingHeader(trailerServerTiming));
+ response.write("\r\n");
+ response.finish();
+}
diff --git a/dom/performance/tests/sharedworker_performance_user_timing.js b/dom/performance/tests/sharedworker_performance_user_timing.js
new file mode 100644
index 0000000000..a5838c41f0
--- /dev/null
+++ b/dom/performance/tests/sharedworker_performance_user_timing.js
@@ -0,0 +1,38 @@
+var port;
+
+function ok(a, msg) {
+ dump("OK: " + !!a + " => " + a + " " + msg + "\n");
+ port.postMessage({ type: "status", status: !!a, msg: a + ": " + msg });
+}
+
+function is(a, b, msg) {
+ dump("IS: " + (a === b) + " => " + a + " | " + b + " " + msg + "\n");
+ port.postMessage({
+ type: "status",
+ status: a === b,
+ msg: a + " === " + b + ": " + msg,
+ });
+}
+
+function isnot(a, b, msg) {
+ dump("ISNOT: " + (a === b) + " => " + a + " | " + b + " " + msg + "\n");
+ port.postMessage({
+ type: "status",
+ status: a != b,
+ msg: a + " != " + b + ": " + msg,
+ });
+}
+
+importScripts("test_performance_user_timing.js");
+
+onconnect = function(evt) {
+ port = evt.ports[0];
+
+ for (var i = 0; i < steps.length; ++i) {
+ performance.clearMarks();
+ performance.clearMeasures();
+ steps[i]();
+ }
+
+ port.postMessage({ type: "finish" });
+};
diff --git a/dom/performance/tests/test_performance_navigation_timing.html b/dom/performance/tests/test_performance_navigation_timing.html
new file mode 100644
index 0000000000..869d0dc106
--- /dev/null
+++ b/dom/performance/tests/test_performance_navigation_timing.html
@@ -0,0 +1,104 @@
+<!DOCTYPE HTML>
+<html>
+ <!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=1462891
+ -->
+ <head>
+ <title>Test for Bug 1462891</title>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ </head>
+ <body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1462891">Mozilla Bug 1462891 - Navigation Timing API</a>
+ <div id="content">
+ </div>
+ <pre id="test">
+ <script class="testbody" type="text/javascript">
+ var index = 0;
+ let isRounded = (x, shouldRound, expectedPrecision) => {
+ if (!shouldRound)
+ return true;
+
+ let rounded = (Math.floor(x / expectedPrecision) * expectedPrecision);
+ // First we do the perfectly normal check that should work just fine
+ if (rounded === x || x === 0)
+ return true;
+
+ // When we're diving by non-whole numbers, we may not get perfect
+ // multiplication/division because of floating points.
+ // When dealing with ms since epoch, a double's precision is on the order
+ // of 1/5 of a microsecond, so we use a value a little higher than that as
+ // our epsilon.
+ // To be clear, this error is introduced in our re-calculation of 'rounded'
+ // above in JavaScript.
+ if (Math.abs(rounded - x + expectedPrecision) < .0005) {
+ return true;
+ } else if (Math.abs(rounded - x) < .0005) {
+ return true;
+ }
+
+ // Then we handle the case where you're sub-millisecond and the timer is not
+ // We check that the timer is not sub-millisecond by assuming it is not if it
+ // returns an even number of milliseconds
+ if (expectedPrecision < 1 && Math.round(x) == x) {
+ if (Math.round(rounded) == x) {
+ return true;
+ }
+ }
+
+ ok(false, "Looming Test Failure, Additional Debugging Info: Expected Precision: " + expectedPrecision + " Measured Value: " + x +
+ " Rounded Vaue: " + rounded + " Fuzzy1: " + Math.abs(rounded - x + expectedPrecision) +
+ " Fuzzy 2: " + Math.abs(rounded - x));
+
+ return false;
+ };
+
+ var metrics = [
+ "unloadEventStart",
+ "unloadEventEnd",
+ "domInteractive",
+ "domContentLoadedEventStart",
+ "domContentLoadedEventEnd",
+ "domComplete",
+ "loadEventStart",
+ "loadEventEnd"
+ ];
+
+ async function runTests(resistFingerprinting, reduceTimerPrecision, expectedPrecision) {
+ await SpecialPowers.pushPrefEnv({
+ "set": [["privacy.resistFingerprinting", resistFingerprinting],
+ ["privacy.reduceTimerPrecision", reduceTimerPrecision],
+ ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
+ ]});
+ var entries = performance.getEntriesByType("navigation");
+ is(entries.length, resistFingerprinting ? 0 : 1, "Checking PerformanceNavigationEntry count");
+
+ for (let i=0; i<entries.length; i++) {
+ for (let j=0; j<metrics.length; j++) {
+ ok(isRounded(entries[i][metrics[j]], reduceTimerPrecision, expectedPrecision),
+ "Testing " + metrics[j] + " with value " + entries[i][metrics[j]] +
+ " with resistFingerprinting " + resistFingerprinting + " reduceTimerPrecision " +
+ reduceTimerPrecision + " precision " + expectedPrecision);
+ }
+ }
+ }
+
+ async function startTests() {
+ await runTests(false, false, 2);
+ await runTests(true, false, 2);
+ await runTests(true, true, 2);
+ await runTests(false, true, 1000);
+ await runTests(false, true, 133);
+ await runTests(false, true, 13);
+ await runTests(false, true, 2);
+ await runTests(false, true, 1);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addLoadEvent(startTests);
+ </script>
+ </pre>
+ </body>
+</html>
diff --git a/dom/performance/tests/test_performance_observer.html b/dom/performance/tests/test_performance_observer.html
new file mode 100644
index 0000000000..6ed16400a9
--- /dev/null
+++ b/dom/performance/tests/test_performance_observer.html
@@ -0,0 +1,142 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset=utf-8>
+<title>Test for performance observer</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div id="log"></div>
+<script>
+SimpleTest.requestFlakyTimeout("For testing when observer callbacks should not be called.");
+SimpleTest.waitForExplicitFinish();
+
+let _tests = [];
+
+let test = promise_test = fn => {
+ let cleanups = [];
+ _tests.push(async () => {
+ try {
+ await fn({
+ add_cleanup: f => { cleanups.push(f); },
+ step_timeout(f, timeout) {
+ var test_this = this;
+ var args = Array.prototype.slice.call(arguments, 2);
+ return setTimeout(() => {
+ return f.apply(test_this, args);
+ }, timeout);
+ }
+ });
+ } catch(e) {
+ ok(false, `got unexpected exception ${e}`);
+ }
+ try {
+ for (const f of cleanups) {
+ f();
+ }
+ runNextTest();
+ } catch (e) {
+ ok(false, `got unexpected exception during cleanup ${e}`);
+ }
+ });
+}
+
+function runNextTest() {
+ if (_tests.length == 0) {
+ SimpleTest.finish()
+ return;
+ }
+ _tests.shift()();
+}
+
+function assert_equals(actual, expected, description) {
+ ok(typeof actual == typeof expected,
+ `${description} expected (${typeof expected}) ${expected} but got (${typeof actual}) ${actual}`);
+ ok(Object.is(actual, expected),
+ `${description} expected ${expected} but got ${actual}`);
+}
+
+function assert_array_equals(actual, expected, description) {
+ ok(actual.length === expected.length,
+ `${description} lengths differ, expected ${expected.length} but got ${actual.length}`);
+ for (var i = 0; i < actual.length; i++) {
+ ok(actual.hasOwnProperty(i) === expected.hasOwnProperty(i),
+ `${description} property expected to be ${expected[i]} but got ${actual[i]}`);
+ }
+}
+
+function assert_throws(expected_exc, func, desc) {
+ try {
+ func.call(this);
+ } catch(e) {
+ var actual = e.name || e.type;
+ var expected = expected_exc.name || expected_exc.type;
+ ok(actual == expected,
+ `Expected '${expected}', got '${actual}'.`);
+ return;
+ }
+ ok(false, "Expected exception, but none was thrown");
+}
+
+function assert_unreached(description) {
+ ok(false, `${description} reached unreachable code`);
+}
+</script>
+<script src="test_performance_observer.js"></script>
+<script>
+function makeXHR(aUrl) {
+ var xmlhttp = new XMLHttpRequest();
+ xmlhttp.open("get", aUrl, true);
+ xmlhttp.send();
+}
+
+let waitForConsole = new Promise(resolve => {
+ SimpleTest.monitorConsole(resolve, [{
+ message: /JavaScript Warning: "Ignoring unsupported entryTypes: invalid."/,
+ }]);
+});
+
+promise_test(t => {
+ var promise = new Promise(resolve => {
+ performance.clearResourceTimings();
+
+ var observer = new PerformanceObserver(list => resolve(list));
+ observer.observe({entryTypes: ['resource']});
+ t.add_cleanup(() => observer.disconnect());
+ });
+
+ makeXHR("test-data.json");
+
+ return promise.then(async list => {
+ assert_equals(list.getEntries().length, 1);
+ assert_array_equals(list.getEntries(),
+ performance.getEntriesByType("resource"),
+ "Observed 'resource' entries should equal to entries obtained by getEntriesByType.");
+
+ // getEntries filtering tests
+ assert_array_equals(list.getEntries({name: "http://mochi.test:8888/tests/dom/base/test/test-data.json"}),
+ performance.getEntriesByName("http://mochi.test:8888/tests/dom/base/test/test-data.json"),
+ "getEntries with name filter should return correct results.");
+ assert_array_equals(list.getEntries({entryType: "resource"}),
+ performance.getEntriesByType("resource"),
+ "getEntries with entryType filter should return correct results.");
+ assert_array_equals(list.getEntries({initiatorType: "xmlhttprequest"}),
+ performance.getEntriesByType("resource"),
+ "getEntries with initiatorType filter should return correct results.");
+ assert_array_equals(list.getEntries({initiatorType: "link"}),
+ [],
+ "getEntries with non-existent initiatorType filter should return an empty array.");
+
+ SimpleTest.endMonitorConsole();
+ await waitForConsole;
+ });
+}, "resource-timing test");
+
+runNextTest();
+</script>
+</body>
diff --git a/dom/performance/tests/test_performance_observer.js b/dom/performance/tests/test_performance_observer.js
new file mode 100644
index 0000000000..eab6180f3b
--- /dev/null
+++ b/dom/performance/tests/test_performance_observer.js
@@ -0,0 +1,286 @@
+test(t => {
+ assert_throws(
+ { name: "TypeError" },
+ function() {
+ new PerformanceObserver();
+ },
+ "PerformanceObserver constructor should throw TypeError if no argument is specified."
+ );
+
+ assert_throws(
+ { name: "TypeError" },
+ function() {
+ new PerformanceObserver({});
+ },
+ "PerformanceObserver constructor should throw TypeError if the argument is not a function."
+ );
+}, "Test that PerformanceObserver constructor throws exception");
+
+test(t => {
+ var observer = new PerformanceObserver(() => {});
+
+ assert_throws(
+ { name: "SyntaxError" },
+ function() {
+ observer.observe();
+ },
+ "observe() should throw TypeError exception if no option specified."
+ );
+
+ assert_throws(
+ { name: "SyntaxError" },
+ function() {
+ observer.observe({ unsupportedAttribute: "unsupported" });
+ },
+ "obsrve() should throw TypeError exception if the option has no 'entryTypes' attribute."
+ );
+
+ assert_equals(
+ undefined,
+ observer.observe({ entryTypes: [] }),
+ "observe() should silently ignore empty 'entryTypes' sequence."
+ );
+
+ assert_throws(
+ { name: "TypeError" },
+ function() {
+ observer.observe({ entryTypes: null });
+ },
+ "obsrve() should throw TypeError exception if 'entryTypes' attribute is null."
+ );
+
+ assert_equals(
+ undefined,
+ observer.observe({ entryTypes: ["invalid"] }),
+ "observe() should silently ignore invalid 'entryTypes' values."
+ );
+}, "Test that PerformanceObserver.observe throws exception");
+
+function promiseObserve(test, options) {
+ return new Promise(resolve => {
+ performance.clearMarks();
+ performance.clearMeasures();
+
+ var observer = new PerformanceObserver(list => resolve(list));
+ observer.observe(options);
+ test.add_cleanup(() => observer.disconnect());
+ });
+}
+
+promise_test(t => {
+ var promise = promiseObserve(t, { entryTypes: ["mark", "measure"] });
+
+ performance.mark("test-start");
+ performance.mark("test-end");
+ performance.measure("test-measure", "test-start", "test-end");
+
+ return promise.then(list => {
+ assert_equals(
+ list.getEntries().length,
+ 3,
+ "There should be three observed entries."
+ );
+
+ var markEntries = list.getEntries().filter(entry => {
+ return entry.entryType == "mark";
+ });
+ assert_array_equals(
+ markEntries,
+ performance.getEntriesByType("mark"),
+ "Observed 'mark' entries should equal to entries obtained by getEntriesByType."
+ );
+
+ var measureEntries = list.getEntries().filter(entry => {
+ return entry.entryType == "measure";
+ });
+ assert_array_equals(
+ measureEntries,
+ performance.getEntriesByType("measure"),
+ "Observed 'measure' entries should equal to entries obtained by getEntriesByType."
+ );
+ });
+}, "Test for user-timing with PerformanceObserver");
+
+promise_test(t => {
+ var promise = new Promise((resolve, reject) => {
+ performance.clearMarks();
+ performance.clearMeasures();
+
+ var observer = new PerformanceObserver(list => reject(list));
+ observer.observe({ entryTypes: ["mark", "measure"] });
+ observer.disconnect();
+ t.step_timeout(resolve, 100);
+ });
+
+ performance.mark("test-start");
+ performance.mark("test-end");
+ performance.measure("test-measure", "test-start", "test-end");
+
+ return promise.then(
+ () => {
+ assert_equals(performance.getEntriesByType("mark").length, 2);
+ assert_equals(performance.getEntriesByType("measure").length, 1);
+ },
+ list => {
+ assert_unreached("Observer callback should never be called.");
+ }
+ );
+}, "Nothing should be notified after disconnecting observer");
+
+promise_test(t => {
+ var promise = promiseObserve(t, { entryTypes: ["mark"] });
+
+ performance.mark("test");
+
+ return promise.then(list => {
+ assert_array_equals(
+ list.getEntries({ entryType: "mark" }),
+ performance.getEntriesByType("mark"),
+ "getEntries with entryType filter should return correct results."
+ );
+
+ assert_array_equals(
+ list.getEntries({ name: "test" }),
+ performance.getEntriesByName("test"),
+ "getEntries with name filter should return correct results."
+ );
+
+ assert_array_equals(
+ list.getEntries({ name: "test", entryType: "mark" }),
+ performance.getEntriesByName("test"),
+ "getEntries with name and entryType filter should return correct results."
+ );
+
+ assert_array_equals(
+ list.getEntries({ name: "invalid" }),
+ [],
+ "getEntries with non-existent name filter should return an empty array."
+ );
+
+ assert_array_equals(
+ list.getEntries({ name: "test", entryType: "measure" }),
+ [],
+ "getEntries with name filter and non-existent entryType should return an empty array."
+ );
+
+ assert_array_equals(
+ list.getEntries({ name: "invalid", entryType: "mark" }),
+ [],
+ "getEntries with non-existent name and entryType filter should return an empty array."
+ );
+
+ assert_array_equals(
+ list.getEntries({ initiatorType: "xmlhttprequest" }),
+ [],
+ "getEntries with initiatorType filter should return an empty array."
+ );
+ });
+}, "Test for PerformanceObserverEntryList.getEntries");
+
+promise_test(t => {
+ var promise = promiseObserve(t, { entryTypes: ["mark", "measure"] });
+
+ performance.mark("test");
+ performance.measure("test-measure", "test", "test");
+
+ return promise.then(list => {
+ assert_array_equals(
+ list.getEntriesByType("mark"),
+ performance.getEntriesByType("mark")
+ );
+ assert_array_equals(
+ list.getEntriesByType("measure"),
+ performance.getEntriesByType("measure")
+ );
+ });
+}, "Test for PerformanceObserverEntryList.getEntriesByType");
+
+promise_test(t => {
+ var promise = promiseObserve(t, { entryTypes: ["mark", "measure"] });
+
+ performance.mark("test");
+ performance.measure("test-measure", "test", "test");
+
+ return promise.then(list => {
+ assert_array_equals(
+ list.getEntriesByName("test"),
+ performance.getEntriesByName("test")
+ );
+ assert_array_equals(
+ list.getEntriesByName("test-measure"),
+ performance.getEntriesByName("test-measure")
+ );
+ });
+}, "Test for PerformanceObserverEntryList.getEntriesByName");
+
+promise_test(t => {
+ var promise = new Promise(resolve => {
+ performance.clearMarks();
+ performance.clearMeasures();
+
+ var observer = new PerformanceObserver(list => resolve(list));
+ observer.observe({ entryTypes: ["mark", "measure"] });
+ observer.observe({ entryTypes: ["mark", "measure"] });
+ t.add_cleanup(() => observer.disconnect());
+ });
+
+ performance.mark("test-start");
+ performance.mark("test-end");
+ performance.measure("test-measure", "test-start", "test-end");
+
+ return promise.then(list => {
+ assert_equals(
+ list.getEntries().length,
+ 3,
+ "Observed user timing entries should have only three entries."
+ );
+ });
+}, "Test that invoking observe method twice affects nothing");
+
+promise_test(t => {
+ var promise = new Promise(resolve => {
+ performance.clearMarks();
+ performance.clearMeasures();
+
+ var observer = new PerformanceObserver(list => resolve(list));
+ observer.observe({ entryTypes: ["mark", "measure"] });
+ observer.observe({ entryTypes: ["mark"] });
+ t.add_cleanup(() => observer.disconnect());
+ });
+
+ performance.mark("test-start");
+ performance.mark("test-end");
+ performance.measure("test-measure", "test-start", "test-end");
+
+ return promise.then(list => {
+ assert_equals(
+ list.getEntries().length,
+ 2,
+ "Observed user timing entries should have only two entries."
+ );
+ });
+}, "Test that observing filter is replaced by a new filter");
+
+promise_test(t => {
+ var promise = new Promise(resolve => {
+ performance.clearMarks();
+ performance.clearMeasures();
+
+ var observer = new PerformanceObserver(list => resolve(list));
+ observer.observe({ entryTypes: ["mark"] });
+ observer.observe({ entryTypes: ["measure"] });
+ t.add_cleanup(() => observer.disconnect());
+ });
+
+ performance.mark("test-start");
+ performance.mark("test-end");
+ performance.measure("test-measure", "test-start", "test-end");
+
+ return promise.then(list => {
+ assert_equals(
+ list.getEntries().length,
+ 1,
+ "Observed user timing entries should have only 1 entries."
+ );
+ });
+}, "Test that observing filter is replaced by a new filter");
diff --git a/dom/performance/tests/test_performance_paint_observer.html b/dom/performance/tests/test_performance_paint_observer.html
new file mode 100644
index 0000000000..2ded1db797
--- /dev/null
+++ b/dom/performance/tests/test_performance_paint_observer.html
@@ -0,0 +1,40 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+ <!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=1518999
+ -->
+ <head>
+ <title>Test for Bug 1518999 (Observer API) </title>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ </head>
+ <body>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1518999">Mozilla
+ Bug 1518999 - Paint Timing API For Observers</a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ <pre id="test">
+ <script class="testbody" type="text/javascript">
+ let tab;
+ function runTest() {
+ tab = window.open("test_performance_paint_observer_helper.html");
+ }
+
+ function done() {
+ tab.close();
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addLoadEvent(runTest);
+ </script>
+ </pre>
+ </div>
+ </body>
+</html>
diff --git a/dom/performance/tests/test_performance_paint_observer_helper.html b/dom/performance/tests/test_performance_paint_observer_helper.html
new file mode 100644
index 0000000000..ae27c9480d
--- /dev/null
+++ b/dom/performance/tests/test_performance_paint_observer_helper.html
@@ -0,0 +1,35 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE html>
+<html>
+ <body>
+ </body>
+ <script>
+ var promise = new Promise(resolve => {
+ var observer = new PerformanceObserver(list => resolve(list));
+ observer.observe({entryTypes: ["paint"]});
+ });
+
+ promise.then(list => {
+ var perfEntries = list.getEntries();
+ opener.is(list.getEntries().length, 1);
+ opener.isDeeply(list.getEntries(),
+ performance.getEntriesByType("paint"),
+ "Observed 'paint' entries should equal to entries obtained by getEntriesByType.");
+ opener.isDeeply(list.getEntries({name: "paint"}),
+ performance.getEntriesByName("paint"),
+ "getEntries with name filter should return correct results.");
+ opener.isDeeply(list.getEntries({entryType: "paint"}),
+ performance.getEntriesByType("paint"),
+ "getEntries with entryType filter should return correct results.");
+ opener.done();
+ });
+
+ const img = document.createElement("IMG");
+ img.src = "http://example.org/tests/dom/performance/tests/logo.png";
+ document.body.appendChild(img);
+
+ </script>
+</html>
diff --git a/dom/performance/tests/test_performance_paint_timing.html b/dom/performance/tests/test_performance_paint_timing.html
new file mode 100644
index 0000000000..f8784ecf26
--- /dev/null
+++ b/dom/performance/tests/test_performance_paint_timing.html
@@ -0,0 +1,38 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+ <!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=1518999
+ -->
+ <head>
+ <title>Test for Bug 1518999</title>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ </head>
+ <body>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1518999">Mozilla
+ Bug 1518999 - Paint Timing API</a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ <pre id="test">
+ <script class="testbody" type="text/javascript">
+ let tab;
+ function runTest() {
+ tab = window.open("test_performance_paint_timing_helper.html");
+ }
+ function done() {
+ tab.close();
+ SimpleTest.finish();
+ }
+ SimpleTest.waitForExplicitFinish();
+ addLoadEvent(runTest);
+ </script>
+ </pre>
+ </div>
+ </body>
+</html>
diff --git a/dom/performance/tests/test_performance_paint_timing_helper.html b/dom/performance/tests/test_performance_paint_timing_helper.html
new file mode 100644
index 0000000000..c05b38cac0
--- /dev/null
+++ b/dom/performance/tests/test_performance_paint_timing_helper.html
@@ -0,0 +1,65 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+ <!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=1518999
+ -->
+ <head>
+ <title>Test for Bug 1518999</title>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ </head>
+ <body>
+ <div id="main"></div>
+ <div id="image"></div>
+ <div id="test">
+ <script class="testbody" type="text/javascript">
+ async function runTest() {
+ const paintEntries = performance.getEntriesByType('paint');
+ opener.is(paintEntries.length, 0, "No paint entries yet");
+
+ const img = document.createElement("img");
+ img.src = "http://example.org/tests/dom/performance/tests/logo.png";
+
+ img.onload = function() {
+ function getAndTestEntries(runCount) {
+ function testEntries(entries) {
+ opener.is(entries.length, 1, "FCP Only returns");
+ opener.is(entries[0].entryType, "paint", "entryType is paint");
+ opener.is(entries[0].name, "first-contentful-paint",
+ "Returned entry should be first-contentful-paint" );
+ const fcpEntriesGotByName =
+ performance.getEntriesByName('first-contentful-paint');
+ opener.is(fcpEntriesGotByName.length, 1, "entries length should match");
+ opener.is(entries[0], fcpEntriesGotByName[0], "should be the same entry");
+ opener.done();
+ }
+ const entries = performance.getEntriesByType('paint');
+ if (entries.length < 1) {
+ if (runCount < 4) {
+ opener.SimpleTest.requestFlakyTimeout("FCP is being registered asynchronously, so wait a bit of time");
+ setTimeout(function() {
+ getAndTestEntries(runCount + 1);
+ }, 20);
+ } else {
+ opener.ok(false, "Unable to find paint entries within a reasonable amount of time");
+ opener.done();
+ }
+ } else {
+ testEntries(entries);
+ }
+ }
+ getAndTestEntries(1);
+ }
+ document.body.appendChild(img);
+ }
+ window.onload = function() {
+ runTest();
+ }
+ </script>
+ </div>
+ </div>
+ </body>
+</html>
diff --git a/dom/performance/tests/test_performance_server_timing.html b/dom/performance/tests/test_performance_server_timing.html
new file mode 100644
index 0000000000..cba11a5fdd
--- /dev/null
+++ b/dom/performance/tests/test_performance_server_timing.html
@@ -0,0 +1,58 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset=utf-8>
+<title>Test for PerformanceServerTiming</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+function makeXHR(aUrl) {
+ var xmlhttp = new XMLHttpRequest();
+ xmlhttp.open("get", aUrl, true);
+ xmlhttp.send();
+}
+
+// Note that |responseServerTiming| and |trailerServerTiming| SHOULD be synced with
+// the ones in serverTiming.sjs.
+var responseServerTiming = [{metric:"metric1", duration:"123.4", description:"description1"},
+ {metric:"metric2", duration:"456.78", description:"description2"}];
+var trailerServerTiming = [{metric:"metric3", duration:"789.11", description:"description3"},
+ {metric:"metric4", duration:"1112.13", description:"description4"}];
+
+function checkServerTimingContent(serverTiming) {
+ var expectedResult = responseServerTiming.concat(trailerServerTiming);
+ assert_equals(serverTiming.length, expectedResult.length);
+
+ for (var i = 0; i < expectedResult.length; i++) {
+ assert_equals(serverTiming[i].name, expectedResult[i].metric);
+ assert_equals(serverTiming[i].description, expectedResult[i].description);
+ assert_equals(serverTiming[i].duration, parseFloat(expectedResult[i].duration));
+ }
+}
+
+promise_test(t => {
+ var promise = new Promise(resolve => {
+ performance.clearResourceTimings();
+
+ var observer = new PerformanceObserver(list => resolve(list));
+ observer.observe({entryTypes: ['resource']});
+ t.add_cleanup(() => observer.disconnect());
+ });
+
+ makeXHR("serverTiming.sjs");
+
+ return promise.then(list => {
+ assert_equals(list.getEntries().length, 1);
+ checkServerTimingContent(list.getEntries()[0].serverTiming);
+ });
+}, "server-timing test");
+
+</script>
+</body>
diff --git a/dom/performance/tests/test_performance_server_timing_plain_http.html b/dom/performance/tests/test_performance_server_timing_plain_http.html
new file mode 100644
index 0000000000..7dcb8bd38d
--- /dev/null
+++ b/dom/performance/tests/test_performance_server_timing_plain_http.html
@@ -0,0 +1,42 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset=utf-8>
+<title>Plain HTTP Test for PerformanceServerTiming</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+function makeXHR(aUrl) {
+ var xmlhttp = new XMLHttpRequest();
+ xmlhttp.open("get", aUrl, true);
+ xmlhttp.send();
+}
+
+promise_test(t => {
+ var promise = new Promise(resolve => {
+ performance.clearResourceTimings();
+
+ var observer = new PerformanceObserver(list => resolve(list));
+ observer.observe({entryTypes: ['resource']});
+ t.add_cleanup(() => observer.disconnect());
+ });
+
+ makeXHR("serverTiming.sjs");
+
+ return promise.then(list => {
+ assert_equals(list.getEntries().length, 1);
+ assert_equals(list.getEntries()[0].serverTiming, undefined);
+ assert_equals(list.getEntries()[0].toJSON().serverTiming, undefined,
+ "toJSON should not pick up properties that aren't on the object");
+ });
+}, "server-timing test");
+
+</script>
+</body>
diff --git a/dom/performance/tests/test_performance_timing_json.html b/dom/performance/tests/test_performance_timing_json.html
new file mode 100644
index 0000000000..97079c0d2f
--- /dev/null
+++ b/dom/performance/tests/test_performance_timing_json.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1375829
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1375829</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1375829 **/
+ var json = performance.timing.toJSON();
+
+ // Ensure it doesn't have any attributes that performance.timing doesn't have
+ for (let key of Object.keys(json)) {
+ ok(key in performance.timing, key + " should be a property of performance.timing");
+ }
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1375829">Mozilla Bug 1375829</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/performance/tests/test_performance_user_timing.html b/dom/performance/tests/test_performance_user_timing.html
new file mode 100644
index 0000000000..fa0aaceb4e
--- /dev/null
+++ b/dom/performance/tests/test_performance_user_timing.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+ <!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=782751
+ -->
+ <head>
+ <title>Test for Bug 782751</title>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="test_performance_user_timing.js"></script>
+ </head>
+ <body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=782751">Mozilla Bug 782751 - User Timing API</a>
+ <div id="content">
+ </div>
+ <pre id="test">
+ <script class="testbody" type="text/javascript">
+ var index = 0;
+
+ function next() {
+ ok(true, "Begin!");
+ var arr;
+ for (var i = 0; i < steps.length; ++i) {
+ try {
+ performance.clearMarks();
+ performance.clearMeasures();
+ performance.clearResourceTimings();
+ is(performance.getEntriesByType("resource").length, 0, "clearing performance resource entries");
+ is(performance.getEntriesByType("mark").length, 0, "clearing performance mark entries");
+ is(performance.getEntriesByType("measure").length, 0, "clearing performance measure entries");
+ steps[i]();
+ } catch(ex) {
+ ok(false, "Caught exception", ex);
+ }
+ }
+
+ SpecialPowers.setBoolPref("privacy.reduceTimerPrecision", reduceTimePrecisionPrevPrefValue);
+ SimpleTest.finish();
+ }
+
+ var reduceTimePrecisionPrevPrefValue = SpecialPowers.getBoolPref("privacy.reduceTimerPrecision");
+ SpecialPowers.setBoolPref("privacy.reduceTimerPrecision", false);
+
+ SimpleTest.waitForExplicitFinish();
+ addLoadEvent(next);
+ </script>
+ </pre>
+ </body>
+</html>
diff --git a/dom/performance/tests/test_performance_user_timing.js b/dom/performance/tests/test_performance_user_timing.js
new file mode 100644
index 0000000000..fe91945953
--- /dev/null
+++ b/dom/performance/tests/test_performance_user_timing.js
@@ -0,0 +1,318 @@
+var steps = [
+ // Test single mark addition
+ function() {
+ ok(true, "Running mark addition test");
+ performance.mark("test");
+ var marks = performance.getEntriesByType("mark");
+ is(marks.length, 1, "Number of marks should be 1");
+ var mark = marks[0];
+ is(mark.name, "test", "mark name should be 'test'");
+ is(mark.entryType, "mark", "mark type should be 'mark'");
+ isnot(mark.startTime, 0, "mark start time should not be 0");
+ is(mark.duration, 0, "mark duration should be 0");
+ },
+ // Test multiple mark addition
+ function() {
+ ok(true, "Running multiple mark with same name addition test");
+ performance.mark("test");
+ performance.mark("test");
+ performance.mark("test");
+ var marks_type = performance.getEntriesByType("mark");
+ is(marks_type.length, 3, "Number of marks by type should be 3");
+ var marks_name = performance.getEntriesByName("test");
+ is(marks_name.length, 3, "Number of marks by name should be 3");
+ var mark = marks_name[0];
+ is(mark.name, "test", "mark name should be 'test'");
+ is(mark.entryType, "mark", "mark type should be 'mark'");
+ isnot(mark.startTime, 0, "mark start time should not be 0");
+ is(mark.duration, 0, "mark duration should be 0");
+ var times = [];
+ // This also tests the chronological ordering specified as
+ // required for getEntries in the performance timeline spec.
+ marks_name.forEach(function(s) {
+ times.forEach(function(time) {
+ ok(
+ s.startTime >= time.startTime,
+ "Times should be equal or increasing between similarly named marks: " +
+ s.startTime +
+ " >= " +
+ time.startTime
+ );
+ });
+ times.push(s);
+ });
+ },
+ // Test all marks removal
+ function() {
+ ok(true, "Running all mark removal test");
+ performance.mark("test");
+ performance.mark("test2");
+ var marks = performance.getEntriesByType("mark");
+ is(marks.length, 2, "number of marks before all removal");
+ performance.clearMarks();
+ marks = performance.getEntriesByType("mark");
+ is(marks.length, 0, "number of marks after all removal");
+ },
+ // Test single mark removal
+ function() {
+ ok(true, "Running removal test (0 'test' marks with other marks)");
+ performance.mark("test2");
+ var marks = performance.getEntriesByType("mark");
+ is(marks.length, 1, "number of marks before all removal");
+ performance.clearMarks("test");
+ marks = performance.getEntriesByType("mark");
+ is(marks.length, 1, "number of marks after all removal");
+ },
+ // Test single mark removal
+ function() {
+ ok(true, "Running removal test (0 'test' marks with no other marks)");
+ var marks = performance.getEntriesByType("mark");
+ is(marks.length, 0, "number of marks before all removal");
+ performance.clearMarks("test");
+ marks = performance.getEntriesByType("mark");
+ is(marks.length, 0, "number of marks after all removal");
+ },
+ function() {
+ ok(true, "Running removal test (1 'test' mark with other marks)");
+ performance.mark("test");
+ performance.mark("test2");
+ var marks = performance.getEntriesByType("mark");
+ is(marks.length, 2, "number of marks before all removal");
+ performance.clearMarks("test");
+ marks = performance.getEntriesByType("mark");
+ is(marks.length, 1, "number of marks after all removal");
+ },
+ function() {
+ ok(true, "Running removal test (1 'test' mark with no other marks)");
+ performance.mark("test");
+ var marks = performance.getEntriesByType("mark");
+ is(marks.length, 1, "number of marks before all removal");
+ performance.clearMarks("test");
+ marks = performance.getEntriesByType("mark");
+ is(marks.length, 0, "number of marks after all removal");
+ },
+ function() {
+ ok(true, "Running removal test (2 'test' marks with other marks)");
+ performance.mark("test");
+ performance.mark("test");
+ performance.mark("test2");
+ var marks = performance.getEntriesByType("mark");
+ is(marks.length, 3, "number of marks before all removal");
+ performance.clearMarks("test");
+ marks = performance.getEntriesByType("mark");
+ is(marks.length, 1, "number of marks after all removal");
+ },
+ function() {
+ ok(true, "Running removal test (2 'test' marks with no other marks)");
+ performance.mark("test");
+ performance.mark("test");
+ var marks = performance.getEntriesByType("mark");
+ is(marks.length, 2, "number of marks before all removal");
+ performance.clearMarks("test");
+ marks = performance.getEntriesByType("mark");
+ is(marks.length, 0, "number of marks after all removal");
+ },
+ // Test mark name being same as navigation timing parameter
+ function() {
+ ok(true, "Running mark name collision test");
+ for (n in performance.timing) {
+ try {
+ if (n == "toJSON") {
+ ok(true, "Skipping toJSON entry in collision test");
+ continue;
+ }
+ performance.mark(n);
+ ok(
+ false,
+ "Mark name collision test failed for name " +
+ n +
+ ", shouldn't make it here!"
+ );
+ } catch (e) {
+ ok(
+ e instanceof DOMException,
+ "DOM exception thrown for mark named " + n
+ );
+ is(
+ e.code,
+ e.SYNTAX_ERR,
+ "DOM exception for name collision is syntax error"
+ );
+ }
+ }
+ },
+ // Test measure
+ function() {
+ ok(true, "Running measure addition with no start/end time test");
+ performance.measure("test");
+ var measures = performance.getEntriesByType("measure");
+ is(measures.length, 1, "number of measures should be 1");
+ var measure = measures[0];
+ is(measure.name, "test", "measure name should be 'test'");
+ is(measure.entryType, "measure", "measure type should be 'measure'");
+ is(measure.startTime, 0, "measure start time should be zero");
+ ok(measure.duration >= 0, "measure duration should not be negative");
+ },
+ function() {
+ ok(true, "Running measure addition with only start time test");
+ performance.mark("test1");
+ performance.measure("test", "test1", undefined);
+ var measures = performance.getEntriesByName("test", "measure");
+ var marks = performance.getEntriesByName("test1", "mark");
+ var measure = measures[0];
+ var mark = marks[0];
+ is(
+ measure.startTime,
+ mark.startTime,
+ "measure start time should be equal to the mark startTime"
+ );
+ ok(measure.duration >= 0, "measure duration should not be negative");
+ },
+ function() {
+ ok(true, "Running measure addition with only end time test");
+ performance.mark("test1");
+ performance.measure("test", undefined, "test1");
+ var measures = performance.getEntriesByName("test", "measure");
+ var marks = performance.getEntriesByName("test1", "mark");
+ var measure = measures[0];
+ var mark = marks[0];
+ ok(measure.duration >= 0, "measure duration should not be negative");
+ },
+ // Test measure picking latest version of similarly named tags
+ function() {
+ ok(true, "Running multiple mark with same name addition test");
+ performance.mark("test");
+ performance.mark("test");
+ performance.mark("test");
+ performance.mark("test2");
+ var marks_name = performance.getEntriesByName("test");
+ is(marks_name.length, 3, "Number of marks by name should be 3");
+ var marks_name2 = performance.getEntriesByName("test2");
+ is(marks_name2.length, 1, "Number of marks by name should be 1");
+ var test_mark = marks_name2[0];
+ performance.measure("test", "test", "test2");
+ var measures_type = performance.getEntriesByType("measure");
+ var last_mark = marks_name[marks_name.length - 1];
+ is(measures_type.length, 1, "Number of measures by type should be 1");
+ var measure = measures_type[0];
+ is(
+ measure.startTime,
+ last_mark.startTime,
+ "Measure start time should be the start time of the latest 'test' mark"
+ );
+ // Tolerance testing to avoid oranges, since we're doing double math across two different languages.
+ ok(
+ measure.duration - (test_mark.startTime - last_mark.startTime) < 0.00001,
+ "Measure duration ( " +
+ measure.duration +
+ ") should be difference between two marks"
+ );
+ },
+ function() {
+ // We don't have navigationStart in workers.
+ if ("window" in self) {
+ ok(true, "Running measure addition with no start/end time test");
+ performance.measure("test", "navigationStart");
+ var measures = performance.getEntriesByType("measure");
+ is(measures.length, 1, "number of measures should be 1");
+ var measure = measures[0];
+ is(measure.name, "test", "measure name should be 'test'");
+ is(measure.entryType, "measure", "measure type should be 'measure'");
+ is(measure.startTime, 0, "measure start time should be zero");
+ ok(measure.duration >= 0, "measure duration should not be negative");
+ }
+ },
+ // Test all measure removal
+ function() {
+ ok(true, "Running all measure removal test");
+ performance.measure("test");
+ performance.measure("test2");
+ var measures = performance.getEntriesByType("measure");
+ is(measures.length, 2, "measure entries should be length 2");
+ performance.clearMeasures();
+ measures = performance.getEntriesByType("measure");
+ is(measures.length, 0, "measure entries should be length 0");
+ },
+ // Test single measure removal
+ function() {
+ ok(true, "Running all measure removal test");
+ performance.measure("test");
+ performance.measure("test2");
+ var measures = performance.getEntriesByType("measure");
+ is(measures.length, 2, "measure entries should be length 2");
+ performance.clearMeasures("test");
+ measures = performance.getEntriesByType("measure");
+ is(measures.length, 1, "measure entries should be length 1");
+ },
+ // Test measure with invalid start time mark name
+ function() {
+ ok(true, "Running measure invalid start test");
+ try {
+ performance.measure("test", "notamark");
+ ok(false, "invalid measure start time exception not thrown!");
+ } catch (e) {
+ ok(e instanceof DOMException, "DOM exception thrown for invalid measure");
+ is(
+ e.code,
+ e.SYNTAX_ERR,
+ "DOM exception for invalid time is syntax error"
+ );
+ }
+ },
+ // Test measure with invalid end time mark name
+ function() {
+ ok(true, "Running measure invalid end test");
+ try {
+ performance.measure("test", undefined, "notamark");
+ ok(false, "invalid measure end time exception not thrown!");
+ } catch (e) {
+ ok(e instanceof DOMException, "DOM exception thrown for invalid measure");
+ is(
+ e.code,
+ e.SYNTAX_ERR,
+ "DOM exception for invalid time is syntax error"
+ );
+ }
+ },
+ // Test measure name being same as navigation timing parameter
+ function() {
+ ok(true, "Running measure name collision test");
+ for (n in performance.timing) {
+ if (n == "toJSON") {
+ ok(true, "Skipping toJSON entry in collision test");
+ continue;
+ }
+ performance.measure(n);
+ ok(true, "Measure name supports name collisions: " + n);
+ }
+ },
+ // Test measure mark being a reserved name
+ function() {
+ ok(true, "Create measures using all reserved names");
+ for (n in performance.timing) {
+ try {
+ if (n == "toJSON") {
+ ok(true, "Skipping toJSON entry in collision test");
+ continue;
+ }
+ performance.measure("test", n);
+ ok(true, "Measure created from reserved name as starting time: " + n);
+ } catch (e) {
+ ok(
+ [
+ "redirectStart",
+ "redirectEnd",
+ "unloadEventStart",
+ "unloadEventEnd",
+ "loadEventEnd",
+ "secureConnectionStart",
+ ].includes(n),
+ "Measure created from reserved name as starting time: " +
+ n +
+ " and threw expected error"
+ );
+ }
+ }
+ },
+ // TODO: Test measure picking latest version of similarly named tags
+];
diff --git a/dom/performance/tests/test_sharedWorker_performance_user_timing.html b/dom/performance/tests/test_sharedWorker_performance_user_timing.html
new file mode 100644
index 0000000000..d26594e292
--- /dev/null
+++ b/dom/performance/tests/test_sharedWorker_performance_user_timing.html
@@ -0,0 +1,30 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Test for worker performance timing API</title>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ </head>
+ <body>
+ <script class="testbody" type="text/javascript">
+
+var sw = new SharedWorker('sharedworker_performance_user_timing.js');
+sw.port.onmessage = function(event) {
+ if (event.data.type == 'finish') {
+ SpecialPowers.setBoolPref("privacy.reduceTimerPrecision", reduceTimePrecisionPrevPrefValue);
+ SimpleTest.finish();
+ } else if (event.data.type == 'status') {
+ ok(event.data.status, event.data.msg);
+ }
+}
+
+var reduceTimePrecisionPrevPrefValue = SpecialPowers.getBoolPref("privacy.reduceTimerPrecision");
+SpecialPowers.setBoolPref("privacy.reduceTimerPrecision", false);
+SimpleTest.waitForExplicitFinish();
+ </script>
+ </body>
+</html>
diff --git a/dom/performance/tests/test_timeOrigin.html b/dom/performance/tests/test_timeOrigin.html
new file mode 100644
index 0000000000..69796a432d
--- /dev/null
+++ b/dom/performance/tests/test_timeOrigin.html
@@ -0,0 +1,76 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Test for performance.timeOrigin</title>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ </head>
+ <body>
+ <script type="text/js-worker" id="worker-src">
+ postMessage({ now: performance.now(), timeOrigin: performance.timeOrigin });
+ </script>
+
+ <script type="text/js-worker" id="shared-worker-src">
+ onconnect = function(evt) {
+ evt.ports[0].postMessage({ now: performance.now(), timeOrigin: performance.timeOrigin });
+ };
+ </script>
+
+ <script class="testbody" type="text/javascript">
+
+function testBasic() {
+ ok("timeOrigin" in performance, "Performance.timeOrigin exists.");
+ ok(performance.timeOrigin > 0, "TimeOrigin must be greater than 0.");
+ next();
+}
+
+function testWorker() {
+ var now = performance.now();
+
+ var blob = new Blob([ document.getElementById("worker-src").textContent ],
+ { type: "text/javascript" });
+ var w = new Worker(URL.createObjectURL(blob));
+ w.onmessage = function(e) {
+ ok (e.data.now + e.data.timeOrigin > now + performance.timeOrigin, "Comparing worker.now and window.now");
+ next();
+ }
+}
+
+function testSharedWorker() {
+ var now = performance.now();
+
+ var blob = new Blob([ document.getElementById("shared-worker-src").textContent ],
+ { type: "text/javascript" });
+ var w = new SharedWorker(URL.createObjectURL(blob));
+ w.port.onmessage = function(e) {
+ ok (e.data.now + e.data.timeOrigin > now + performance.timeOrigin, "Comparing worker.now and window.now");
+ next();
+ }
+}
+
+var tests = [ testBasic, testWorker, testSharedWorker ];
+function next() {
+ if (!tests.length) {
+ SpecialPowers.setBoolPref("privacy.reduceTimerPrecision", reduceTimePrecisionPrevPrefValue);
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+// It is a known issue that comparing time between a worker and a window
+// when timer clamping is in effect may cause time to go backwards.
+// Do not run this test with this preference set. For large values of
+// clamping you will see failures. For small values, it is intermitant.
+var reduceTimePrecisionPrevPrefValue = SpecialPowers.getBoolPref("privacy.reduceTimerPrecision");
+SpecialPowers.setBoolPref("privacy.reduceTimerPrecision", false);
+
+addLoadEvent(next);
+ </script>
+ </pre>
+ </body>
+</html>
diff --git a/dom/performance/tests/test_worker_observer.html b/dom/performance/tests/test_worker_observer.html
new file mode 100644
index 0000000000..7f4df855c9
--- /dev/null
+++ b/dom/performance/tests/test_worker_observer.html
@@ -0,0 +1,41 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset=utf-8>
+<title>Test for performance observer in worker</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+const worker = new Worker("worker_performance_observer.js");
+
+promise_test(t => {
+ let found = false;
+ return new Promise(resolve => {
+ SpecialPowers.registerConsoleListener(msg => {
+ if (msg.errorMessage === "Ignoring unsupported entryTypes: invalid.") {
+ found = true;
+ resolve();
+ }
+ });
+ worker.addEventListener("error", resolve);
+ worker.addEventListener("message", function(event) {
+ if (event.data.type === "complete") {
+ resolve();
+ }
+ });
+ }).then(() => {
+ SpecialPowers.postConsoleSentinel();
+ assert_true(found, "got the expected console warning");
+ });
+}, "Console warnings about invalid types should be logged during the tests");
+
+fetch_tests_from_worker(worker);
+</script>
+</body>
diff --git a/dom/performance/tests/test_worker_performance_entries.html b/dom/performance/tests/test_worker_performance_entries.html
new file mode 100644
index 0000000000..d3f124fdb3
--- /dev/null
+++ b/dom/performance/tests/test_worker_performance_entries.html
@@ -0,0 +1,39 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>PerformanceResouceTiming in workers</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+// The worker assumes it will take some amount of time to load a resource.
+// With a low enough precision, the duration to load a resource may clamp
+// down to zero.
+var reduceTimePrecisionPrevPrefValue = SpecialPowers.getBoolPref("privacy.reduceTimerPrecision");
+SpecialPowers.setBoolPref("privacy.reduceTimerPrecision", false);
+
+var worker = new Worker('test_worker_performance_entries.js');
+worker.onmessage = function(event) {
+ if (event.data.type == "check") {
+ ok(event.data.status, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "finish") {
+ SpecialPowers.setBoolPref("privacy.reduceTimerPrecision", reduceTimePrecisionPrevPrefValue);
+ SimpleTest.finish();
+ return;
+ }
+
+ ok(false, "?!?");
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/performance/tests/test_worker_performance_entries.js b/dom/performance/tests/test_worker_performance_entries.js
new file mode 100644
index 0000000000..1eea24efd9
--- /dev/null
+++ b/dom/performance/tests/test_worker_performance_entries.js
@@ -0,0 +1,107 @@
+function ok(a, msg) {
+ postMessage({ type: "check", status: !!a, msg });
+}
+
+function is(a, b, msg) {
+ ok(a === b, msg);
+}
+
+function finish(a, msg) {
+ postMessage({ type: "finish" });
+}
+
+function check(resource, initiatorType, protocol) {
+ let entries = performance.getEntries();
+ ok(entries.length == 1, "We have an entry");
+
+ ok(entries[0] instanceof PerformanceEntry, "The entry is a PerformanceEntry");
+ ok(entries[0].name.endsWith(resource), "The entry has been found!");
+
+ is(entries[0].entryType, "resource", "Correct EntryType");
+ ok(entries[0].startTime > 0, "We have a startTime");
+ ok(entries[0].duration > 0, "We have a duration");
+
+ ok(
+ entries[0] instanceof PerformanceResourceTiming,
+ "The entry is a PerformanceResourceTiming"
+ );
+
+ is(entries[0].initiatorType, initiatorType, "Correct initiatorType");
+ is(entries[0].nextHopProtocol, protocol, "Correct protocol");
+
+ performance.clearResourceTimings();
+}
+
+function simple_checks() {
+ ok("performance" in self, "We have self.performance");
+ performance.clearResourceTimings();
+ next();
+}
+
+function fetch_request() {
+ fetch("test_worker_performance_entries.sjs")
+ .then(r => r.blob())
+ .then(blob => {
+ check("test_worker_performance_entries.sjs", "fetch", "http/1.1");
+ })
+ .then(next);
+}
+
+function xhr_request() {
+ let xhr = new XMLHttpRequest();
+ xhr.open("GET", "test_worker_performance_entries.sjs");
+ xhr.send();
+ xhr.onload = () => {
+ check("test_worker_performance_entries.sjs", "xmlhttprequest", "http/1.1");
+ next();
+ };
+}
+
+function sync_xhr_request() {
+ let xhr = new XMLHttpRequest();
+ xhr.open("GET", "test_worker_performance_entries.sjs", false);
+ xhr.send();
+ check("test_worker_performance_entries.sjs", "xmlhttprequest", "http/1.1");
+ next();
+}
+
+function import_script() {
+ importScripts(["empty.js"]);
+ check("empty.js", "other", "http/1.1");
+ next();
+}
+
+function redirect() {
+ fetch("test_worker_performance_entries.sjs?redirect")
+ .then(r => r.text())
+ .then(text => {
+ is(text, "Hello world \\o/", "The redirect worked correctly");
+ check(
+ "test_worker_performance_entries.sjs?redirect",
+ "fetch",
+ "http/1.1"
+ );
+ })
+ .then(next);
+}
+
+let tests = [
+ simple_checks,
+ fetch_request,
+ xhr_request,
+ sync_xhr_request,
+ import_script,
+ redirect,
+];
+
+function next() {
+ if (!tests.length) {
+ finish();
+ return;
+ }
+
+ let test = tests.shift();
+ test();
+}
+
+next();
diff --git a/dom/performance/tests/test_worker_performance_entries.sjs b/dom/performance/tests/test_worker_performance_entries.sjs
new file mode 100644
index 0000000000..2d4f6fd866
--- /dev/null
+++ b/dom/performance/tests/test_worker_performance_entries.sjs
@@ -0,0 +1,12 @@
+function handleRequest(request, response)
+{
+ response.setHeader("Content-Type", "text/html");
+
+ if (request.queryString == "redirect") {
+ response.setStatusLine(request.httpVersion, 302, "See Other");
+ response.setHeader("Location", "test_worker_performance_entries.sjs?ok");
+ } else {
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.write("Hello world \\o/");
+ }
+}
diff --git a/dom/performance/tests/test_worker_performance_now.html b/dom/performance/tests/test_worker_performance_now.html
new file mode 100644
index 0000000000..be4f8f56ea
--- /dev/null
+++ b/dom/performance/tests/test_worker_performance_now.html
@@ -0,0 +1,31 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Validate Interfaces Exposed to Workers</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var reduceTimePrecisionPrevPrefValue = SpecialPowers.getBoolPref("privacy.reduceTimerPrecision");
+SpecialPowers.setBoolPref("privacy.reduceTimerPrecision", false);
+
+var worker = new Worker('test_worker_performance_now.js');
+worker.onmessage = function(event) {
+ if (event.data.type == 'finish') {
+ SpecialPowers.setBoolPref("privacy.reduceTimerPrecision", reduceTimePrecisionPrevPrefValue);
+ SimpleTest.finish();
+
+ } else if (event.data.type == 'status') {
+ ok(event.data.status, event.data.msg);
+ }
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/performance/tests/test_worker_performance_now.js b/dom/performance/tests/test_worker_performance_now.js
new file mode 100644
index 0000000000..a22f66256e
--- /dev/null
+++ b/dom/performance/tests/test_worker_performance_now.js
@@ -0,0 +1,68 @@
+function ok(a, msg) {
+ dump("OK: " + !!a + " => " + a + ": " + msg + "\n");
+ postMessage({ type: "status", status: !!a, msg: a + ": " + msg });
+}
+
+function workerTestDone() {
+ postMessage({ type: "finish" });
+}
+
+ok(self.performance, "Performance object should exist.");
+ok(
+ typeof self.performance.now == "function",
+ "Performance object should have a 'now' method."
+);
+var n = self.performance.now(),
+ d = Date.now();
+ok(n >= 0, "The value of now() should be equal to or greater than 0.");
+ok(
+ self.performance.now() >= n,
+ "The value of now() should monotonically increase."
+);
+
+// Spin on setTimeout() until performance.now() increases. Due to recent
+// security developments, the hr-time working group has not yet reached
+// consensus on what the recommend minimum clock resolution should be:
+// https://w3c.github.io/hr-time/#clock-resolution
+// Since setTimeout might return too early/late, our goal is for
+// performance.now() to increase before a 2 ms deadline rather than specific
+// number of setTimeout(N) invocations.
+// See bug 749894 (intermittent failures of this test)
+setTimeout(checkAfterTimeout, 1);
+
+var checks = 0;
+
+function checkAfterTimeout() {
+ checks++;
+ var d2 = Date.now();
+ var n2 = self.performance.now();
+
+ // Spin on setTimeout() until performance.now() increases. Abort the test
+ // if it runs for more than 2 ms or 50 timeouts.
+ let elapsedTime = d2 - d;
+ let elapsedPerf = n2 - n;
+ if (elapsedPerf == 0 && elapsedTime < 2 && checks < 50) {
+ setTimeout(checkAfterTimeout, 1);
+ return;
+ }
+
+ // Our implementation provides 1 ms resolution (bug 1451790), but we
+ // can't assert that elapsedPerf >= 1 ms because this worker test runs with
+ // "privacy.reduceTimerPrecision" == false so performance.now() is not
+ // limited to 1 ms resolution.
+ ok(
+ elapsedPerf > 0,
+ `Loose - the value of now() should increase after 2ms. ` +
+ `delta now(): ${elapsedPerf} ms`
+ );
+
+ // If we need more than 1 iteration, then either performance.now() resolution
+ // is shorter than 1 ms or setTimeout() is returning too early.
+ ok(
+ checks == 1,
+ `Strict - the value of now() should increase after one setTimeout. ` +
+ `iters: ${checks}, dt: ${elapsedTime}, now(): ${n2}`
+ );
+
+ workerTestDone();
+}
diff --git a/dom/performance/tests/test_worker_user_timing.html b/dom/performance/tests/test_worker_user_timing.html
new file mode 100644
index 0000000000..ebeac24e4f
--- /dev/null
+++ b/dom/performance/tests/test_worker_user_timing.html
@@ -0,0 +1,30 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Test for worker performance timing API</title>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ </head>
+ <body>
+ <script class="testbody" type="text/javascript">
+
+var worker = new Worker('worker_performance_user_timing.js');
+worker.onmessage = function(event) {
+ if (event.data.type == 'finish') {
+ SpecialPowers.setBoolPref("privacy.reduceTimerPrecision", reduceTimePrecisionPrevPrefValue);
+ SimpleTest.finish();
+ } else if (event.data.type == 'status') {
+ ok(event.data.status, event.data.msg);
+ }
+}
+
+var reduceTimePrecisionPrevPrefValue = SpecialPowers.getBoolPref("privacy.reduceTimerPrecision");
+SpecialPowers.setBoolPref("privacy.reduceTimerPrecision", false);
+SimpleTest.waitForExplicitFinish();
+ </script>
+ </body>
+</html>
diff --git a/dom/performance/tests/worker_performance_observer.js b/dom/performance/tests/worker_performance_observer.js
new file mode 100644
index 0000000000..3282c9d157
--- /dev/null
+++ b/dom/performance/tests/worker_performance_observer.js
@@ -0,0 +1,4 @@
+importScripts(["/resources/testharness.js"]);
+importScripts(["test_performance_observer.js"]);
+
+done();
diff --git a/dom/performance/tests/worker_performance_user_timing.js b/dom/performance/tests/worker_performance_user_timing.js
new file mode 100644
index 0000000000..257040f09f
--- /dev/null
+++ b/dom/performance/tests/worker_performance_user_timing.js
@@ -0,0 +1,32 @@
+function ok(a, msg) {
+ dump("OK: " + !!a + " => " + a + " " + msg + "\n");
+ postMessage({ type: "status", status: !!a, msg: a + ": " + msg });
+}
+
+function is(a, b, msg) {
+ dump("IS: " + (a === b) + " => " + a + " | " + b + " " + msg + "\n");
+ postMessage({
+ type: "status",
+ status: a === b,
+ msg: a + " === " + b + ": " + msg,
+ });
+}
+
+function isnot(a, b, msg) {
+ dump("ISNOT: " + (a === b) + " => " + a + " | " + b + " " + msg + "\n");
+ postMessage({
+ type: "status",
+ status: a != b,
+ msg: a + " != " + b + ": " + msg,
+ });
+}
+
+importScripts(["test_performance_user_timing.js"]);
+
+for (var i = 0; i < steps.length; ++i) {
+ performance.clearMarks();
+ performance.clearMeasures();
+ steps[i]();
+}
+
+postMessage({ type: "finish" });