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 const char UnsupportedEntryTypesIgnoredMsgId
[] = "UnsupportedEntryTypesIgnored";
50 const char AllEntryTypesIgnoredMsgId
[] = "AllEntryTypesIgnored";
52 PerformanceObserver::PerformanceObserver(nsPIDOMWindowInner
* aOwner
,
53 PerformanceObserverCallback
& aCb
)
56 mObserverType(ObserverTypeUndefined
),
59 mPerformance
= aOwner
->GetPerformance();
62 PerformanceObserver::PerformanceObserver(WorkerPrivate
* aWorkerPrivate
,
63 PerformanceObserverCallback
& aCb
)
64 : mCallback(&aCb
), mObserverType(ObserverTypeUndefined
), mConnected(false) {
65 MOZ_ASSERT(aWorkerPrivate
);
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::ReportUnsupportedTypesErrorToConsole(
141 bool aIsMainThread
, const char* msgId
, const nsString
& aInvalidTypes
) {
142 if (!aIsMainThread
) {
143 nsTArray
<nsString
> params
;
144 params
.AppendElement(aInvalidTypes
);
145 WorkerPrivate::ReportErrorToConsole(msgId
, params
);
147 nsCOMPtr
<nsPIDOMWindowInner
> ownerWindow
= do_QueryInterface(mOwner
);
148 Document
* document
= ownerWindow
->GetExtantDoc();
149 AutoTArray
<nsString
, 1> params
= {aInvalidTypes
};
150 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag
, "DOM"_ns
,
151 document
, nsContentUtils::eDOM_PROPERTIES
,
156 void PerformanceObserver::Observe(const PerformanceObserverInit
& aOptions
,
158 const Optional
<Sequence
<nsString
>>& maybeEntryTypes
= aOptions
.mEntryTypes
;
159 const Optional
<nsString
>& maybeType
= aOptions
.mType
;
160 const Optional
<bool>& maybeBuffered
= aOptions
.mBuffered
;
163 aRv
.Throw(NS_ERROR_FAILURE
);
167 if (!maybeEntryTypes
.WasPassed() && !maybeType
.WasPassed()) {
168 /* Per spec (3.3.1.2), this should be a syntax error. */
169 aRv
.ThrowTypeError("Can't call observe without `type` or `entryTypes`");
173 if (maybeEntryTypes
.WasPassed() &&
174 (maybeType
.WasPassed() || maybeBuffered
.WasPassed())) {
175 /* Per spec (3.3.1.3), this, too, should be a syntax error. */
176 aRv
.ThrowTypeError("Can't call observe with both `type` and `entryTypes`");
181 if (mObserverType
== ObserverTypeUndefined
) {
182 if (maybeEntryTypes
.WasPassed()) {
183 mObserverType
= ObserverTypeMultiple
;
185 mObserverType
= ObserverTypeSingle
;
190 if (mObserverType
== ObserverTypeSingle
&& maybeEntryTypes
.WasPassed()) {
191 aRv
.Throw(NS_ERROR_DOM_INVALID_MODIFICATION_ERR
);
195 if (mObserverType
== ObserverTypeMultiple
&& maybeType
.WasPassed()) {
196 aRv
.Throw(NS_ERROR_DOM_INVALID_MODIFICATION_ERR
);
200 bool needQueueNotificationObserverTask
= false;
202 if (mObserverType
== ObserverTypeMultiple
) {
203 const Sequence
<nsString
>& entryTypes
= maybeEntryTypes
.Value();
205 if (entryTypes
.IsEmpty()) {
210 nsTArray
<nsString
> validEntryTypes
;
212 if (StaticPrefs::dom_enable_event_timing()) {
213 for (const nsLiteralString
& name
: kValidEventTimingNames
) {
214 if (entryTypes
.Contains(name
) && !validEntryTypes
.Contains(name
)) {
215 validEntryTypes
.AppendElement(name
);
219 if (StaticPrefs::dom_enable_largest_contentful_paint()) {
220 if (entryTypes
.Contains(kLargestContentfulPaintName
) &&
221 !validEntryTypes
.Contains(kLargestContentfulPaintName
)) {
222 validEntryTypes
.AppendElement(kLargestContentfulPaintName
);
225 for (const nsLiteralString
& name
: kValidTypeNames
) {
226 if (entryTypes
.Contains(name
) && !validEntryTypes
.Contains(name
)) {
227 validEntryTypes
.AppendElement(name
);
231 nsAutoString invalidTypesJoined
;
232 bool addComma
= false;
233 for (const auto& type
: entryTypes
) {
234 if (!validEntryTypes
.Contains
<nsString
>(type
)) {
236 invalidTypesJoined
.AppendLiteral(", ");
239 invalidTypesJoined
.Append(type
);
243 if (!invalidTypesJoined
.IsEmpty()) {
244 ReportUnsupportedTypesErrorToConsole(NS_IsMainThread(),
245 UnsupportedEntryTypesIgnoredMsgId
,
250 if (validEntryTypes
.IsEmpty()) {
251 nsString errorString
;
252 ReportUnsupportedTypesErrorToConsole(
253 NS_IsMainThread(), AllEntryTypesIgnoredMsgId
, errorString
);
258 * Registered or not, we clear out the list of options, and start fresh
259 * with the one that we are using here. (3.3.1.5.4,5)
262 mOptions
.AppendElement(aOptions
);
265 MOZ_ASSERT(mObserverType
== ObserverTypeSingle
);
266 bool typeValid
= false;
267 nsString type
= maybeType
.Value();
270 if (StaticPrefs::dom_enable_event_timing()) {
271 for (const nsLiteralString
& name
: kValidEventTimingNames
) {
278 for (const nsLiteralString
& name
: kValidTypeNames
) {
285 if (StaticPrefs::dom_enable_largest_contentful_paint()) {
286 if (type
== kLargestContentfulPaintName
) {
292 ReportUnsupportedTypesErrorToConsole(
293 NS_IsMainThread(), UnsupportedEntryTypesIgnoredMsgId
, type
);
297 /* 3.3.1.6.4, 3.3.1.6.4 */
298 bool didUpdateOptionsList
= false;
299 nsTArray
<PerformanceObserverInit
> updatedOptionsList
;
300 for (auto& option
: mOptions
) {
301 if (option
.mType
.WasPassed() && option
.mType
.Value() == type
) {
302 updatedOptionsList
.AppendElement(aOptions
);
303 didUpdateOptionsList
= true;
305 updatedOptionsList
.AppendElement(option
);
308 if (!didUpdateOptionsList
) {
309 updatedOptionsList
.AppendElement(aOptions
);
311 mOptions
= std::move(updatedOptionsList
);
314 if (maybeBuffered
.WasPassed() && maybeBuffered
.Value()) {
315 nsTArray
<RefPtr
<PerformanceEntry
>> existingEntries
;
316 mPerformance
->GetEntriesByTypeForObserver(type
, existingEntries
);
317 if (!existingEntries
.IsEmpty()) {
318 mQueuedEntries
.AppendElements(existingEntries
);
319 needQueueNotificationObserverTask
= true;
323 /* Add ourselves to the list of registered performance
324 * observers, if necessary. (3.3.1.5.4,5; 3.3.1.6.4)
326 mPerformance
->AddObserver(this);
328 if (needQueueNotificationObserverTask
) {
329 mPerformance
->QueueNotificationObserversTask();
334 void PerformanceObserver::GetSupportedEntryTypes(
335 const GlobalObject
& aGlobal
, JS::MutableHandle
<JSObject
*> aObject
) {
336 nsTArray
<nsString
> validTypes
;
337 JS::Rooted
<JS::Value
> val(aGlobal
.Context());
339 if (StaticPrefs::dom_enable_event_timing()) {
340 for (const nsLiteralString
& name
: kValidEventTimingNames
) {
341 validTypes
.AppendElement(name
);
345 if (StaticPrefs::dom_enable_largest_contentful_paint()) {
346 validTypes
.AppendElement(u
"largest-contentful-paint"_ns
);
348 for (const nsLiteralString
& name
: kValidTypeNames
) {
349 validTypes
.AppendElement(name
);
352 if (!ToJSValue(aGlobal
.Context(), validTypes
, &val
)) {
354 * If this conversion fails, we don't set a result.
355 * The spec does not allow us to throw an exception.
359 aObject
.set(&val
.toObject());
362 bool PerformanceObserver::ObservesTypeOfEntry(PerformanceEntry
* aEntry
) {
363 for (auto& option
: mOptions
) {
364 if (aEntry
->ShouldAddEntryToObserverBuffer(option
)) {
371 void PerformanceObserver::Disconnect() {
373 MOZ_ASSERT(mPerformance
);
374 mPerformance
->RemoveObserver(this);
380 void PerformanceObserver::TakeRecords(
381 nsTArray
<RefPtr
<PerformanceEntry
>>& aRetval
) {
382 MOZ_ASSERT(aRetval
.IsEmpty());
383 aRetval
= std::move(mQueuedEntries
);