summaryrefslogtreecommitdiff
path: root/dom/performance
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /dom/performance
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloaduxp-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/performance')
-rw-r--r--dom/performance/Performance.cpp536
-rw-r--r--dom/performance/Performance.h162
-rw-r--r--dom/performance/PerformanceEntry.cpp43
-rw-r--r--dom/performance/PerformanceEntry.h96
-rw-r--r--dom/performance/PerformanceMainThread.cpp336
-rw-r--r--dom/performance/PerformanceMainThread.h87
-rw-r--r--dom/performance/PerformanceMark.cpp31
-rw-r--r--dom/performance/PerformanceMark.h38
-rw-r--r--dom/performance/PerformanceMeasure.cpp33
-rw-r--r--dom/performance/PerformanceMeasure.h45
-rw-r--r--dom/performance/PerformanceNavigation.cpp43
-rw-r--r--dom/performance/PerformanceNavigation.h61
-rw-r--r--dom/performance/PerformanceObserver.cpp184
-rw-r--r--dom/performance/PerformanceObserver.h79
-rw-r--r--dom/performance/PerformanceObserverEntryList.cpp96
-rw-r--r--dom/performance/PerformanceObserverEntryList.h65
-rw-r--r--dom/performance/PerformanceResourceTiming.cpp53
-rw-r--r--dom/performance/PerformanceResourceTiming.h185
-rw-r--r--dom/performance/PerformanceTiming.cpp361
-rw-r--r--dom/performance/PerformanceTiming.h278
-rw-r--r--dom/performance/PerformanceWorker.cpp85
-rw-r--r--dom/performance/PerformanceWorker.h100
-rw-r--r--dom/performance/moz.build39
-rw-r--r--dom/performance/tests/mochitest.ini17
-rw-r--r--dom/performance/tests/performance_observer.html74
-rw-r--r--dom/performance/tests/sharedworker_performance_user_timing.js30
-rw-r--r--dom/performance/tests/test_performance_observer.html17
-rw-r--r--dom/performance/tests/test_performance_observer.js226
-rw-r--r--dom/performance/tests/test_performance_user_timing.html45
-rw-r--r--dom/performance/tests/test_performance_user_timing.js272
-rw-r--r--dom/performance/tests/test_sharedWorker_performance_user_timing.html27
-rw-r--r--dom/performance/tests/test_worker_observer.html17
-rw-r--r--dom/performance/tests/test_worker_performance_now.html32
-rw-r--r--dom/performance/tests/test_worker_performance_now.js76
-rw-r--r--dom/performance/tests/test_worker_user_timing.html27
-rw-r--r--dom/performance/tests/worker_performance_observer.html32
-rw-r--r--dom/performance/tests/worker_performance_observer.js4
-rw-r--r--dom/performance/tests/worker_performance_user_timing.js24
38 files changed, 3956 insertions, 0 deletions
diff --git a/dom/performance/Performance.cpp b/dom/performance/Performance.cpp
new file mode 100644
index 0000000000..17273c55df
--- /dev/null
+++ b/dom/performance/Performance.cpp
@@ -0,0 +1,536 @@
+/* -*- 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 "PerformanceEntry.h"
+#include "PerformanceMainThread.h"
+#include "PerformanceMark.h"
+#include "PerformanceMeasure.h"
+#include "PerformanceObserver.h"
+#include "PerformanceResourceTiming.h"
+#include "PerformanceWorker.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/IntegerPrintfMacros.h"
+#include "mozilla/Preferences.h"
+#include "WorkerPrivate.h"
+#include "WorkerRunnable.h"
+
+#ifdef MOZ_WIDGET_GONK
+#define PERFLOG(msg, ...) __android_log_print(ANDROID_LOG_INFO, "PerformanceTiming", msg, ##__VA_ARGS__)
+#else
+#define PERFLOG(msg, ...) printf_stderr(msg, ##__VA_ARGS__)
+#endif
+
+namespace mozilla {
+namespace dom {
+
+using namespace workers;
+
+namespace {
+
+// 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();
+ }
+};
+
+class PrefEnabledRunnable final
+ : public WorkerCheckAPIExposureOnMainThreadRunnable
+{
+public:
+ PrefEnabledRunnable(WorkerPrivate* aWorkerPrivate,
+ const nsCString& aPrefName)
+ : WorkerCheckAPIExposureOnMainThreadRunnable(aWorkerPrivate)
+ , mEnabled(false)
+ , mPrefName(aPrefName)
+ { }
+
+ bool MainThreadRun() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ mEnabled = Preferences::GetBool(mPrefName.get(), false);
+ return true;
+ }
+
+ bool IsEnabled() const
+ {
+ return mEnabled;
+ }
+
+private:
+ bool mEnabled;
+ nsCString mPrefName;
+};
+
+} // anonymous namespace
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(Performance)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(Performance,
+ DOMEventTargetHelper,
+ mUserEntries,
+ mResourceEntries);
+
+NS_IMPL_ADDREF_INHERITED(Performance, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(Performance, DOMEventTargetHelper)
+
+/* static */ already_AddRefed<Performance>
+Performance::CreateForMainThread(nsPIDOMWindowInner* aWindow,
+ nsDOMNavigationTiming* aDOMTiming,
+ nsITimedChannel* aChannel,
+ Performance* aParentPerformance)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<Performance> performance =
+ new PerformanceMainThread(aWindow, aDOMTiming, aChannel,
+ aParentPerformance);
+ return performance.forget();
+}
+
+/* static */ already_AddRefed<Performance>
+Performance::CreateForWorker(workers::WorkerPrivate* aWorkerPrivate)
+{
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ RefPtr<Performance> performance = new PerformanceWorker(aWorkerPrivate);
+ return performance.forget();
+}
+
+Performance::Performance()
+ : mResourceTimingBufferSize(kDefaultResourceTimingBufferSize)
+ , mPendingNotificationObserversTask(false)
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+}
+
+Performance::Performance(nsPIDOMWindowInner* aWindow)
+ : DOMEventTargetHelper(aWindow)
+ , mResourceTimingBufferSize(kDefaultResourceTimingBufferSize)
+ , mPendingNotificationObserversTask(false)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+Performance::~Performance()
+{}
+
+JSObject*
+Performance::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return PerformanceBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+Performance::GetEntries(nsTArray<RefPtr<PerformanceEntry>>& aRetval)
+{
+ aRetval = mResourceEntries;
+ aRetval.AppendElements(mUserEntries);
+ aRetval.Sort(PerformanceEntryComparator());
+}
+
+void
+Performance::GetEntriesByType(const nsAString& aEntryType,
+ nsTArray<RefPtr<PerformanceEntry>>& aRetval)
+{
+ if (aEntryType.EqualsLiteral("resource")) {
+ aRetval = mResourceEntries;
+ return;
+ }
+
+ aRetval.Clear();
+
+ if (aEntryType.EqualsLiteral("mark") ||
+ aEntryType.EqualsLiteral("measure")) {
+ for (PerformanceEntry* entry : mUserEntries) {
+ if (entry->GetEntryType().Equals(aEntryType)) {
+ aRetval.AppendElement(entry);
+ }
+ }
+ }
+}
+
+void
+Performance::GetEntriesByName(const nsAString& aName,
+ const Optional<nsAString>& aEntryType,
+ nsTArray<RefPtr<PerformanceEntry>>& aRetval)
+{
+ aRetval.Clear();
+
+ for (PerformanceEntry* entry : mResourceEntries) {
+ if (entry->GetName().Equals(aName) &&
+ (!aEntryType.WasPassed() ||
+ entry->GetEntryType().Equals(aEntryType.Value()))) {
+ aRetval.AppendElement(entry);
+ }
+ }
+
+ for (PerformanceEntry* entry : mUserEntries) {
+ if (entry->GetName().Equals(aName) &&
+ (!aEntryType.WasPassed() ||
+ entry->GetEntryType().Equals(aEntryType.Value()))) {
+ aRetval.AppendElement(entry);
+ }
+ }
+
+ aRetval.Sort(PerformanceEntryComparator());
+}
+
+void
+Performance::ClearUserEntries(const Optional<nsAString>& aEntryName,
+ const nsAString& aEntryType)
+{
+ for (uint32_t i = 0; i < mUserEntries.Length();) {
+ if ((!aEntryName.WasPassed() ||
+ mUserEntries[i]->GetName().Equals(aEntryName.Value())) &&
+ (aEntryType.IsEmpty() ||
+ mUserEntries[i]->GetEntryType().Equals(aEntryType))) {
+ mUserEntries.RemoveElementAt(i);
+ } else {
+ ++i;
+ }
+ }
+}
+
+void
+Performance::ClearResourceTimings()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mResourceEntries.Clear();
+}
+
+DOMHighResTimeStamp
+Performance::RoundTime(double aTime) const
+{
+ // Round down to the nearest 20us, because if the timer is too accurate people
+ // can do nasty timing attacks with it.
+ const double maxResolutionMs = 0.020;
+ return floor(aTime / maxResolutionMs) * maxResolutionMs;
+}
+
+
+void
+Performance::Mark(const nsAString& aName, ErrorResult& aRv)
+{
+ // Don't add the entry if the buffer is full. XXX should be removed by bug 1159003.
+ if (mUserEntries.Length() >= mResourceTimingBufferSize) {
+ return;
+ }
+
+ if (IsPerformanceTimingAttribute(aName)) {
+ aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+ return;
+ }
+
+ RefPtr<PerformanceMark> performanceMark =
+ new PerformanceMark(GetAsISupports(), aName, Now());
+ InsertUserEntry(performanceMark);
+
+ if (profiler_is_active()) {
+ PROFILER_MARKER(NS_ConvertUTF16toUTF8(aName).get());
+ }
+}
+
+void
+Performance::ClearMarks(const Optional<nsAString>& aName)
+{
+ ClearUserEntries(aName, NS_LITERAL_STRING("mark"));
+}
+
+DOMHighResTimeStamp
+Performance::ResolveTimestampFromName(const nsAString& aName,
+ ErrorResult& aRv)
+{
+ AutoTArray<RefPtr<PerformanceEntry>, 1> arr;
+ DOMHighResTimeStamp ts;
+ 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;
+ }
+
+ 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)
+{
+ // Don't add the entry if the buffer is full. XXX should be removed by bug
+ // 1159003.
+ if (mUserEntries.Length() >= mResourceTimingBufferSize) {
+ return;
+ }
+
+ DOMHighResTimeStamp startTime;
+ DOMHighResTimeStamp endTime;
+
+ if (IsPerformanceTimingAttribute(aName)) {
+ aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+ return;
+ }
+
+ 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(GetAsISupports(), aName, startTime, endTime);
+ InsertUserEntry(performanceMeasure);
+}
+
+void
+Performance::ClearMeasures(const Optional<nsAString>& aName)
+{
+ ClearUserEntries(aName, NS_LITERAL_STRING("measure"));
+}
+
+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()).get(),
+ NS_ConvertUTF16toUTF8(aEntry->GetName()).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;
+ init.mName = aEntry->GetName();
+ init.mEntryType = aEntry->GetEntryType();
+ init.mStartTime = aEntry->StartTime();
+ init.mDuration = aEntry->Duration();
+ init.mEpoch = aEpoch;
+ init.mOrigin = NS_ConvertUTF8toUTF16(aOwner.BeginReading());
+
+ RefPtr<PerformanceEntryEvent> perfEntryEvent =
+ PerformanceEntryEvent::Constructor(this, NS_LITERAL_STRING("performanceentry"), init);
+
+ nsCOMPtr<EventTarget> et = do_QueryInterface(GetOwner());
+ if (et) {
+ bool dummy = false;
+ et->DispatchEvent(perfEntryEvent, &dummy);
+ }
+}
+
+void
+Performance::InsertUserEntry(PerformanceEntry* aEntry)
+{
+ mUserEntries.InsertElementSorted(aEntry,
+ PerformanceEntryComparator());
+
+ QueueEntry(aEntry);
+}
+
+void
+Performance::SetResourceTimingBufferSize(uint64_t aMaxSize)
+{
+ mResourceTimingBufferSize = aMaxSize;
+}
+
+void
+Performance::InsertResourceEntry(PerformanceEntry* aEntry)
+{
+ MOZ_ASSERT(aEntry);
+ MOZ_ASSERT(mResourceEntries.Length() < mResourceTimingBufferSize);
+ if (mResourceEntries.Length() >= mResourceTimingBufferSize) {
+ return;
+ }
+
+ mResourceEntries.InsertElementSorted(aEntry,
+ PerformanceEntryComparator());
+ if (mResourceEntries.Length() == mResourceTimingBufferSize) {
+ // call onresourcetimingbufferfull
+ DispatchBufferFullEvent();
+ }
+ QueueEntry(aEntry);
+}
+
+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,
+ PerformanceObserver,
+ Notify, ());
+}
+
+void
+Performance::CancelNotificationObservers()
+{
+ mPendingNotificationObserversTask = false;
+}
+
+class NotifyObserversTask final : public CancelableRunnable
+{
+public:
+ explicit NotifyObserversTask(Performance* aPerformance)
+ : mPerformance(aPerformance)
+ {
+ MOZ_ASSERT(mPerformance);
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(mPerformance);
+ mPerformance->NotifyObservers();
+ return NS_OK;
+ }
+
+ nsresult Cancel() override
+ {
+ mPerformance->CancelNotificationObservers();
+ mPerformance = nullptr;
+ return NS_OK;
+ }
+
+private:
+ ~NotifyObserversTask()
+ {
+ }
+
+ RefPtr<Performance> mPerformance;
+};
+
+void
+Performance::RunNotificationObserversTask()
+{
+ mPendingNotificationObserversTask = true;
+ nsCOMPtr<nsIRunnable> task = new NotifyObserversTask(this);
+ nsresult rv = NS_DispatchToCurrentThread(task);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mPendingNotificationObserversTask = false;
+ }
+}
+
+void
+Performance::QueueEntry(PerformanceEntry* aEntry)
+{
+ if (mObservers.IsEmpty()) {
+ return;
+ }
+ NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mObservers,
+ PerformanceObserver,
+ QueueEntry, (aEntry));
+
+ if (!mPendingNotificationObserversTask) {
+ RunNotificationObserversTask();
+ }
+}
+
+/* static */ bool
+Performance::IsEnabled(JSContext* aCx, JSObject* aGlobal)
+{
+ if (NS_IsMainThread()) {
+ return Preferences::GetBool("dom.enable_user_timing", false);
+ }
+
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+ workerPrivate->AssertIsOnWorkerThread();
+
+ RefPtr<PrefEnabledRunnable> runnable =
+ new PrefEnabledRunnable(workerPrivate,
+ NS_LITERAL_CSTRING("dom.enable_user_timing"));
+ return runnable->Dispatch() && runnable->IsEnabled();
+}
+
+/* static */ bool
+Performance::IsObserverEnabled(JSContext* aCx, JSObject* aGlobal)
+{
+ if (NS_IsMainThread()) {
+ return Preferences::GetBool("dom.enable_performance_observer", false);
+ }
+
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+ workerPrivate->AssertIsOnWorkerThread();
+
+ RefPtr<PrefEnabledRunnable> runnable =
+ new PrefEnabledRunnable(workerPrivate,
+ NS_LITERAL_CSTRING("dom.enable_performance_observer"));
+
+ return runnable->Dispatch() && runnable->IsEnabled();
+}
+
+} // dom namespace
+} // mozilla namespace
diff --git a/dom/performance/Performance.h b/dom/performance/Performance.h
new file mode 100644
index 0000000000..bc70589a5f
--- /dev/null
+++ b/dom/performance/Performance.h
@@ -0,0 +1,162 @@
+/* -*- 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"
+
+class nsITimedChannel;
+class nsIHttpChannel;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+class PerformanceEntry;
+class PerformanceNavigation;
+class PerformanceObserver;
+class PerformanceTiming;
+
+namespace workers {
+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 IsEnabled(JSContext* aCx, JSObject* aGlobal);
+
+ static bool IsObserverEnabled(JSContext* aCx, JSObject* aGlobal);
+
+ static already_AddRefed<Performance>
+ CreateForMainThread(nsPIDOMWindowInner* aWindow,
+ nsDOMNavigationTiming* aDOMTiming,
+ nsITimedChannel* aChannel,
+ Performance* aParentPerformance);
+
+ static already_AddRefed<Performance>
+ CreateForWorker(workers::WorkerPrivate* aWorkerPrivate);
+
+ JSObject* WrapObject(JSContext *cx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ void GetEntries(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);
+
+ virtual void AddEntry(nsIHttpChannel* channel,
+ nsITimedChannel* timedChannel) = 0;
+
+ void ClearResourceTimings();
+
+ virtual DOMHighResTimeStamp Now() const = 0;
+
+ 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);
+ void NotifyObservers();
+ void CancelNotificationObservers();
+
+ virtual PerformanceTiming* Timing() = 0;
+
+ virtual PerformanceNavigation* Navigation() = 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 Performance* GetParentPerformance() const = 0;
+
+protected:
+ Performance();
+ explicit Performance(nsPIDOMWindowInner* aWindow);
+
+ virtual ~Performance();
+
+ virtual void InsertUserEntry(PerformanceEntry* aEntry);
+ void InsertResourceEntry(PerformanceEntry* aEntry);
+
+ void ClearUserEntries(const Optional<nsAString>& aEntryName,
+ const nsAString& aEntryType);
+
+ DOMHighResTimeStamp ResolveTimestampFromName(const nsAString& aName,
+ ErrorResult& aRv);
+
+ virtual nsISupports* GetAsISupports() = 0;
+
+ virtual void DispatchBufferFullEvent() = 0;
+
+ virtual TimeStamp CreationTimeStamp() const = 0;
+
+ virtual DOMHighResTimeStamp CreationTime() const = 0;
+
+ virtual bool IsPerformanceTimingAttribute(const nsAString& aName) = 0;
+
+ virtual DOMHighResTimeStamp
+ GetPerformanceTimingFromString(const nsAString& aTimingName) = 0;
+
+ bool IsResourceEntryLimitReached() const
+ {
+ return mResourceEntries.Length() >= mResourceTimingBufferSize;
+ }
+
+ void LogEntry(PerformanceEntry* aEntry, const nsACString& aOwner) const;
+ void TimingNotification(PerformanceEntry* aEntry, const nsACString& aOwner,
+ uint64_t epoch);
+
+ void RunNotificationObserversTask();
+ void QueueEntry(PerformanceEntry* aEntry);
+
+ DOMHighResTimeStamp RoundTime(double aTime) const;
+
+ nsTObserverArray<PerformanceObserver*> mObservers;
+
+private:
+ nsTArray<RefPtr<PerformanceEntry>> mUserEntries;
+ nsTArray<RefPtr<PerformanceEntry>> mResourceEntries;
+
+ uint64_t mResourceTimingBufferSize;
+ static const uint64_t kDefaultResourceTimingBufferSize = 150;
+ bool mPendingNotificationObserversTask;
+};
+
+} // 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..8a4bc4c71d
--- /dev/null
+++ b/dom/performance/PerformanceEntry.cpp
@@ -0,0 +1,43 @@
+/* -*- 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"
+#include "mozilla/dom/PerformanceEntryBinding.h"
+#include "nsIURI.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(aName),
+ mEntryType(aEntryType)
+{
+ // mParent is null in workers.
+ MOZ_ASSERT(mParent || !NS_IsMainThread());
+}
+
+PerformanceEntry::~PerformanceEntry()
+{
+}
+
+JSObject*
+PerformanceEntry::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return mozilla::dom::PerformanceEntryBinding::Wrap(aCx, this, aGivenProto);
+}
diff --git a/dom/performance/PerformanceEntry.h b/dom/performance/PerformanceEntry.h
new file mode 100644
index 0000000000..bc4f84f1c2
--- /dev/null
+++ b/dom/performance/PerformanceEntry.h
@@ -0,0 +1,96 @@
+/* -*- 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"
+
+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)
+
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ nsISupports* GetParentObject() const
+ {
+ return mParent;
+ }
+
+ void GetName(nsAString& aName) const
+ {
+ aName = mName;
+ }
+
+ const nsAString& GetName() const
+ {
+ return mName;
+ }
+
+ void SetName(const nsAString& aName)
+ {
+ mName = aName;
+ }
+
+ void GetEntryType(nsAString& aEntryType) const
+ {
+ aEntryType = mEntryType;
+ }
+
+ const nsAString& GetEntryType()
+ {
+ return mEntryType;
+ }
+
+ void SetEntryType(const nsAString& aEntryType)
+ {
+ mEntryType = aEntryType;
+ }
+
+ virtual DOMHighResTimeStamp StartTime() const
+ {
+ return 0;
+ }
+
+ virtual DOMHighResTimeStamp Duration() const
+ {
+ return 0;
+ }
+
+ virtual const PerformanceResourceTiming* ToResourceTiming() const
+ {
+ return nullptr;
+ }
+
+protected:
+ nsCOMPtr<nsISupports> mParent;
+ nsString mName;
+ nsString mEntryType;
+};
+
+} // 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..b60b68f627
--- /dev/null
+++ b/dom/performance/PerformanceMainThread.cpp
@@ -0,0 +1,336 @@
+/* -*- 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"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(PerformanceMainThread)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(PerformanceMainThread,
+ Performance)
+NS_IMPL_CYCLE_COLLECTION_UNLINK(mTiming,
+ mNavigation,
+ mParentPerformance)
+ 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,
+ mParentPerformance)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+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,
+ Performance* aParentPerformance)
+ : Performance(aWindow)
+ , mDOMTiming(aDOMTiming)
+ , mChannel(aChannel)
+ , mParentPerformance(aParentPerformance)
+{
+ MOZ_ASSERT(aWindow, "Parent window object should be provided");
+}
+
+PerformanceMainThread::~PerformanceMainThread()
+{
+ mozilla::DropJSObjects(this);
+}
+
+void
+PerformanceMainThread::GetMozMemory(JSContext *aCx,
+ JS::MutableHandle<JSObject*> aObj)
+{
+ if (!mMozMemory) {
+ mMozMemory = js::gc::NewMemoryInfoObject(aCx);
+ if (mMozMemory) {
+ mozilla::HoldJSObjects(this);
+ }
+ }
+
+ aObj.set(mMozMemory);
+}
+
+PerformanceTiming*
+PerformanceMainThread::Timing()
+{
+ if (!mTiming) {
+ // For navigation timing, the third argument (an nsIHtttpChannel) 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(NS_LITERAL_STRING("resourcetimingbufferfull"), true, false);
+ event->SetTrusted(true);
+ DispatchDOMEvent(nullptr, event, nullptr, nullptr);
+}
+
+PerformanceNavigation*
+PerformanceMainThread::Navigation()
+{
+ if (!mNavigation) {
+ mNavigation = new PerformanceNavigation(this);
+ }
+
+ return mNavigation;
+}
+
+DOMHighResTimeStamp
+PerformanceMainThread::Now() const
+{
+ return RoundTime(GetDOMTiming()->TimeStampToDOMHighRes(TimeStamp::Now()));
+}
+
+/**
+ * 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());
+
+ // Check if resource timing is prefed off.
+ if (!nsContentUtils::IsResourceTimingEnabled()) {
+ return;
+ }
+
+ // Don't add the entry if the buffer is full
+ if (IsResourceEntryLimitReached()) {
+ return;
+ }
+
+ if (channel && timedChannel) {
+ nsAutoCString name;
+ nsAutoString initiatorType;
+ nsCOMPtr<nsIURI> originalURI;
+
+ timedChannel->GetInitiatorType(initiatorType);
+
+ // 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."
+ channel->GetOriginalURI(getter_AddRefs(originalURI));
+ originalURI->GetSpec(name);
+ NS_ConvertUTF8toUTF16 entryName(name);
+
+ // 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).
+ RefPtr<PerformanceTiming> performanceTiming =
+ new PerformanceTiming(this, timedChannel, channel,
+ 0);
+
+ // The PerformanceResourceTiming object will use the PerformanceTiming
+ // object to get all the required timings.
+ RefPtr<PerformanceResourceTiming> performanceEntry =
+ new PerformanceResourceTiming(performanceTiming, this, entryName);
+
+ nsAutoCString protocol;
+ channel->GetProtocolVersion(protocol);
+ performanceEntry->SetNextHopProtocol(NS_ConvertUTF8toUTF16(protocol));
+
+ uint64_t encodedBodySize = 0;
+ channel->GetEncodedBodySize(&encodedBodySize);
+ performanceEntry->SetEncodedBodySize(encodedBodySize);
+
+ uint64_t transferSize = 0;
+ channel->GetTransferSize(&transferSize);
+ performanceEntry->SetTransferSize(transferSize);
+
+ uint64_t decodedBodySize = 0;
+ channel->GetDecodedBodySize(&decodedBodySize);
+ if (decodedBodySize == 0) {
+ decodedBodySize = encodedBodySize;
+ }
+ performanceEntry->SetDecodedBodySize(decodedBodySize);
+
+ // If the initiator type had no valid value, then set it to the default
+ // ("other") value.
+ if (initiatorType.IsEmpty()) {
+ initiatorType = NS_LITERAL_STRING("other");
+ }
+ performanceEntry->SetInitiatorType(initiatorType);
+ InsertResourceEntry(performanceEntry);
+ }
+}
+
+// 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", "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)
+{
+ if (!IsPerformanceTimingAttribute(aProperty)) {
+ return 0;
+ }
+ if (aProperty.EqualsLiteral("navigationStart")) {
+ // DOMHighResTimeStamp is in relation to navigationStart, so this will be
+ // zero.
+ return GetDOMTiming()->GetNavigationStart();
+ }
+ if (aProperty.EqualsLiteral("unloadEventStart")) {
+ return GetDOMTiming()->GetUnloadEventStart();
+ }
+ if (aProperty.EqualsLiteral("unloadEventEnd")) {
+ return GetDOMTiming()->GetUnloadEventEnd();
+ }
+ 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("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();
+ }
+ if (aProperty.EqualsLiteral("domLoading")) {
+ return GetDOMTiming()->GetDomLoading();
+ }
+ if (aProperty.EqualsLiteral("domInteractive")) {
+ return GetDOMTiming()->GetDomInteractive();
+ }
+ if (aProperty.EqualsLiteral("domContentLoadedEventStart")) {
+ return GetDOMTiming()->GetDomContentLoadedEventStart();
+ }
+ if (aProperty.EqualsLiteral("domContentLoadedEventEnd")) {
+ return GetDOMTiming()->GetDomContentLoadedEventEnd();
+ }
+ if (aProperty.EqualsLiteral("domComplete")) {
+ return GetDOMTiming()->GetDomComplete();
+ }
+ if (aProperty.EqualsLiteral("loadEventStart")) {
+ return GetDOMTiming()->GetLoadEventStart();
+ }
+ if (aProperty.EqualsLiteral("loadEventEnd")) {
+ return GetDOMTiming()->GetLoadEventEnd();
+ }
+ MOZ_CRASH("IsPerformanceTimingAttribute and GetPerformanceTimingFromString are out of sync");
+ return 0;
+}
+
+void
+PerformanceMainThread::InsertUserEntry(PerformanceEntry* aEntry)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsAutoCString uri;
+ uint64_t markCreationEpoch = 0;
+
+ if (nsContentUtils::IsUserTimingLoggingEnabled() ||
+ nsContentUtils::SendPerformanceTimingNotifications()) {
+ 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 (nsContentUtils::IsUserTimingLoggingEnabled()) {
+ Performance::LogEntry(aEntry, uri);
+ }
+ }
+
+ if (nsContentUtils::SendPerformanceTimingNotifications()) {
+ TimingNotification(aEntry, uri, markCreationEpoch);
+ }
+
+ Performance::InsertUserEntry(aEntry);
+}
+
+TimeStamp
+PerformanceMainThread::CreationTimeStamp() const
+{
+ return GetDOMTiming()->GetNavigationStartTimeStamp();
+}
+
+DOMHighResTimeStamp
+PerformanceMainThread::CreationTime() const
+{
+ return GetDOMTiming()->GetNavigationStart();
+}
+
+} // dom namespace
+} // mozilla namespace
diff --git a/dom/performance/PerformanceMainThread.h b/dom/performance/PerformanceMainThread.h
new file mode 100644
index 0000000000..84773f29b0
--- /dev/null
+++ b/dom/performance/PerformanceMainThread.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_PerformanceMainThread_h
+#define mozilla_dom_PerformanceMainThread_h
+
+#include "Performance.h"
+
+namespace mozilla {
+namespace dom {
+
+class PerformanceMainThread final : public Performance
+{
+public:
+ PerformanceMainThread(nsPIDOMWindowInner* aWindow,
+ nsDOMNavigationTiming* aDOMTiming,
+ nsITimedChannel* aChannel,
+ Performance* aParentPerformance);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(PerformanceMainThread,
+ Performance)
+
+ // Performance WebIDL methods
+ DOMHighResTimeStamp Now() const override;
+
+ virtual PerformanceTiming* Timing() override;
+
+ virtual PerformanceNavigation* Navigation() override;
+
+ virtual void AddEntry(nsIHttpChannel* channel,
+ nsITimedChannel* timedChannel) 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 nsITimedChannel* GetChannel() const override
+ {
+ return mChannel;
+ }
+
+ virtual Performance* GetParentPerformance() const override
+ {
+ return mParentPerformance;
+ }
+
+protected:
+ ~PerformanceMainThread();
+
+ nsISupports* GetAsISupports() override
+ {
+ return this;
+ }
+
+ void InsertUserEntry(PerformanceEntry* aEntry) override;
+
+ bool IsPerformanceTimingAttribute(const nsAString& aName) override;
+
+ DOMHighResTimeStamp
+ GetPerformanceTimingFromString(const nsAString& aTimingName) override;
+
+ void DispatchBufferFullEvent() override;
+
+ RefPtr<nsDOMNavigationTiming> mDOMTiming;
+ nsCOMPtr<nsITimedChannel> mChannel;
+ RefPtr<PerformanceTiming> mTiming;
+ RefPtr<PerformanceNavigation> mNavigation;
+ RefPtr<Performance> mParentPerformance;
+ JS::Heap<JSObject*> mMozMemory;
+};
+
+} // 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..30abde65d4
--- /dev/null
+++ b/dom/performance/PerformanceMark.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 "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, NS_LITERAL_STRING("mark"))
+ , mStartTime(aStartTime)
+{
+ // mParent is null in workers.
+ MOZ_ASSERT(mParent || !NS_IsMainThread());
+}
+
+PerformanceMark::~PerformanceMark()
+{
+}
+
+JSObject*
+PerformanceMark::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return PerformanceMarkBinding::Wrap(aCx, this, aGivenProto);
+}
diff --git a/dom/performance/PerformanceMark.h b/dom/performance/PerformanceMark.h
new file mode 100644
index 0000000000..52ad49805c
--- /dev/null
+++ b/dom/performance/PerformanceMark.h
@@ -0,0 +1,38 @@
+/* -*- 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;
+ }
+
+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..d91589fda3
--- /dev/null
+++ b/dom/performance/PerformanceMeasure.cpp
@@ -0,0 +1,33 @@
+/* -*- 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, NS_LITERAL_STRING("measure")),
+ mStartTime(aStartTime),
+ mDuration(aEndTime - aStartTime)
+{
+ // mParent is null in workers.
+ MOZ_ASSERT(mParent || !NS_IsMainThread());
+}
+
+PerformanceMeasure::~PerformanceMeasure()
+{
+}
+
+JSObject*
+PerformanceMeasure::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return PerformanceMeasureBinding::Wrap(aCx, this, aGivenProto);
+}
diff --git a/dom/performance/PerformanceMeasure.h b/dom/performance/PerformanceMeasure.h
new file mode 100644
index 0000000000..241ec0d5b9
--- /dev/null
+++ b/dom/performance/PerformanceMeasure.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 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;
+ }
+
+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..474e6c2618
--- /dev/null
+++ b/dom/performance/PerformanceNavigation.cpp
@@ -0,0 +1,43 @@
+/* -*- 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 {
+namespace 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()
+{
+}
+
+JSObject*
+PerformanceNavigation::WrapObject(JSContext *cx,
+ JS::Handle<JSObject*> aGivenProto)
+{
+ return PerformanceNavigationBinding::Wrap(cx, this, aGivenProto);
+}
+
+uint16_t
+PerformanceNavigation::RedirectCount() const
+{
+ return GetPerformanceTiming()->GetRedirectCount();
+}
+
+} // dom namespace
+} // mozilla namespace
diff --git a/dom/performance/PerformanceNavigation.h b/dom/performance/PerformanceNavigation.h
new file mode 100644
index 0000000000..ee49b6b128
--- /dev/null
+++ b/dom/performance/PerformanceNavigation.h
@@ -0,0 +1,61 @@
+/* -*- 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/PerformanceObserver.cpp b/dom/performance/PerformanceObserver.cpp
new file mode 100644
index 0000000000..11dd30ac23
--- /dev/null
+++ b/dom/performance/PerformanceObserver.cpp
@@ -0,0 +1,184 @@
+/* -*- 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 "nsPIDOMWindow.h"
+#include "nsQueryObject.h"
+#include "nsString.h"
+#include "PerformanceEntry.h"
+#include "PerformanceObserverEntryList.h"
+#include "WorkerPrivate.h"
+#include "WorkerScope.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::dom::workers;
+
+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_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_SCRIPT_OBJECTS
+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
+
+PerformanceObserver::PerformanceObserver(nsPIDOMWindowInner* aOwner,
+ PerformanceObserverCallback& aCb)
+ : mOwner(aOwner)
+ , mCallback(&aCb)
+ , mConnected(false)
+{
+ MOZ_ASSERT(mOwner);
+ mPerformance = aOwner->GetPerformance();
+}
+
+PerformanceObserver::PerformanceObserver(WorkerPrivate* aWorkerPrivate,
+ PerformanceObserverCallback& aCb)
+ : mCallback(&aCb)
+ , 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;
+ }
+ MOZ_ASSERT(ownerWindow->IsInnerWindow());
+
+ 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 PerformanceObserverBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+PerformanceObserver::Notify()
+{
+ if (mQueuedEntries.IsEmpty()) {
+ return;
+ }
+ RefPtr<PerformanceObserverEntryList> list =
+ new PerformanceObserverEntryList(this, mQueuedEntries);
+
+ ErrorResult rv;
+ mCallback->Call(this, *list, *this, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ }
+ mQueuedEntries.Clear();
+}
+
+void
+PerformanceObserver::QueueEntry(PerformanceEntry* aEntry)
+{
+ MOZ_ASSERT(aEntry);
+
+ nsAutoString entryType;
+ aEntry->GetEntryType(entryType);
+ if (!mEntryTypes.Contains<nsString>(entryType)) {
+ return;
+ }
+
+ mQueuedEntries.AppendElement(aEntry);
+}
+
+static const char16_t *const sValidTypeNames[4] = {
+ u"mark",
+ u"measure",
+ u"resource",
+ u"server"
+};
+
+void
+PerformanceObserver::Observe(const PerformanceObserverInit& aOptions,
+ ErrorResult& aRv)
+{
+ if (aOptions.mEntryTypes.IsEmpty()) {
+ aRv.Throw(NS_ERROR_DOM_TYPE_ERR);
+ return;
+ }
+
+ nsTArray<nsString> validEntryTypes;
+
+ for (const char16_t* name : sValidTypeNames) {
+ nsDependentString validTypeName(name);
+ if (aOptions.mEntryTypes.Contains<nsString>(validTypeName) &&
+ !validEntryTypes.Contains<nsString>(validTypeName)) {
+ validEntryTypes.AppendElement(validTypeName);
+ }
+ }
+
+ if (validEntryTypes.IsEmpty()) {
+ aRv.Throw(NS_ERROR_DOM_TYPE_ERR);
+ return;
+ }
+
+ mEntryTypes.SwapElements(validEntryTypes);
+
+ mPerformance->AddObserver(this);
+ mConnected = true;
+}
+
+void
+PerformanceObserver::Disconnect()
+{
+ if (mConnected) {
+ MOZ_ASSERT(mPerformance);
+ mPerformance->RemoveObserver(this);
+ mConnected = false;
+ }
+}
diff --git a/dom/performance/PerformanceObserver.h b/dom/performance/PerformanceObserver.h
new file mode 100644
index 0000000000..a02c960cd6
--- /dev/null
+++ b/dom/performance/PerformanceObserver.h
@@ -0,0 +1,79 @@
+/* -*- 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/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;
+struct PerformanceObserverInit;
+namespace workers {
+class WorkerPrivate;
+} // namespace workers
+
+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(workers::WorkerPrivate* aWorkerPrivate,
+ PerformanceObserverCallback& aCb);
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ nsISupports* GetParentObject() const { return mOwner; }
+
+ void Observe(const PerformanceObserverInit& aOptions,
+ mozilla::ErrorResult& aRv);
+
+ void Disconnect();
+
+ void Notify();
+ void QueueEntry(PerformanceEntry* aEntry);
+
+private:
+ ~PerformanceObserver();
+
+ nsCOMPtr<nsISupports> mOwner;
+ RefPtr<PerformanceObserverCallback> mCallback;
+ RefPtr<Performance> mPerformance;
+ nsTArray<nsString> mEntryTypes;
+ 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..349103f08c
--- /dev/null
+++ b/dom/performance/PerformanceObserverEntryList.cpp
@@ -0,0 +1,96 @@
+/* -*- 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()
+{
+}
+
+JSObject*
+PerformanceObserverEntryList::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return PerformanceObserverEntryListBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+PerformanceObserverEntryList::GetEntries(
+ const PerformanceEntryFilterOptions& aFilter,
+ nsTArray<RefPtr<PerformanceEntry>>& aRetval)
+{
+ aRetval.Clear();
+ 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 (aFilter.mName.WasPassed() &&
+ !entry->GetName().Equals(aFilter.mName.Value())) {
+ continue;
+ }
+ if (aFilter.mEntryType.WasPassed() &&
+ !entry->GetEntryType().Equals(aFilter.mEntryType.Value())) {
+ continue;
+ }
+
+ aRetval.AppendElement(entry);
+ }
+}
+
+void
+PerformanceObserverEntryList::GetEntriesByType(
+ const nsAString& aEntryType,
+ nsTArray<RefPtr<PerformanceEntry>>& aRetval)
+{
+ aRetval.Clear();
+ for (const RefPtr<PerformanceEntry>& entry : mEntries) {
+ if (entry->GetEntryType().Equals(aEntryType)) {
+ aRetval.AppendElement(entry);
+ }
+ }
+}
+
+void
+PerformanceObserverEntryList::GetEntriesByName(
+ const nsAString& aName,
+ const Optional<nsAString>& aEntryType,
+ nsTArray<RefPtr<PerformanceEntry>>& aRetval)
+{
+ aRetval.Clear();
+ for (const RefPtr<PerformanceEntry>& entry : mEntries) {
+ if (entry->GetName().Equals(aName)) {
+ aRetval.AppendElement(entry);
+ }
+ }
+}
diff --git a/dom/performance/PerformanceObserverEntryList.h b/dom/performance/PerformanceObserverEntryList.h
new file mode 100644
index 0000000000..e78ce7dfb3
--- /dev/null
+++ b/dom/performance/PerformanceObserverEntryList.h
@@ -0,0 +1,65 @@
+/* -*- 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,
+ nsTArray<RefPtr<PerformanceEntry>>&
+ aEntries)
+ : mOwner(aOwner)
+ , mEntries(aEntries)
+ {
+ }
+
+ 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/PerformanceResourceTiming.cpp b/dom/performance/PerformanceResourceTiming.cpp
new file mode 100644
index 0000000000..60a20ca28c
--- /dev/null
+++ b/dom/performance/PerformanceResourceTiming.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 "PerformanceResourceTiming.h"
+#include "mozilla/dom/PerformanceResourceTimingBinding.h"
+
+using namespace mozilla::dom;
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(PerformanceResourceTiming,
+ PerformanceEntry,
+ mTiming)
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(PerformanceResourceTiming,
+ PerformanceEntry)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(PerformanceResourceTiming)
+NS_INTERFACE_MAP_END_INHERITING(PerformanceEntry)
+
+NS_IMPL_ADDREF_INHERITED(PerformanceResourceTiming, PerformanceEntry)
+NS_IMPL_RELEASE_INHERITED(PerformanceResourceTiming, PerformanceEntry)
+
+PerformanceResourceTiming::PerformanceResourceTiming(PerformanceTiming* aPerformanceTiming,
+ Performance* aPerformance,
+ const nsAString& aName)
+: PerformanceEntry(aPerformance, aName, NS_LITERAL_STRING("resource")),
+ mTiming(aPerformanceTiming),
+ mEncodedBodySize(0),
+ mTransferSize(0),
+ mDecodedBodySize(0)
+{
+ MOZ_ASSERT(aPerformance, "Parent performance object should be provided");
+}
+
+PerformanceResourceTiming::~PerformanceResourceTiming()
+{
+}
+
+DOMHighResTimeStamp
+PerformanceResourceTiming::StartTime() const
+{
+ DOMHighResTimeStamp startTime = mTiming->RedirectStartHighRes();
+ return startTime ? startTime : mTiming->FetchStartHighRes();
+}
+
+JSObject*
+PerformanceResourceTiming::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return PerformanceResourceTimingBinding::Wrap(aCx, this, aGivenProto);
+}
diff --git a/dom/performance/PerformanceResourceTiming.h b/dom/performance/PerformanceResourceTiming.h
new file mode 100644
index 0000000000..abb653d66b
--- /dev/null
+++ b/dom/performance/PerformanceResourceTiming.h
@@ -0,0 +1,185 @@
+/* -*- 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 "nsCOMPtr.h"
+#include "nsIChannel.h"
+#include "nsITimedChannel.h"
+#include "Performance.h"
+#include "PerformanceEntry.h"
+#include "PerformanceTiming.h"
+
+namespace mozilla {
+namespace dom {
+
+// http://www.w3.org/TR/resource-timing/#performanceresourcetiming
+class PerformanceResourceTiming final : public PerformanceEntry
+{
+public:
+ typedef mozilla::TimeStamp TimeStamp;
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(
+ PerformanceResourceTiming,
+ PerformanceEntry)
+
+ PerformanceResourceTiming(PerformanceTiming* aPerformanceTiming,
+ 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
+ {
+ aNextHopProtocol = mNextHopProtocol;
+ }
+
+ void SetNextHopProtocol(const nsAString& aNextHopProtocol)
+ {
+ mNextHopProtocol = aNextHopProtocol;
+ }
+
+ DOMHighResTimeStamp FetchStart() const {
+ return mTiming
+ ? mTiming->FetchStartHighRes()
+ : 0;
+ }
+
+ DOMHighResTimeStamp RedirectStart() const {
+ // We have to check if all the redirect URIs had the same origin (since
+ // there is no check in RedirectEndHighRes())
+ return mTiming && mTiming->ShouldReportCrossOriginRedirect()
+ ? mTiming->RedirectStartHighRes()
+ : 0;
+ }
+
+ DOMHighResTimeStamp RedirectEnd() const {
+ // We have to check if all the redirect URIs had the same origin (since
+ // there is no check in RedirectEndHighRes())
+ return mTiming && mTiming->ShouldReportCrossOriginRedirect()
+ ? mTiming->RedirectEndHighRes()
+ : 0;
+ }
+
+ DOMHighResTimeStamp DomainLookupStart() const {
+ return mTiming && mTiming->TimingAllowed()
+ ? mTiming->DomainLookupStartHighRes()
+ : 0;
+ }
+
+ DOMHighResTimeStamp DomainLookupEnd() const {
+ return mTiming && mTiming->TimingAllowed()
+ ? mTiming->DomainLookupEndHighRes()
+ : 0;
+ }
+
+ DOMHighResTimeStamp ConnectStart() const {
+ return mTiming && mTiming->TimingAllowed()
+ ? mTiming->ConnectStartHighRes()
+ : 0;
+ }
+
+ DOMHighResTimeStamp ConnectEnd() const {
+ return mTiming && mTiming->TimingAllowed()
+ ? mTiming->ConnectEndHighRes()
+ : 0;
+ }
+
+ DOMHighResTimeStamp RequestStart() const {
+ return mTiming && mTiming->TimingAllowed()
+ ? mTiming->RequestStartHighRes()
+ : 0;
+ }
+
+ DOMHighResTimeStamp ResponseStart() const {
+ return mTiming && mTiming->TimingAllowed()
+ ? mTiming->ResponseStartHighRes()
+ : 0;
+ }
+
+ DOMHighResTimeStamp ResponseEnd() const {
+ return mTiming
+ ? mTiming->ResponseEndHighRes()
+ : 0;
+ }
+
+ DOMHighResTimeStamp SecureConnectionStart() const
+ {
+ // This measurement is not available for Navigation Timing either.
+ // There is a different bug submitted for it.
+ return 0;
+ }
+
+ virtual const PerformanceResourceTiming* ToResourceTiming() const override
+ {
+ return this;
+ }
+
+ uint64_t TransferSize() const
+ {
+ return mTiming && mTiming->TimingAllowed() ? mTransferSize : 0;
+ }
+
+ uint64_t EncodedBodySize() const
+ {
+ return mTiming && mTiming->TimingAllowed() ? mEncodedBodySize : 0;
+ }
+
+ uint64_t DecodedBodySize() const
+ {
+ return mTiming && mTiming->TimingAllowed() ? mDecodedBodySize : 0;
+ }
+
+ void SetEncodedBodySize(uint64_t aEncodedBodySize)
+ {
+ mEncodedBodySize = aEncodedBodySize;
+ }
+
+ void SetTransferSize(uint64_t aTransferSize)
+ {
+ mTransferSize = aTransferSize;
+ }
+
+ void SetDecodedBodySize(uint64_t aDecodedBodySize)
+ {
+ mDecodedBodySize = aDecodedBodySize;
+ }
+
+protected:
+ virtual ~PerformanceResourceTiming();
+
+ nsString mInitiatorType;
+ nsString mNextHopProtocol;
+ RefPtr<PerformanceTiming> mTiming;
+ uint64_t mEncodedBodySize;
+ uint64_t mTransferSize;
+ uint64_t mDecodedBodySize;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_PerformanceResourceTiming_h___ */
diff --git a/dom/performance/PerformanceTiming.cpp b/dom/performance/PerformanceTiming.cpp
new file mode 100644
index 0000000000..527cf94419
--- /dev/null
+++ b/dom/performance/PerformanceTiming.cpp
@@ -0,0 +1,361 @@
+/* -*- 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/dom/PerformanceTimingBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PerformanceTiming, mPerformance)
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(PerformanceTiming, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(PerformanceTiming, Release)
+
+PerformanceTiming::PerformanceTiming(Performance* aPerformance,
+ nsITimedChannel* aChannel,
+ nsIHttpChannel* aHttpChannel,
+ DOMHighResTimeStamp aZeroTime)
+ : mPerformance(aPerformance),
+ mFetchStart(0.0),
+ mZeroTime(aZeroTime),
+ mRedirectCount(0),
+ mTimingAllowed(true),
+ mAllRedirectsSameOrigin(true),
+ mInitialized(!!aChannel),
+ mReportCrossOriginRedirect(true)
+{
+ MOZ_ASSERT(aPerformance, "Parent performance object should be provided");
+
+ if (!nsContentUtils::IsPerformanceTimingEnabled()) {
+ mZeroTime = 0;
+ }
+
+ // 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) {
+ mTimingAllowed = CheckAllowedOrigin(aHttpChannel, aChannel);
+ bool redirectsPassCheck = false;
+ aChannel->GetAllRedirectsPassTimingAllowCheck(&redirectsPassCheck);
+ mReportCrossOriginRedirect = mTimingAllowed && redirectsPassCheck;
+ }
+
+ InitializeTimingInfo(aChannel);
+}
+
+// Copy the timing info from the channel so we don't need to keep the channel
+// alive just to get the timestamps.
+void
+PerformanceTiming::InitializeTimingInfo(nsITimedChannel* aChannel)
+{
+ 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->GetConnectEnd(&mConnectEnd);
+ aChannel->GetRequestStart(&mRequestStart);
+ aChannel->GetResponseStart(&mResponseStart);
+ aChannel->GetCacheReadStart(&mCacheReadStart);
+ aChannel->GetResponseEnd(&mResponseEnd);
+ aChannel->GetCacheReadEnd(&mCacheReadEnd);
+ }
+}
+
+PerformanceTiming::~PerformanceTiming()
+{
+}
+
+DOMHighResTimeStamp
+PerformanceTiming::FetchStartHighRes()
+{
+ if (!mFetchStart) {
+ if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
+ return mZeroTime;
+ }
+ MOZ_ASSERT(!mAsyncOpen.IsNull(), "The fetch start time stamp should always be "
+ "valid if the performance timing is enabled");
+ mFetchStart = (!mAsyncOpen.IsNull())
+ ? TimeStampToDOMHighRes(mAsyncOpen)
+ : 0.0;
+ }
+ return mFetchStart;
+}
+
+DOMTimeMilliSec
+PerformanceTiming::FetchStart()
+{
+ return static_cast<int64_t>(FetchStartHighRes());
+}
+
+bool
+PerformanceTiming::CheckAllowedOrigin(nsIHttpChannel* aResourceChannel,
+ nsITimedChannel* aChannel)
+{
+ if (!IsInitialized()) {
+ return false;
+ }
+
+ // Check that the current document passes the ckeck.
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ aResourceChannel->GetLoadInfo(getter_AddRefs(loadInfo));
+ if (!loadInfo) {
+ return false;
+ }
+
+ // TYPE_DOCUMENT loads have no loadingPrincipal. And that's OK, because we
+ // never actually need to have a performance timing entry for TYPE_DOCUMENT
+ // loads.
+ if (loadInfo->GetExternalContentPolicyType() == nsIContentPolicy::TYPE_DOCUMENT) {
+ return false;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = loadInfo->LoadingPrincipal();
+
+ // 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);
+}
+
+bool
+PerformanceTiming::TimingAllowed() const
+{
+ return mTimingAllowed;
+}
+
+uint16_t
+PerformanceTiming::GetRedirectCount() const
+{
+ if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
+ return 0;
+ }
+ if (!mAllRedirectsSameOrigin) {
+ return 0;
+ }
+ return mRedirectCount;
+}
+
+bool
+PerformanceTiming::ShouldReportCrossOriginRedirect() const
+{
+ if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
+ 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;
+}
+
+/**
+ * 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
+PerformanceTiming::RedirectStartHighRes()
+{
+ if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
+ return mZeroTime;
+ }
+ return TimeStampToDOMHighResOrFetchStart(mRedirectStart);
+}
+
+DOMTimeMilliSec
+PerformanceTiming::RedirectStart()
+{
+ if (!IsInitialized()) {
+ return 0;
+ }
+ // We have to check if all the redirect URIs had the same origin (since there
+ // is no check in RedirectStartHighRes())
+ if (mAllRedirectsSameOrigin && mRedirectCount) {
+ return static_cast<int64_t>(RedirectStartHighRes());
+ }
+ 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
+PerformanceTiming::RedirectEndHighRes()
+{
+ if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
+ return mZeroTime;
+ }
+ return TimeStampToDOMHighResOrFetchStart(mRedirectEnd);
+}
+
+DOMTimeMilliSec
+PerformanceTiming::RedirectEnd()
+{
+ if (!IsInitialized()) {
+ return 0;
+ }
+ // We have to check if all the redirect URIs had the same origin (since there
+ // is no check in RedirectEndHighRes())
+ if (mAllRedirectsSameOrigin && mRedirectCount) {
+ return static_cast<int64_t>(RedirectEndHighRes());
+ }
+ return 0;
+}
+
+DOMHighResTimeStamp
+PerformanceTiming::DomainLookupStartHighRes()
+{
+ if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
+ return mZeroTime;
+ }
+ return TimeStampToDOMHighResOrFetchStart(mDomainLookupStart);
+}
+
+DOMTimeMilliSec
+PerformanceTiming::DomainLookupStart()
+{
+ return static_cast<int64_t>(DomainLookupStartHighRes());
+}
+
+DOMHighResTimeStamp
+PerformanceTiming::DomainLookupEndHighRes()
+{
+ if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
+ return mZeroTime;
+ }
+ // Bug 1155008 - nsHttpTransaction is racy. Return DomainLookupStart when null
+ return mDomainLookupEnd.IsNull() ? DomainLookupStartHighRes()
+ : TimeStampToDOMHighRes(mDomainLookupEnd);
+}
+
+DOMTimeMilliSec
+PerformanceTiming::DomainLookupEnd()
+{
+ return static_cast<int64_t>(DomainLookupEndHighRes());
+}
+
+DOMHighResTimeStamp
+PerformanceTiming::ConnectStartHighRes()
+{
+ if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
+ return mZeroTime;
+ }
+ return mConnectStart.IsNull() ? DomainLookupEndHighRes()
+ : TimeStampToDOMHighRes(mConnectStart);
+}
+
+DOMTimeMilliSec
+PerformanceTiming::ConnectStart()
+{
+ return static_cast<int64_t>(ConnectStartHighRes());
+}
+
+DOMHighResTimeStamp
+PerformanceTiming::ConnectEndHighRes()
+{
+ if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
+ return mZeroTime;
+ }
+ // Bug 1155008 - nsHttpTransaction is racy. Return ConnectStart when null
+ return mConnectEnd.IsNull() ? ConnectStartHighRes()
+ : TimeStampToDOMHighRes(mConnectEnd);
+}
+
+DOMTimeMilliSec
+PerformanceTiming::ConnectEnd()
+{
+ return static_cast<int64_t>(ConnectEndHighRes());
+}
+
+DOMHighResTimeStamp
+PerformanceTiming::RequestStartHighRes()
+{
+ if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
+ return mZeroTime;
+ }
+ return TimeStampToDOMHighResOrFetchStart(mRequestStart);
+}
+
+DOMTimeMilliSec
+PerformanceTiming::RequestStart()
+{
+ return static_cast<int64_t>(RequestStartHighRes());
+}
+
+DOMHighResTimeStamp
+PerformanceTiming::ResponseStartHighRes()
+{
+ if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
+ return mZeroTime;
+ }
+ if (mResponseStart.IsNull() ||
+ (!mCacheReadStart.IsNull() && mCacheReadStart < mResponseStart)) {
+ mResponseStart = mCacheReadStart;
+ }
+ return TimeStampToDOMHighResOrFetchStart(mResponseStart);
+}
+
+DOMTimeMilliSec
+PerformanceTiming::ResponseStart()
+{
+ return static_cast<int64_t>(ResponseStartHighRes());
+}
+
+DOMHighResTimeStamp
+PerformanceTiming::ResponseEndHighRes()
+{
+ if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
+ return mZeroTime;
+ }
+ if (mResponseEnd.IsNull() ||
+ (!mCacheReadEnd.IsNull() && mCacheReadEnd < mResponseEnd)) {
+ mResponseEnd = mCacheReadEnd;
+ }
+ // Bug 1155008 - nsHttpTransaction is racy. Return ResponseStart when null
+ return mResponseEnd.IsNull() ? ResponseStartHighRes()
+ : TimeStampToDOMHighRes(mResponseEnd);
+}
+
+DOMTimeMilliSec
+PerformanceTiming::ResponseEnd()
+{
+ return static_cast<int64_t>(ResponseEndHighRes());
+}
+
+bool
+PerformanceTiming::IsInitialized() const
+{
+ return mInitialized;
+}
+
+JSObject*
+PerformanceTiming::WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto)
+{
+ return PerformanceTimingBinding::Wrap(cx, this, aGivenProto);
+}
+
+} // dom namespace
+} // mozilla namespace
diff --git a/dom/performance/PerformanceTiming.h b/dom/performance/PerformanceTiming.h
new file mode 100644
index 0000000000..aef54a258c
--- /dev/null
+++ b/dom/performance/PerformanceTiming.h
@@ -0,0 +1,278 @@
+/* -*- 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 "nsContentUtils.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsWrapperCache.h"
+#include "Performance.h"
+
+class nsIHttpChannel;
+class nsITimedChannel;
+
+namespace mozilla {
+namespace dom {
+
+// 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;
+ }
+
+ /**
+ * @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 TimeStampToDOMHighResOrFetchStart(TimeStamp aStamp)
+ {
+ return (!aStamp.IsNull())
+ ? TimeStampToDOMHighRes(aStamp)
+ : FetchStartHighRes();
+ }
+
+ /**
+ * 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
+ * envet's timesramp) 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(TimeStamp aStamp) const
+ {
+ MOZ_ASSERT(!aStamp.IsNull());
+ TimeDuration duration =
+ aStamp - GetDOMTiming()->GetNavigationStartTimeStamp();
+ return duration.ToMilliseconds() + mZeroTime;
+ }
+
+ virtual JSObject* WrapObject(JSContext *cx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // PerformanceNavigation WebIDL methods
+ DOMTimeMilliSec NavigationStart() const
+ {
+ if (!nsContentUtils::IsPerformanceTimingEnabled()) {
+ return 0;
+ }
+ return GetDOMTiming()->GetNavigationStart();
+ }
+
+ DOMTimeMilliSec UnloadEventStart()
+ {
+ if (!nsContentUtils::IsPerformanceTimingEnabled()) {
+ return 0;
+ }
+ return GetDOMTiming()->GetUnloadEventStart();
+ }
+
+ DOMTimeMilliSec UnloadEventEnd()
+ {
+ if (!nsContentUtils::IsPerformanceTimingEnabled()) {
+ return 0;
+ }
+ return GetDOMTiming()->GetUnloadEventEnd();
+ }
+
+ uint16_t GetRedirectCount() const;
+
+ // 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);
+
+ // Cached result of CheckAllowedOrigin. If false, security sensitive
+ // attributes of the resourceTiming object will be set to 0
+ bool TimingAllowed() const;
+
+ // 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;
+
+ // High resolution (used by resource timing)
+ DOMHighResTimeStamp FetchStartHighRes();
+ DOMHighResTimeStamp RedirectStartHighRes();
+ DOMHighResTimeStamp RedirectEndHighRes();
+ DOMHighResTimeStamp DomainLookupStartHighRes();
+ DOMHighResTimeStamp DomainLookupEndHighRes();
+ DOMHighResTimeStamp ConnectStartHighRes();
+ DOMHighResTimeStamp ConnectEndHighRes();
+ DOMHighResTimeStamp RequestStartHighRes();
+ DOMHighResTimeStamp ResponseStartHighRes();
+ DOMHighResTimeStamp ResponseEndHighRes();
+
+ // Low resolution (used by navigation timing)
+ DOMTimeMilliSec FetchStart();
+ DOMTimeMilliSec RedirectStart();
+ DOMTimeMilliSec RedirectEnd();
+ DOMTimeMilliSec DomainLookupStart();
+ DOMTimeMilliSec DomainLookupEnd();
+ DOMTimeMilliSec ConnectStart();
+ DOMTimeMilliSec ConnectEnd();
+ DOMTimeMilliSec RequestStart();
+ DOMTimeMilliSec ResponseStart();
+ DOMTimeMilliSec ResponseEnd();
+
+ DOMTimeMilliSec DomLoading()
+ {
+ if (!nsContentUtils::IsPerformanceTimingEnabled()) {
+ return 0;
+ }
+ return GetDOMTiming()->GetDomLoading();
+ }
+
+ DOMTimeMilliSec DomInteractive() const
+ {
+ if (!nsContentUtils::IsPerformanceTimingEnabled()) {
+ return 0;
+ }
+ return GetDOMTiming()->GetDomInteractive();
+ }
+
+ DOMTimeMilliSec DomContentLoadedEventStart() const
+ {
+ if (!nsContentUtils::IsPerformanceTimingEnabled()) {
+ return 0;
+ }
+ return GetDOMTiming()->GetDomContentLoadedEventStart();
+ }
+
+ DOMTimeMilliSec DomContentLoadedEventEnd() const
+ {
+ if (!nsContentUtils::IsPerformanceTimingEnabled()) {
+ return 0;
+ }
+ return GetDOMTiming()->GetDomContentLoadedEventEnd();
+ }
+
+ DOMTimeMilliSec DomComplete() const
+ {
+ if (!nsContentUtils::IsPerformanceTimingEnabled()) {
+ return 0;
+ }
+ return GetDOMTiming()->GetDomComplete();
+ }
+
+ DOMTimeMilliSec LoadEventStart() const
+ {
+ if (!nsContentUtils::IsPerformanceTimingEnabled()) {
+ return 0;
+ }
+ return GetDOMTiming()->GetLoadEventStart();
+ }
+
+ DOMTimeMilliSec LoadEventEnd() const
+ {
+ if (!nsContentUtils::IsPerformanceTimingEnabled()) {
+ return 0;
+ }
+ return GetDOMTiming()->GetLoadEventEnd();
+ }
+
+private:
+ ~PerformanceTiming();
+
+ bool IsInitialized() const;
+ void InitializeTimingInfo(nsITimedChannel* aChannel);
+
+ RefPtr<Performance> mPerformance;
+ DOMHighResTimeStamp mFetchStart;
+
+ // 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;
+
+ TimeStamp mAsyncOpen;
+ TimeStamp mRedirectStart;
+ TimeStamp mRedirectEnd;
+ TimeStamp mDomainLookupStart;
+ TimeStamp mDomainLookupEnd;
+ TimeStamp mConnectStart;
+ TimeStamp mConnectEnd;
+ TimeStamp mRequestStart;
+ TimeStamp mResponseStart;
+ TimeStamp mCacheReadStart;
+ TimeStamp mResponseEnd;
+ TimeStamp mCacheReadEnd;
+ uint16_t mRedirectCount;
+ bool mTimingAllowed;
+ bool mAllRedirectsSameOrigin;
+ bool mInitialized;
+
+ // 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;
+};
+
+} // namespace dom
+} // 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..85ca2ccd82
--- /dev/null
+++ b/dom/performance/PerformanceWorker.cpp
@@ -0,0 +1,85 @@
+/* -*- 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 "WorkerPrivate.h"
+
+namespace mozilla {
+namespace dom {
+
+using namespace workers;
+
+PerformanceWorker::PerformanceWorker(WorkerPrivate* aWorkerPrivate)
+ : mWorkerPrivate(aWorkerPrivate)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+}
+
+PerformanceWorker::~PerformanceWorker()
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+}
+
+DOMHighResTimeStamp
+PerformanceWorker::Now() const
+{
+ TimeDuration duration =
+ TimeStamp::Now() - mWorkerPrivate->NowBaseTimeStamp();
+ return RoundTime(duration.ToMilliseconds());
+}
+
+// To be removed once bug 1124165 lands
+bool
+PerformanceWorker::IsPerformanceTimingAttribute(const nsAString& aName)
+{
+ // In workers we just support navigationStart.
+ return aName.EqualsASCII("navigationStart");
+}
+
+DOMHighResTimeStamp
+PerformanceWorker::GetPerformanceTimingFromString(const nsAString& aProperty)
+{
+ if (!IsPerformanceTimingAttribute(aProperty)) {
+ return 0;
+ }
+
+ if (aProperty.EqualsLiteral("navigationStart")) {
+ return mWorkerPrivate->NowBaseTime();
+ }
+
+ MOZ_CRASH("IsPerformanceTimingAttribute and GetPerformanceTimingFromString are out of sync");
+ return 0;
+}
+
+void
+PerformanceWorker::InsertUserEntry(PerformanceEntry* aEntry)
+{
+ if (mWorkerPrivate->PerformanceLoggingEnabled()) {
+ 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->NowBaseTimeStamp();
+}
+
+DOMHighResTimeStamp
+PerformanceWorker::CreationTime() const
+{
+ return mWorkerPrivate->NowBaseTime();
+}
+
+} // dom namespace
+} // mozilla namespace
diff --git a/dom/performance/PerformanceWorker.h b/dom/performance/PerformanceWorker.h
new file mode 100644
index 0000000000..7eef0d9748
--- /dev/null
+++ b/dom/performance/PerformanceWorker.h
@@ -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/. */
+
+#ifndef mozilla_dom_PerformanceWorker_h
+#define mozilla_dom_PerformanceWorker_h
+
+#include "Performance.h"
+
+namespace mozilla {
+namespace dom {
+
+namespace workers {
+class WorkerPrivate;
+}
+
+class PerformanceWorker final : public Performance
+{
+public:
+ explicit PerformanceWorker(workers::WorkerPrivate* aWorkerPrivate);
+
+ // Performance WebIDL methods
+ DOMHighResTimeStamp Now() const override;
+
+ 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 AddEntry(nsIHttpChannel* channel,
+ nsITimedChannel* timedChannel) 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 nsITimedChannel* GetChannel() const override
+ {
+ MOZ_CRASH("This should not be called on workers.");
+ return nullptr;
+ }
+
+ virtual Performance* GetParentPerformance() const override
+ {
+ MOZ_CRASH("This should not be called on workers.");
+ return nullptr;
+ }
+
+protected:
+ ~PerformanceWorker();
+
+ nsISupports* GetAsISupports() override
+ {
+ return nullptr;
+ }
+
+ void InsertUserEntry(PerformanceEntry* aEntry) override;
+
+ bool IsPerformanceTimingAttribute(const nsAString& aName) override;
+
+ DOMHighResTimeStamp
+ GetPerformanceTimingFromString(const nsAString& aTimingName) override;
+
+ void DispatchBufferFullEvent() override
+ {
+ MOZ_CRASH("This should not be called on workers.");
+ }
+
+private:
+ workers::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..3286a0a4c7
--- /dev/null
+++ b/dom/performance/moz.build
@@ -0,0 +1,39 @@
+# -*- 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/.
+
+EXPORTS.mozilla.dom += [
+ 'Performance.h',
+ 'PerformanceEntry.h',
+ 'PerformanceMark.h',
+ 'PerformanceMeasure.h',
+ 'PerformanceNavigation.h',
+ 'PerformanceObserver.h',
+ 'PerformanceObserverEntryList.h',
+ 'PerformanceResourceTiming.h',
+ 'PerformanceTiming.h',
+]
+
+UNIFIED_SOURCES += [
+ 'Performance.cpp',
+ 'PerformanceEntry.cpp',
+ 'PerformanceMainThread.cpp',
+ 'PerformanceMark.cpp',
+ 'PerformanceMeasure.cpp',
+ 'PerformanceNavigation.cpp',
+ 'PerformanceObserver.cpp',
+ 'PerformanceObserverEntryList.cpp',
+ 'PerformanceResourceTiming.cpp',
+ 'PerformanceTiming.cpp',
+ 'PerformanceWorker.cpp',
+]
+
+LOCAL_INCLUDES += [
+ '/dom/workers',
+]
+
+MOCHITEST_MANIFESTS += [ 'tests/mochitest.ini' ]
+
+FINAL_LIBRARY = 'xul'
diff --git a/dom/performance/tests/mochitest.ini b/dom/performance/tests/mochitest.ini
new file mode 100644
index 0000000000..18f7f0e455
--- /dev/null
+++ b/dom/performance/tests/mochitest.ini
@@ -0,0 +1,17 @@
+[DEFAULT]
+support-files =
+ performance_observer.html
+ 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
+ worker_performance_observer.html
+
+[test_performance_observer.html]
+[test_performance_user_timing.html]
+[test_worker_user_timing.html]
+[test_worker_observer.html]
+[test_sharedWorker_performance_user_timing.html]
+[test_worker_performance_now.html]
diff --git a/dom/performance/tests/performance_observer.html b/dom/performance/tests/performance_observer.html
new file mode 100644
index 0000000000..b8ced9296c
--- /dev/null
+++ b/dom/performance/tests/performance_observer.html
@@ -0,0 +1,74 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE html>
+<meta charset=utf-8>
+<html>
+<head>
+<title>Test for performance observer</title>
+<script>
+'use strict';
+[
+ "promise_test", "test", "setup",
+ "assert_true", "assert_equals", "assert_array_equals",
+ "assert_throws", "assert_unreached"
+].forEach(func => {
+ window[func] = opener[func].bind(opener);
+});
+function done() {
+ opener.add_completion_callback(() => {
+ self.close();
+ });
+ opener.done();
+}
+
+</script>
+<script src="test_performance_observer.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("test-data.json");
+
+ return promise.then(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.");
+ });
+}, "resource-timing test");
+
+done();
+
+</script>
+</body>
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..ced3e95fab
--- /dev/null
+++ b/dom/performance/tests/sharedworker_performance_user_timing.js
@@ -0,0 +1,30 @@
+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_observer.html b/dom/performance/tests/test_performance_observer.html
new file mode 100644
index 0000000000..d368783155
--- /dev/null
+++ b/dom/performance/tests/test_performance_observer.html
@@ -0,0 +1,17 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test for performance observer</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+'use strict';
+SpecialPowers.pushPrefEnv({"set": [["dom.enable_performance_observer", true]]},
+ function() {
+ window.open("performance_observer.html");
+ });
+</script>
diff --git a/dom/performance/tests/test_performance_observer.js b/dom/performance/tests/test_performance_observer.js
new file mode 100644
index 0000000000..9716570e29
--- /dev/null
+++ b/dom/performance/tests/test_performance_observer.js
@@ -0,0 +1,226 @@
+setup({ explicit_done: true });
+
+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: "TypeError"}, function() {
+ observer.observe();
+ }, "observe() should throw TypeError exception if no option specified.");
+
+ assert_throws({name: "TypeError"}, function() {
+ observer.observe({ unsupportedAttribute: "unsupported" });
+ }, "obsrve() should throw TypeError exception if the option has no 'entryTypes' attribute.");
+
+ assert_throws({name: "TypeError"}, function() {
+ observer.observe({ entryTypes: [] });
+ }, "obsrve() should throw TypeError exception if 'entryTypes' attribute is an empty sequence.");
+
+ assert_throws({name: "TypeError"}, function() {
+ observer.observe({ entryTypes: null });
+ }, "obsrve() should throw TypeError exception if 'entryTypes' attribute is null.");
+
+ assert_throws({name: "TypeError"}, function() {
+ observer.observe({ entryTypes: ["invalid"]});
+ }, "obsrve() should throw TypeError exception if 'entryTypes' attribute value is invalid.");
+}, "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_user_timing.html b/dom/performance/tests/test_performance_user_timing.html
new file mode 100644
index 0000000000..5f4123eaeb
--- /dev/null
+++ b/dom/performance/tests/test_performance_user_timing.html
@@ -0,0 +1,45 @@
+<!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 type="text/javascript" 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);
+ }
+ }
+
+ SimpleTest.finish();
+ }
+
+ 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..3d05ebb77c
--- /dev/null
+++ b/dom/performance/tests/test_performance_user_timing.js
@@ -0,0 +1,272 @@
+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) < .00001,
+ "Measure duration ( " + measure.duration + ") should be difference between two marks");
+ },
+ function() {
+ 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) {
+ try {
+ if (n == "toJSON") {
+ ok(true, "Skipping toJSON entry in collision test");
+ continue;
+ }
+ performance.measure(n);
+ ok(false, "Measure name collision test failed for name " + n + ", shouldn't make it here!");
+ } catch (e) {
+ ok(e instanceof DOMException, "DOM exception thrown for measure named " + n);
+ is(e.code, e.SYNTAX_ERR, "DOM exception for name collision is syntax error");
+ }
+ };
+ },
+ // 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"].indexOf(n) >= 0,
+ "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..0498672f3d
--- /dev/null
+++ b/dom/performance/tests/test_sharedWorker_performance_user_timing.html
@@ -0,0 +1,27 @@
+<!--
+ 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 type="text/javascript" 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') {
+ SimpleTest.finish();
+ } else if (event.data.type == 'status') {
+ ok(event.data.status, event.data.msg);
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+ </script>
+ </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..b9ed0c9646
--- /dev/null
+++ b/dom/performance/tests/test_worker_observer.html
@@ -0,0 +1,17 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test for performance observer in worker</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+'use strict';
+SpecialPowers.pushPrefEnv({"set": [["dom.enable_performance_observer", true]]},
+ function() {
+ window.open("worker_performance_observer.html");
+ });
+</script>
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..ec84ccd10d
--- /dev/null
+++ b/dom/performance/tests/test_worker_performance_now.html
@@ -0,0 +1,32 @@
+<!-- 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 type="text/javascript" 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 worker = new Worker('test_worker_performance_now.js');
+worker.onmessage = function(event) {
+ if (event.data.type == 'finish') {
+ SimpleTest.finish();
+
+ } else if (event.data.type == 'status') {
+ ok(event.data.status, event.data.msg);
+
+ } else if (event.data.type == 'getOSCPU') {
+ worker.postMessage({
+ type: 'returnOSCPU',
+ result: navigator.oscpu
+ });
+ }
+}
+
+</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..c2a9050319
--- /dev/null
+++ b/dom/performance/tests/test_worker_performance_now.js
@@ -0,0 +1,76 @@
+function ok(a, msg) {
+ dump("OK: " + !!a + " => " + a + ": " + msg + "\n");
+ postMessage({type: 'status', status: !!a, msg: a + ": " + msg });
+}
+
+function workerTestDone() {
+ postMessage({ type: 'finish' });
+}
+
+function workerTestGetOSCPU(cb) {
+ addEventListener('message', function workerTestGetOSCPUCB(e) {
+ if (e.data.type !== 'returnOSCPU') {
+ return;
+ }
+ removeEventListener('message', workerTestGetOSCPUCB);
+ cb(e.data.result);
+ });
+ postMessage({
+ type: 'getOSCPU'
+ });
+}
+
+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.");
+
+// The spec says performance.now() should have micro-second resolution, but allows 1ms if the platform doesn't support it.
+// Our implementation does provide micro-second resolution, except for windows XP combined with some HW properties
+// where we can't use QueryPerformanceCounters (see comments at mozilla-central/xpcom/ds/TimeStamp_windows.cpp).
+// This XP-low-res case results in about 15ms resolutions, and can be identified when perf.now() returns only integers.
+//
+// Since setTimeout might return too early/late, our goal is that perf.now() changed within 2ms
+// (or 25ms for XP-low-res), rather than specific number of setTimeout(N) invocations.
+// See bug 749894 (intermittent failures of this test)
+var platformPossiblyLowRes;
+workerTestGetOSCPU(function(oscpu) {
+ platformPossiblyLowRes = oscpu.indexOf("Windows NT 5.1") == 0; // XP only
+ setTimeout(checkAfterTimeout, 1);
+});
+var allInts = (n % 1) == 0; // Indicator of limited HW resolution.
+var checks = 0;
+
+function checkAfterTimeout() {
+ checks++;
+ var d2 = Date.now();
+ var n2 = self.performance.now();
+
+ allInts = allInts && (n2 % 1) == 0;
+ var lowResCounter = platformPossiblyLowRes && allInts;
+
+ if ( n2 == n && checks < 50 && // 50 is just a failsafe. Our real goals are 2ms or 25ms.
+ ( (d2 - d) < 2 // The spec allows 1ms resolution. We allow up to measured 2ms to ellapse.
+ ||
+ lowResCounter &&
+ (d2 - d) < 25
+ )
+ ) {
+ setTimeout(checkAfterTimeout, 1);
+ return;
+ }
+
+ // Loose spec: 1ms resolution, or 15ms resolution for the XP-low-res case.
+ // We shouldn't test that dt is actually within 2/25ms since the iterations break if it isn't, and timeout could be late.
+ ok(n2 > n, "Loose - the value of now() should increase within 2ms (or 25ms if low-res counter) (delta now(): " + (n2 - n) + " ms).");
+
+ // Strict spec: if it's not the XP-low-res case, while the spec allows 1ms resolution, it prefers microseconds, which we provide.
+ // Since the fastest setTimeout return which I observed was ~500 microseconds, a microseconds counter should change in 1 iteretion.
+ ok(n2 > n && (lowResCounter || checks == 1),
+ "Strict - [if high-res counter] the value of now() should increase after one setTimeout (hi-res: " + (!lowResCounter) +
+ ", iters: " + checks +
+ ", dt: " + (d2 - d) +
+ ", 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..2bf465cbda
--- /dev/null
+++ b/dom/performance/tests/test_worker_user_timing.html
@@ -0,0 +1,27 @@
+<!--
+ 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 type="text/javascript" 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') {
+ SimpleTest.finish();
+ } else if (event.data.type == 'status') {
+ ok(event.data.status, event.data.msg);
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+ </script>
+ </body>
+</html>
diff --git a/dom/performance/tests/worker_performance_observer.html b/dom/performance/tests/worker_performance_observer.html
new file mode 100644
index 0000000000..613762f521
--- /dev/null
+++ b/dom/performance/tests/worker_performance_observer.html
@@ -0,0 +1,32 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE html>
+<meta charset=utf-8>
+<html>
+<head>
+<title>Test for performance observer in worker</title>
+</head>
+<body>
+<div id="log"></div>
+<script>
+[
+ "async_test", "test", "setup",
+ "assert_true", "assert_equals", "assert_array_equals",
+ "assert_throws", "fetch_tests_from_worker"
+].forEach(func => {
+ window[func] = opener[func].bind(opener);
+});
+
+function done() {
+ opener.add_completion_callback(() => {
+ self.close();
+ });
+ opener.done();
+}
+
+fetch_tests_from_worker(new Worker("worker_performance_observer.js"));
+done();
+</script>
+</body>
diff --git a/dom/performance/tests/worker_performance_observer.js b/dom/performance/tests/worker_performance_observer.js
new file mode 100644
index 0000000000..8faf66f67e
--- /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..fa2f1afab0
--- /dev/null
+++ b/dom/performance/tests/worker_performance_user_timing.js
@@ -0,0 +1,24 @@
+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'});