1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "PerformanceObserver.h"
9 #include "mozilla/dom/Performance.h"
10 #include "mozilla/dom/PerformanceBinding.h"
11 #include "mozilla/dom/PerformanceEntryBinding.h"
12 #include "mozilla/dom/PerformanceObserverBinding.h"
13 #include "mozilla/dom/WorkerScope.h"
14 #include "mozilla/StaticPrefs_dom.h"
15 #include "nsIScriptError.h"
16 #include "nsPIDOMWindow.h"
17 #include "nsQueryObject.h"
19 #include "PerformanceEntry.h"
20 #include "LargestContentfulPaint.h"
21 #include "PerformanceObserverEntryList.h"
23 using namespace mozilla
;
24 using namespace mozilla::dom
;
26 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(PerformanceObserver
)
27 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(PerformanceObserver
)
29 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCallback
)
30 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPerformance
)
31 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner
)
32 NS_IMPL_CYCLE_COLLECTION_UNLINK(mQueuedEntries
)
33 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
34 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
35 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(PerformanceObserver
)
36 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallback
)
37 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPerformance
)
38 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner
)
39 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mQueuedEntries
)
40 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
42 NS_IMPL_CYCLE_COLLECTING_ADDREF(PerformanceObserver
)
43 NS_IMPL_CYCLE_COLLECTING_RELEASE(PerformanceObserver
)
44 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PerformanceObserver
)
45 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
46 NS_INTERFACE_MAP_ENTRY(nsISupports
)
49 PerformanceObserver::PerformanceObserver(nsPIDOMWindowInner
* aOwner
,
50 PerformanceObserverCallback
& aCb
)
51 : mOwner(aOwner
->AsGlobal()),
53 mObserverType(ObserverTypeUndefined
),
56 mPerformance
= aOwner
->GetPerformance();
59 PerformanceObserver::PerformanceObserver(WorkerPrivate
* aWorkerPrivate
,
60 PerformanceObserverCallback
& aCb
)
61 : mOwner(aWorkerPrivate
->GlobalScope()),
63 mObserverType(ObserverTypeUndefined
),
65 MOZ_ASSERT(aWorkerPrivate
->GlobalScope());
66 mPerformance
= aWorkerPrivate
->GlobalScope()->GetPerformance();
69 PerformanceObserver::~PerformanceObserver() {
71 MOZ_ASSERT(!mConnected
);
75 already_AddRefed
<PerformanceObserver
> PerformanceObserver::Constructor(
76 const GlobalObject
& aGlobal
, PerformanceObserverCallback
& aCb
,
78 if (NS_IsMainThread()) {
79 nsCOMPtr
<nsPIDOMWindowInner
> ownerWindow
=
80 do_QueryInterface(aGlobal
.GetAsSupports());
82 aRv
.Throw(NS_ERROR_FAILURE
);
86 RefPtr
<PerformanceObserver
> observer
=
87 new PerformanceObserver(ownerWindow
, aCb
);
88 return observer
.forget();
91 JSContext
* cx
= aGlobal
.Context();
92 WorkerPrivate
* workerPrivate
= GetWorkerPrivateFromContext(cx
);
93 MOZ_ASSERT(workerPrivate
);
95 RefPtr
<PerformanceObserver
> observer
=
96 new PerformanceObserver(workerPrivate
, aCb
);
97 return observer
.forget();
100 JSObject
* PerformanceObserver::WrapObject(JSContext
* aCx
,
101 JS::Handle
<JSObject
*> aGivenProto
) {
102 return PerformanceObserver_Binding::Wrap(aCx
, this, aGivenProto
);
105 void PerformanceObserver::Notify() {
106 if (mQueuedEntries
.IsEmpty()) {
109 RefPtr
<PerformanceObserverEntryList
> list
=
110 new PerformanceObserverEntryList(this, mQueuedEntries
);
112 mQueuedEntries
.Clear();
115 RefPtr
<PerformanceObserverCallback
> callback(mCallback
);
116 callback
->Call(this, *list
, *this, rv
);
117 if (NS_WARN_IF(rv
.Failed())) {
118 rv
.SuppressException();
122 void PerformanceObserver::QueueEntry(PerformanceEntry
* aEntry
) {
124 MOZ_ASSERT(ObservesTypeOfEntry(aEntry
));
126 mQueuedEntries
.AppendElement(aEntry
);
129 static constexpr nsLiteralString kValidEventTimingNames
[2] = {
130 u
"event"_ns
, u
"first-input"_ns
};
133 * Keep this list in alphabetical order.
134 * https://w3c.github.io/performance-timeline/#supportedentrytypes-attribute
136 static constexpr nsLiteralString kValidTypeNames
[5] = {
137 u
"mark"_ns
, u
"measure"_ns
, u
"navigation"_ns
, u
"paint"_ns
, u
"resource"_ns
,
140 void PerformanceObserver::Observe(const PerformanceObserverInit
& aOptions
,
142 const Optional
<Sequence
<nsString
>>& maybeEntryTypes
= aOptions
.mEntryTypes
;
143 const Optional
<nsString
>& maybeType
= aOptions
.mType
;
144 const Optional
<bool>& maybeBuffered
= aOptions
.mBuffered
;
146 if (!mPerformance
|| !mOwner
) {
147 aRv
.Throw(NS_ERROR_FAILURE
);
151 if (!maybeEntryTypes
.WasPassed() && !maybeType
.WasPassed()) {
152 /* Per spec (3.3.1.2), this should be a syntax error. */
153 aRv
.ThrowTypeError("Can't call observe without `type` or `entryTypes`");
157 if (maybeEntryTypes
.WasPassed() &&
158 (maybeType
.WasPassed() || maybeBuffered
.WasPassed())) {
159 /* Per spec (3.3.1.3), this, too, should be a syntax error. */
160 aRv
.ThrowTypeError("Can't call observe with both `type` and `entryTypes`");
165 if (mObserverType
== ObserverTypeUndefined
) {
166 if (maybeEntryTypes
.WasPassed()) {
167 mObserverType
= ObserverTypeMultiple
;
169 mObserverType
= ObserverTypeSingle
;
174 if (mObserverType
== ObserverTypeSingle
&& maybeEntryTypes
.WasPassed()) {
175 aRv
.Throw(NS_ERROR_DOM_INVALID_MODIFICATION_ERR
);
179 if (mObserverType
== ObserverTypeMultiple
&& maybeType
.WasPassed()) {
180 aRv
.Throw(NS_ERROR_DOM_INVALID_MODIFICATION_ERR
);
184 bool needQueueNotificationObserverTask
= false;
186 if (mObserverType
== ObserverTypeMultiple
) {
187 const Sequence
<nsString
>& entryTypes
= maybeEntryTypes
.Value();
189 if (entryTypes
.IsEmpty()) {
194 nsTArray
<nsString
> validEntryTypes
;
196 if (StaticPrefs::dom_enable_event_timing()) {
197 for (const nsLiteralString
& name
: kValidEventTimingNames
) {
198 if (entryTypes
.Contains(name
) && !validEntryTypes
.Contains(name
)) {
199 validEntryTypes
.AppendElement(name
);
203 if (StaticPrefs::dom_enable_largest_contentful_paint()) {
204 if (entryTypes
.Contains(kLargestContentfulPaintName
) &&
205 !validEntryTypes
.Contains(kLargestContentfulPaintName
)) {
206 validEntryTypes
.AppendElement(kLargestContentfulPaintName
);
209 for (const nsLiteralString
& name
: kValidTypeNames
) {
210 if (entryTypes
.Contains(name
) && !validEntryTypes
.Contains(name
)) {
211 validEntryTypes
.AppendElement(name
);
215 nsAutoString invalidTypesJoined
;
216 bool addComma
= false;
217 for (const auto& type
: entryTypes
) {
218 if (!validEntryTypes
.Contains
<nsString
>(type
)) {
220 invalidTypesJoined
.AppendLiteral(", ");
223 invalidTypesJoined
.Append(type
);
227 if (!invalidTypesJoined
.IsEmpty()) {
228 AutoTArray
<nsString
, 1> params
= {invalidTypesJoined
};
229 mOwner
->ReportToConsole(nsIScriptError::warningFlag
, "DOM"_ns
,
230 nsContentUtils::eDOM_PROPERTIES
,
231 "UnsupportedEntryTypesIgnored"_ns
, params
);
232 // (we don't return because we're ignoring and we keep going)
236 if (validEntryTypes
.IsEmpty()) {
237 mOwner
->ReportToConsole(nsIScriptError::warningFlag
, "DOM"_ns
,
238 nsContentUtils::eDOM_PROPERTIES
,
239 "AllEntryTypesIgnored"_ns
);
244 * Registered or not, we clear out the list of options, and start fresh
245 * with the one that we are using here. (3.3.1.5.4,5)
248 mOptions
.AppendElement(aOptions
);
251 MOZ_ASSERT(mObserverType
== ObserverTypeSingle
);
252 bool typeValid
= false;
253 nsString type
= maybeType
.Value();
256 if (StaticPrefs::dom_enable_event_timing()) {
257 for (const nsLiteralString
& name
: kValidEventTimingNames
) {
264 for (const nsLiteralString
& name
: kValidTypeNames
) {
271 if (StaticPrefs::dom_enable_largest_contentful_paint()) {
272 if (type
== kLargestContentfulPaintName
) {
278 AutoTArray
<nsString
, 1> params
= {type
};
279 mOwner
->ReportToConsole(nsIScriptError::warningFlag
, "DOM"_ns
,
280 nsContentUtils::eDOM_PROPERTIES
,
281 "UnsupportedEntryTypesIgnored"_ns
, params
);
285 /* 3.3.1.6.4, 3.3.1.6.4 */
286 bool didUpdateOptionsList
= false;
287 nsTArray
<PerformanceObserverInit
> updatedOptionsList
;
288 for (auto& option
: mOptions
) {
289 if (option
.mType
.WasPassed() && option
.mType
.Value() == type
) {
290 updatedOptionsList
.AppendElement(aOptions
);
291 didUpdateOptionsList
= true;
293 updatedOptionsList
.AppendElement(option
);
296 if (!didUpdateOptionsList
) {
297 updatedOptionsList
.AppendElement(aOptions
);
299 mOptions
= std::move(updatedOptionsList
);
302 if (maybeBuffered
.WasPassed() && maybeBuffered
.Value()) {
303 nsTArray
<RefPtr
<PerformanceEntry
>> existingEntries
;
304 mPerformance
->GetEntriesByTypeForObserver(type
, existingEntries
);
305 if (!existingEntries
.IsEmpty()) {
306 mQueuedEntries
.AppendElements(existingEntries
);
307 needQueueNotificationObserverTask
= true;
311 /* Add ourselves to the list of registered performance
312 * observers, if necessary. (3.3.1.5.4,5; 3.3.1.6.4)
314 mPerformance
->AddObserver(this);
316 if (needQueueNotificationObserverTask
) {
317 mPerformance
->QueueNotificationObserversTask();
322 void PerformanceObserver::GetSupportedEntryTypes(
323 const GlobalObject
& aGlobal
, JS::MutableHandle
<JSObject
*> aObject
) {
324 nsTArray
<nsString
> validTypes
;
325 JS::Rooted
<JS::Value
> val(aGlobal
.Context());
327 if (StaticPrefs::dom_enable_event_timing()) {
328 for (const nsLiteralString
& name
: kValidEventTimingNames
) {
329 validTypes
.AppendElement(name
);
333 if (StaticPrefs::dom_enable_largest_contentful_paint()) {
334 validTypes
.AppendElement(u
"largest-contentful-paint"_ns
);
336 for (const nsLiteralString
& name
: kValidTypeNames
) {
337 validTypes
.AppendElement(name
);
340 if (!ToJSValue(aGlobal
.Context(), validTypes
, &val
)) {
342 * If this conversion fails, we don't set a result.
343 * The spec does not allow us to throw an exception.
347 aObject
.set(&val
.toObject());
350 bool PerformanceObserver::ObservesTypeOfEntry(PerformanceEntry
* aEntry
) {
351 for (auto& option
: mOptions
) {
352 if (aEntry
->ShouldAddEntryToObserverBuffer(option
)) {
359 void PerformanceObserver::Disconnect() {
361 MOZ_ASSERT(mPerformance
);
362 mPerformance
->RemoveObserver(this);
368 void PerformanceObserver::TakeRecords(
369 nsTArray
<RefPtr
<PerformanceEntry
>>& aRetval
) {
370 MOZ_ASSERT(aRetval
.IsEmpty());
371 aRetval
= std::move(mQueuedEntries
);