From 040cf3439b25da595172463da18c183e441c21f7 Mon Sep 17 00:00:00 2001 From: Hiroyuki Ikezoe Date: Mon, 17 Aug 2015 15:13:20 -0700 Subject: [PATCH] Bug 1165796 - Part 2: Implement PerformanceObserver.r=baku --- dom/base/PerformanceEntry.h | 6 + dom/base/PerformanceObserver.cpp | 154 ++++++++++++ dom/base/PerformanceObserver.h | 76 ++++++ dom/base/PerformanceObserverEntryList.cpp | 100 ++++++++ dom/base/PerformanceObserverEntryList.h | 64 +++++ dom/base/PerformanceResourceTiming.h | 5 + dom/base/moz.build | 4 + dom/base/nsPerformance.cpp | 40 +++ dom/base/nsPerformance.h | 9 + dom/base/test/mochitest.ini | 3 + dom/base/test/performance_observer.html | 63 +++++ dom/base/test/test_performance_observer.html | 17 ++ dom/base/test/test_performance_observer.js | 236 ++++++++++++++++++ dom/webidl/Performance.webidl | 1 + dom/webidl/PerformanceObserver.webidl | 23 ++ .../PerformanceObserverEntryList.webidl | 24 ++ dom/webidl/moz.build | 2 + dom/workers/test/mochitest.ini | 3 + dom/workers/test/performance_observer.html | 32 +++ .../test/test_performance_observer.html | 17 ++ .../test/worker_performance_observer.js | 4 + 21 files changed, 883 insertions(+) create mode 100644 dom/base/PerformanceObserver.cpp create mode 100644 dom/base/PerformanceObserver.h create mode 100644 dom/base/PerformanceObserverEntryList.cpp create mode 100644 dom/base/PerformanceObserverEntryList.h create mode 100644 dom/base/test/performance_observer.html create mode 100644 dom/base/test/test_performance_observer.html create mode 100644 dom/base/test/test_performance_observer.js create mode 100644 dom/webidl/PerformanceObserver.webidl create mode 100644 dom/webidl/PerformanceObserverEntryList.webidl create mode 100644 dom/workers/test/performance_observer.html create mode 100644 dom/workers/test/test_performance_observer.html create mode 100644 dom/workers/test/worker_performance_observer.js diff --git a/dom/base/PerformanceEntry.h b/dom/base/PerformanceEntry.h index ec8d2e8efbe..bc4f84f1c27 100644 --- a/dom/base/PerformanceEntry.h +++ b/dom/base/PerformanceEntry.h @@ -15,6 +15,7 @@ class nsISupports; namespace mozilla { namespace dom { +class PerformanceResourceTiming; // http://www.w3.org/TR/performance-timeline/#performanceentry class PerformanceEntry : public nsISupports, @@ -78,6 +79,11 @@ public: return 0; } + virtual const PerformanceResourceTiming* ToResourceTiming() const + { + return nullptr; + } + protected: nsCOMPtr mParent; nsString mName; diff --git a/dom/base/PerformanceObserver.cpp b/dom/base/PerformanceObserver.cpp new file mode 100644 index 00000000000..21d7741b1bb --- /dev/null +++ b/dom/base/PerformanceObserver.cpp @@ -0,0 +1,154 @@ +/* -*- 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/PerformanceBinding.h" +#include "mozilla/dom/PerformanceEntryBinding.h" +#include "mozilla/dom/PerformanceObserverBinding.h" +#include "mozilla/dom/workers/bindings/Performance.h" +#include "nsPerformance.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_WRAPPERCACHE(PerformanceObserver, + mOwner, + mPerformance, + mCallback) +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(nsPIDOMWindow* aOwner, + PerformanceObserverCallback& aCb) + : mOwner(aOwner) + , mCallback(&aCb) +{ + MOZ_ASSERT(mOwner); + mPerformance = aOwner->GetPerformance(); +} + +PerformanceObserver::PerformanceObserver(WorkerPrivate* aWorkerPrivate, + PerformanceObserverCallback& aCb) + : mCallback(&aCb) +{ + MOZ_ASSERT(aWorkerPrivate); + mPerformance = aWorkerPrivate->GlobalScope()->GetPerformance(); +} + +PerformanceObserver::~PerformanceObserver() +{ +} + +// static +already_AddRefed +PerformanceObserver::Constructor(const GlobalObject& aGlobal, + PerformanceObserverCallback& aCb, + ErrorResult& aRv) +{ + if (NS_IsMainThread()) { + nsCOMPtr ownerWindow = + do_QueryInterface(aGlobal.GetAsSupports()); + if (!ownerWindow) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + MOZ_ASSERT(ownerWindow->IsInnerWindow()); + + nsRefPtr observer = + new PerformanceObserver(ownerWindow, aCb); + return observer.forget(); + } + + JSContext* cx = aGlobal.Context(); + WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx); + MOZ_ASSERT(workerPrivate); + + nsRefPtr observer = + new PerformanceObserver(workerPrivate, aCb); + return observer.forget(); +} + +JSObject* +PerformanceObserver::WrapObject(JSContext* aCx, JS::Handle aGivenProto) +{ + return PerformanceObserverBinding::Wrap(aCx, this, aGivenProto); +} + +void +PerformanceObserver::Notify(PerformanceEntry* aEntry) +{ + MOZ_ASSERT(aEntry); + + nsAutoString entryType; + aEntry->GetEntryType(entryType); + if (!mEntryTypes.Contains(entryType)) { + return; + } + + nsRefPtr list = new PerformanceObserverEntryList(this); + list->AppendEntry(aEntry); + + ErrorResult rv; + mCallback->Call(this, *list, *this, rv); + NS_WARN_IF(rv.Failed()); +} + +static nsString sValidTypeNames[7] = { + NS_LITERAL_STRING("composite"), + NS_LITERAL_STRING("mark"), + NS_LITERAL_STRING("measure"), + NS_LITERAL_STRING("navigation"), + NS_LITERAL_STRING("render"), + NS_LITERAL_STRING("resource"), + NS_LITERAL_STRING("server") +}; + +void +PerformanceObserver::Observe(const PerformanceObserverInit& aOptions, + ErrorResult& aRv) +{ + if (aOptions.mEntryTypes.IsEmpty()) { + aRv.Throw(NS_ERROR_DOM_TYPE_ERR); + return; + } + + nsTArray validEntryTypes; + + for (const nsString& validTypeName : sValidTypeNames) { + if (aOptions.mEntryTypes.Contains(validTypeName) && + !validEntryTypes.Contains(validTypeName)) { + validEntryTypes.AppendElement(validTypeName); + } + } + + if (validEntryTypes.IsEmpty()) { + aRv.Throw(NS_ERROR_DOM_TYPE_ERR); + return; + } + + mEntryTypes = validEntryTypes; + + mPerformance->AddObserver(this); +} + +void +PerformanceObserver::Disconnect() +{ + mPerformance->RemoveObserver(this); +} diff --git a/dom/base/PerformanceObserver.h b/dom/base/PerformanceObserver.h new file mode 100644 index 00000000000..0a7fcd60330 --- /dev/null +++ b/dom/base/PerformanceObserver.h @@ -0,0 +1,76 @@ +/* -*- 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/nsRefPtr.h" +#include "nsString.h" +#include "nsTArray.h" +#include "nsWrapperCache.h" + +class nsPIDOMWindow; +class PerformanceBase; + +namespace mozilla { + +class ErrorResult; + +namespace dom { + +class GlobalObject; +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 + Constructor(const GlobalObject& aGlobal, + PerformanceObserverCallback& aCb, + ErrorResult& aRv); + + PerformanceObserver(nsPIDOMWindow* aOwner, + PerformanceObserverCallback& aCb); + + PerformanceObserver(workers::WorkerPrivate* aWorkerPrivate, + PerformanceObserverCallback& aCb); + + virtual JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + + nsISupports* GetParentObject() const { return mOwner; } + + void Observe(const PerformanceObserverInit& aOptions, + mozilla::ErrorResult& aRv); + + void Disconnect(); + + void Notify(PerformanceEntry* entry); + +private: + ~PerformanceObserver(); + + nsCOMPtr mOwner; + nsRefPtr mCallback; + nsRefPtr mPerformance; + nsTArray mEntryTypes; +}; + +} // namespace dom +} // namespace mozilla + +#endif diff --git a/dom/base/PerformanceObserverEntryList.cpp b/dom/base/PerformanceObserverEntryList.cpp new file mode 100644 index 00000000000..34dee75d206 --- /dev/null +++ b/dom/base/PerformanceObserverEntryList.cpp @@ -0,0 +1,100 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PerformanceObserverEntryList.h" + +#include "mozilla/dom/PerformanceObserverEntryListBinding.h" +#include "nsPerformance.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 aGivenProto) +{ + return PerformanceObserverEntryListBinding::Wrap(aCx, this, aGivenProto); +} + +void +PerformanceObserverEntryList::GetEntries( + const PerformanceEntryFilterOptions& aFilter, + nsTArray>& aRetval) +{ + aRetval.Clear(); + for (const nsRefPtr& entry : mEntries) { + const PerformanceResourceTiming* resourceEntry = + entry->ToResourceTiming(); + if (aFilter.mInitiatorType.WasPassed() && resourceEntry) { + 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>& aRetval) +{ + aRetval.Clear(); + for (const nsRefPtr& entry : mEntries) { + if (entry->GetEntryType().Equals(aEntryType)) { + aRetval.AppendElement(entry); + } + } +} + +void +PerformanceObserverEntryList::GetEntriesByName( + const nsAString& aName, + const Optional& aEntryType, + nsTArray>& aRetval) +{ + aRetval.Clear(); + for (const nsRefPtr& entry : mEntries) { + if (entry->GetName().Equals(aName)) { + aRetval.AppendElement(entry); + } + } +} + +void +PerformanceObserverEntryList::AppendEntry(PerformanceEntry* aEntry) +{ + mEntries.AppendElement(aEntry); +} + diff --git a/dom/base/PerformanceObserverEntryList.h b/dom/base/PerformanceObserverEntryList.h new file mode 100644 index 00000000000..8d098349126 --- /dev/null +++ b/dom/base/PerformanceObserverEntryList.h @@ -0,0 +1,64 @@ +/* -*- 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 class Optional; + +class PerformanceObserverEntryList final : public nsISupports, + public nsWrapperCache +{ + ~PerformanceObserverEntryList(); + +public: + explicit PerformanceObserverEntryList(nsISupports* aOwner) + : mOwner(aOwner) + { + } + + nsISupports* GetParentObject() const + { + return mOwner; + } + + virtual JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(PerformanceObserverEntryList) + + void GetEntries(const PerformanceEntryFilterOptions& aFilter, + nsTArray>& aRetval); + void GetEntriesByType(const nsAString& aEntryType, + nsTArray>& aRetval); + void GetEntriesByName(const nsAString& aName, + const Optional& aEntryType, + nsTArray>& aRetval); + + void AppendEntry(PerformanceEntry* aEntry); + +private: + nsCOMPtr mOwner; + nsTArray> mEntries; +}; + +} // namespace dom +} // namespace mozilla + + +#endif diff --git a/dom/base/PerformanceResourceTiming.h b/dom/base/PerformanceResourceTiming.h index b6d9c26f657..659429f496e 100644 --- a/dom/base/PerformanceResourceTiming.h +++ b/dom/base/PerformanceResourceTiming.h @@ -123,6 +123,11 @@ public: return 0; } + virtual const PerformanceResourceTiming* ToResourceTiming() const override + { + return this; + } + protected: virtual ~PerformanceResourceTiming(); diff --git a/dom/base/moz.build b/dom/base/moz.build index 3d89566a812..5c5b37d6288 100644 --- a/dom/base/moz.build +++ b/dom/base/moz.build @@ -189,6 +189,8 @@ EXPORTS.mozilla.dom += [ 'PerformanceEntry.h', 'PerformanceMark.h', 'PerformanceMeasure.h', + 'PerformanceObserver.h', + 'PerformanceObserverEntryList.h', 'PerformanceResourceTiming.h', 'ProcessGlobal.h', 'ResponsiveImageSelector.h', @@ -328,6 +330,8 @@ UNIFIED_SOURCES += [ 'PerformanceEntry.cpp', 'PerformanceMark.cpp', 'PerformanceMeasure.cpp', + 'PerformanceObserver.cpp', + 'PerformanceObserverEntryList.cpp', 'PerformanceResourceTiming.cpp', 'PostMessageEvent.cpp', 'ProcessGlobal.cpp', diff --git a/dom/base/nsPerformance.cpp b/dom/base/nsPerformance.cpp index 4c745cb0042..2f502add75a 100644 --- a/dom/base/nsPerformance.cpp +++ b/dom/base/nsPerformance.cpp @@ -18,12 +18,14 @@ #include "PerformanceEntry.h" #include "PerformanceMark.h" #include "PerformanceMeasure.h" +#include "PerformanceObserver.h" #include "PerformanceResourceTiming.h" #include "mozilla/ErrorResult.h" #include "mozilla/dom/PerformanceBinding.h" #include "mozilla/dom/PerformanceEntryEvent.h" #include "mozilla/dom/PerformanceTimingBinding.h" #include "mozilla/dom/PerformanceNavigationBinding.h" +#include "mozilla/dom/PerformanceObserverBinding.h" #include "mozilla/Preferences.h" #include "mozilla/IntegerPrintfMacros.h" #include "mozilla/TimeStamp.h" @@ -737,6 +739,25 @@ nsPerformance::IsEnabled(JSContext* aCx, JSObject* aGlobal) return runnable->IsEnabled(); } +/* static */ bool +nsPerformance::IsObserverEnabled(JSContext* aCx, JSObject* aGlobal) +{ + if (NS_IsMainThread()) { + return Preferences::GetBool("dom.enable_performance_observer", false); + } + + WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(workerPrivate); + workerPrivate->AssertIsOnWorkerThread(); + + nsRefPtr runnable = + new PrefEnabledRunnable(workerPrivate, + NS_LITERAL_CSTRING("dom.enable_performance_observer")); + runnable->Dispatch(workerPrivate->GetJSContext()); + + return runnable->IsEnabled(); +} + void nsPerformance::InsertUserEntry(PerformanceEntry* aEntry) { @@ -1028,6 +1049,10 @@ PerformanceBase::InsertUserEntry(PerformanceEntry* aEntry) { mUserEntries.InsertElementSorted(aEntry, PerformanceEntryComparator()); + + NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mObservers, + PerformanceObserver, + Notify, (aEntry)); } void @@ -1051,4 +1076,19 @@ PerformanceBase::InsertResourceEntry(PerformanceEntry* aEntry) // call onresourcetimingbufferfull DispatchBufferFullEvent(); } + NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mObservers, + PerformanceObserver, + Notify, (aEntry)); +} + +void +PerformanceBase::AddObserver(PerformanceObserver* aObserver) +{ + mObservers.AppendElementUnlessExists(aObserver); +} + +void +PerformanceBase::RemoveObserver(PerformanceObserver* aObserver) +{ + mObservers.RemoveElement(aObserver); } diff --git a/dom/base/nsPerformance.h b/dom/base/nsPerformance.h index 5ba926c7d67..7d9f1fe6738 100644 --- a/dom/base/nsPerformance.h +++ b/dom/base/nsPerformance.h @@ -26,6 +26,7 @@ namespace mozilla { class ErrorResult; namespace dom { class PerformanceEntry; + class PerformanceObserver; } // namespace dom } // namespace mozilla @@ -300,6 +301,7 @@ public: explicit PerformanceBase(nsPIDOMWindow* aWindow); typedef mozilla::dom::PerformanceEntry PerformanceEntry; + typedef mozilla::dom::PerformanceObserver PerformanceObserver; void GetEntries(nsTArray>& aRetval); void GetEntriesByType(const nsAString& aEntryType, @@ -321,6 +323,9 @@ public: void SetResourceTimingBufferSize(uint64_t aMaxSize); + void AddObserver(PerformanceObserver* aObserver); + void RemoveObserver(PerformanceObserver* aObserver); + protected: virtual ~PerformanceBase(); @@ -353,6 +358,8 @@ protected: void LogEntry(PerformanceEntry* aEntry, const nsACString& aOwner) const; void TimingNotification(PerformanceEntry* aEntry, const nsACString& aOwner, uint64_t epoch); + nsTObserverArray mObservers; + private: nsTArray> mUserEntries; nsTArray> mResourceEntries; @@ -376,6 +383,8 @@ public: static bool IsEnabled(JSContext* aCx, JSObject* aGlobal); + static bool IsObserverEnabled(JSContext* aCx, JSObject* aGlobal); + nsDOMNavigationTiming* GetDOMTiming() const { return mDOMTiming; diff --git a/dom/base/test/mochitest.ini b/dom/base/test/mochitest.ini index 8920fe9a360..8ef19e5cb21 100644 --- a/dom/base/test/mochitest.ini +++ b/dom/base/test/mochitest.ini @@ -247,6 +247,8 @@ support-files = referrer_testserver.sjs script_postmessages_fileList.js iframe_postMessages.html + test_performance_observer.js + performance_observer.html [test_anonymousContent_api.html] [test_anonymousContent_append_after_reflow.html] @@ -805,6 +807,7 @@ skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' skip-if = buildapp == 'b2g' || toolkit == 'android' || e10s [test_window_define_nonconfigurable.html] [test_root_iframe.html] +[test_performance_observer.html] [test_performance_user_timing.html] [test_bug1126851.html] skip-if = buildapp == 'mulet' || buildapp == 'b2g' diff --git a/dom/base/test/performance_observer.html b/dom/base/test/performance_observer.html new file mode 100644 index 00000000000..b8c715cdac1 --- /dev/null +++ b/dom/base/test/performance_observer.html @@ -0,0 +1,63 @@ + + + + + +Test for performance observer + + + + +
+ + diff --git a/dom/base/test/test_performance_observer.html b/dom/base/test/test_performance_observer.html new file mode 100644 index 00000000000..d368783155a --- /dev/null +++ b/dom/base/test/test_performance_observer.html @@ -0,0 +1,17 @@ + + + +Test for performance observer + + +
+ diff --git a/dom/base/test/test_performance_observer.js b/dom/base/test/test_performance_observer.js new file mode 100644 index 00000000000..8066ced6eb3 --- /dev/null +++ b/dom/base/test/test_performance_observer.js @@ -0,0 +1,236 @@ +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"); + +test(t => { + performance.clearMarks(); + performance.clearMeasures(); + + var observedEntries = []; + var observer = new PerformanceObserver(list => { + list.getEntries().forEach(entry => observedEntries.push(entry)); + }); + observer.observe({entryTypes: ['mark', 'measure']}); + + assert_equals(observedEntries.length, 0, + "User timing entries should never be observed."); + assert_equals(performance.getEntriesByType("mark").length, 0, + "There should be no 'mark' entries."); + assert_equals(performance.getEntriesByType("measure").length, 0, + "There should be no 'measure' entries."); + + performance.mark("test-start"); + performance.mark("test-end"); + performance.measure("test-measure", "test-start", "test-end"); + + assert_equals(observedEntries.length, 3, + "There should be three observed entries."); + + var markEntries = observedEntries.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 = observedEntries.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"); + +test(t => { + performance.clearMarks(); + performance.clearMeasures(); + + var observedEntries = []; + var observer = new PerformanceObserver(list => { + list.getEntries().forEach(entry => observedEntries.push(entry)); + }); + observer.observe({entryTypes: ['mark', 'measure']}); + + observer.disconnect(); + + assert_equals(observedEntries.length, 0, + "User timing entries should be never observed."); + + performance.mark("test-start"); + performance.mark("test-end"); + performance.measure("test-measure", "test-start", "test-end"); + + assert_equals(performance.getEntriesByType("mark").length, 2); + assert_equals(performance.getEntriesByType("measure").length, 1); + + assert_equals(observedEntries.length, 0, + "User timing entries should be never observed after disconnecting observer."); +}, "Nothing should be notified after disconnecting observer"); + +test(t => { + performance.clearMarks(); + performance.clearMeasures(); + + var observedEntryList; + var observer = new PerformanceObserver(list => { + observedEntryList = list; + }); + observer.observe({entryTypes: ['mark']}); + + performance.mark("test"); + assert_array_equals(observedEntryList.getEntries({"entryType": "mark"}), + performance.getEntriesByType("mark"), + "getEntries with entryType filter should return correct results."); + + assert_array_equals(observedEntryList.getEntries({"name": "test"}), + performance.getEntriesByName("test"), + "getEntries with name filter should return correct results."); + + assert_array_equals(observedEntryList.getEntries({"name": "test", + "entryType": "mark"}), + performance.getEntriesByName("test"), + "getEntries with name and entryType filter should return correct results."); + + assert_array_equals(observedEntryList.getEntries({"name": "invalid"}), + [], + "getEntries with non-existent name filter should return an empty array."); + + assert_array_equals(observedEntryList.getEntries({"name": "test", + "entryType": "measure"}), + [], + "getEntries with name filter and non-existent entryType should return an empty array."); + + assert_array_equals(observedEntryList.getEntries({"name": "invalid", + "entryType": "mark"}), + [], + "getEntries with non-existent name and entryType filter should return an empty array."); +}, "Test for PerformanceObserverEntryList.getEntries"); + +test(t => { + performance.clearMarks(); + performance.clearMeasures(); + + var observedEntryList; + var observer = new PerformanceObserver(list => { + observedEntryList = list; + }); + observer.observe({entryTypes: ['mark', 'measure']}); + + performance.mark("test"); + assert_array_equals(observedEntryList.getEntriesByType("mark"), + performance.getEntriesByType("mark")); + + performance.measure("test-measure", "test", "test"); + assert_array_equals(observedEntryList.getEntriesByType("measure"), + performance.getEntriesByType("measure")); +}, "Test for PerformanceObserverEntryList.getEntriesByType"); + +test(t => { + performance.clearMarks(); + performance.clearMeasures(); + + var observedEntryList; + var observer = new PerformanceObserver(list => { + observedEntryList = list; + }); + observer.observe({entryTypes: ['mark', 'measure']}); + + performance.mark("test"); + assert_array_equals(observedEntryList.getEntriesByName("test"), + performance.getEntriesByName("test")); + + performance.measure("test-measure", "test", "test"); + assert_array_equals(observedEntryList.getEntriesByName("test-measure"), + performance.getEntriesByName("test-measure")); +}, "Test for PerformanceObserverEntryList.getEntriesByName"); + +test(t => { + performance.clearMarks(); + performance.clearMeasures(); + + var observedEntries = []; + var observer = new PerformanceObserver(list => { + list.getEntries().forEach(entry => observedEntries.push(entry)); + }); + + observer.observe({entryTypes: ['mark', 'measure']}); + observer.observe({entryTypes: ['mark', 'measure']}); + + performance.mark("test-start"); + performance.mark("test-end"); + performance.measure("test-measure", "test-start", "test-end"); + + assert_equals(observedEntries.length, 3, + "Observed user timing entries should have only three entries."); +}, "Test that invoking observe method twice affects nothing"); + +test(t => { + performance.clearMarks(); + performance.clearMeasures(); + + var observedEntries = []; + var observer = new PerformanceObserver(list => { + list.getEntries().forEach(entry => observedEntries.push(entry)); + }); + + observer.observe({entryTypes: ['mark', 'measure']}); + observer.observe({entryTypes: ['mark']}); + + performance.mark("test-start"); + performance.mark("test-end"); + performance.measure("test-measure", "test-start", "test-end"); + + assert_equals(observedEntries.length, 2, + "Observed user timing entries should have only two entries."); +}, "Test that observing filter is replaced by a new filter"); + +test(t => { + performance.clearMarks(); + performance.clearMeasures(); + + var observedEntries = []; + var observer = new PerformanceObserver(list => { + list.getEntries().forEach(entry => observedEntries.push(entry)); + }); + + observer.observe({entryTypes: ['mark']}); + observer.observe({entryTypes: ['measure']}); + + performance.mark("test-start"); + performance.mark("test-end"); + performance.measure("test-measure", "test-start", "test-end"); + + assert_equals(observedEntries.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/webidl/Performance.webidl b/dom/webidl/Performance.webidl index ea1f0b21c60..64c4bdb7b48 100644 --- a/dom/webidl/Performance.webidl +++ b/dom/webidl/Performance.webidl @@ -71,3 +71,4 @@ partial interface Performance { [Func="nsPerformance::IsEnabled"] void clearMeasures(optional DOMString measureName); }; + diff --git a/dom/webidl/PerformanceObserver.webidl b/dom/webidl/PerformanceObserver.webidl new file mode 100644 index 00000000000..c98bc1d02b7 --- /dev/null +++ b/dom/webidl/PerformanceObserver.webidl @@ -0,0 +1,23 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. + * + * The origin of this IDL file is + * https://w3c.github.io/performance-timeline/#the-performance-observer-interface + */ + +dictionary PerformanceObserverInit { + required sequence entryTypes; +}; + +callback PerformanceObserverCallback = void (PerformanceObserverEntryList entries, PerformanceObserver observer); + +[Func="nsPerformance::IsObserverEnabled", + Constructor(PerformanceObserverCallback callback), + Exposed=(Window,Worker)] +interface PerformanceObserver { + [Throws] + void observe(PerformanceObserverInit options); + void disconnect(); +}; diff --git a/dom/webidl/PerformanceObserverEntryList.webidl b/dom/webidl/PerformanceObserverEntryList.webidl new file mode 100644 index 00000000000..fa17568e833 --- /dev/null +++ b/dom/webidl/PerformanceObserverEntryList.webidl @@ -0,0 +1,24 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. + * + * The origin of this IDL file is + * https://w3c.github.io/performance-timeline/#the-performanceobserverentrylist-interface + */ + +// XXX should be moved into Performance.webidl. +dictionary PerformanceEntryFilterOptions { + DOMString name; + DOMString entryType; + DOMString initiatorType; +}; + +[Func="nsPerformance::IsObserverEnabled", Exposed=(Window,Worker)] +interface PerformanceObserverEntryList { + PerformanceEntryList getEntries(optional PerformanceEntryFilterOptions filter); + PerformanceEntryList getEntriesByType(DOMString entryType); + PerformanceEntryList getEntriesByName(DOMString name, + optional DOMString entryType); +}; + diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build index 302dcc5e040..5a4f1ebbf25 100644 --- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -353,6 +353,8 @@ WEBIDL_FILES = [ 'PerformanceMark.webidl', 'PerformanceMeasure.webidl', 'PerformanceNavigation.webidl', + 'PerformanceObserver.webidl', + 'PerformanceObserverEntryList.webidl', 'PerformanceResourceTiming.webidl', 'PerformanceTiming.webidl', 'PeriodicWave.webidl', diff --git a/dom/workers/test/mochitest.ini b/dom/workers/test/mochitest.ini index faeb0c39be6..a4e216b48c2 100644 --- a/dom/workers/test/mochitest.ini +++ b/dom/workers/test/mochitest.ini @@ -106,8 +106,10 @@ support-files = bug1132924_worker.js empty.html worker_performance_user_timing.js + worker_performance_observer.js sharedworker_performance_user_timing.js referrer.sjs + performance_observer.html [test_404.html] [test_atob.html] @@ -168,6 +170,7 @@ skip-if = buildapp == 'mulet' [test_onLine.html] skip-if = (toolkit == 'gonk' && debug) #debug-only failure [test_performance_user_timing.html] +[test_performance_observer.html] [test_promise.html] [test_promise_resolved_with_string.html] [test_recursion.html] diff --git a/dom/workers/test/performance_observer.html b/dom/workers/test/performance_observer.html new file mode 100644 index 00000000000..613762f521d --- /dev/null +++ b/dom/workers/test/performance_observer.html @@ -0,0 +1,32 @@ + + + + + +Test for performance observer in worker + + +
+ + diff --git a/dom/workers/test/test_performance_observer.html b/dom/workers/test/test_performance_observer.html new file mode 100644 index 00000000000..7efc09c9575 --- /dev/null +++ b/dom/workers/test/test_performance_observer.html @@ -0,0 +1,17 @@ + + + +Test for performance observer in worker + + +
+ diff --git a/dom/workers/test/worker_performance_observer.js b/dom/workers/test/worker_performance_observer.js new file mode 100644 index 00000000000..5b150956ee1 --- /dev/null +++ b/dom/workers/test/worker_performance_observer.js @@ -0,0 +1,4 @@ +importScripts(['/resources/testharness.js']); +importScripts(['../../../dom/base/test/test_performance_observer.js']); + +done();