diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /dom/performance | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | uxp-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz |
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/performance')
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'}); |