no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / dom / performance / PerformanceObserver.cpp
blobd5d1725c577364d242f76b000261e05700c7c270
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"
18 #include "nsString.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)
28 tmp->Disconnect();
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)
47 NS_INTERFACE_MAP_END
49 const char UnsupportedEntryTypesIgnoredMsgId[] = "UnsupportedEntryTypesIgnored";
50 const char AllEntryTypesIgnoredMsgId[] = "AllEntryTypesIgnored";
52 PerformanceObserver::PerformanceObserver(nsPIDOMWindowInner* aOwner,
53 PerformanceObserverCallback& aCb)
54 : mOwner(aOwner),
55 mCallback(&aCb),
56 mObserverType(ObserverTypeUndefined),
57 mConnected(false) {
58 MOZ_ASSERT(mOwner);
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() {
70 Disconnect();
71 MOZ_ASSERT(!mConnected);
74 // static
75 already_AddRefed<PerformanceObserver> PerformanceObserver::Constructor(
76 const GlobalObject& aGlobal, PerformanceObserverCallback& aCb,
77 ErrorResult& aRv) {
78 if (NS_IsMainThread()) {
79 nsCOMPtr<nsPIDOMWindowInner> ownerWindow =
80 do_QueryInterface(aGlobal.GetAsSupports());
81 if (!ownerWindow) {
82 aRv.Throw(NS_ERROR_FAILURE);
83 return nullptr;
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()) {
107 return;
109 RefPtr<PerformanceObserverEntryList> list =
110 new PerformanceObserverEntryList(this, mQueuedEntries);
112 mQueuedEntries.Clear();
114 ErrorResult rv;
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) {
123 MOZ_ASSERT(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);
146 } else {
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,
152 msgId, params);
156 void PerformanceObserver::Observe(const PerformanceObserverInit& aOptions,
157 ErrorResult& aRv) {
158 const Optional<Sequence<nsString>>& maybeEntryTypes = aOptions.mEntryTypes;
159 const Optional<nsString>& maybeType = aOptions.mType;
160 const Optional<bool>& maybeBuffered = aOptions.mBuffered;
162 if (!mPerformance) {
163 aRv.Throw(NS_ERROR_FAILURE);
164 return;
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`");
170 return;
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`");
177 return;
180 /* 3.3.1.4.1 */
181 if (mObserverType == ObserverTypeUndefined) {
182 if (maybeEntryTypes.WasPassed()) {
183 mObserverType = ObserverTypeMultiple;
184 } else {
185 mObserverType = ObserverTypeSingle;
189 /* 3.3.1.4.2 */
190 if (mObserverType == ObserverTypeSingle && maybeEntryTypes.WasPassed()) {
191 aRv.Throw(NS_ERROR_DOM_INVALID_MODIFICATION_ERR);
192 return;
194 /* 3.3.1.4.3 */
195 if (mObserverType == ObserverTypeMultiple && maybeType.WasPassed()) {
196 aRv.Throw(NS_ERROR_DOM_INVALID_MODIFICATION_ERR);
197 return;
200 bool needQueueNotificationObserverTask = false;
201 /* 3.3.1.5 */
202 if (mObserverType == ObserverTypeMultiple) {
203 const Sequence<nsString>& entryTypes = maybeEntryTypes.Value();
205 if (entryTypes.IsEmpty()) {
206 return;
209 /* 3.3.1.5.2 */
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)) {
235 if (addComma) {
236 invalidTypesJoined.AppendLiteral(", ");
238 addComma = true;
239 invalidTypesJoined.Append(type);
243 if (!invalidTypesJoined.IsEmpty()) {
244 ReportUnsupportedTypesErrorToConsole(NS_IsMainThread(),
245 UnsupportedEntryTypesIgnoredMsgId,
246 invalidTypesJoined);
249 /* 3.3.1.5.3 */
250 if (validEntryTypes.IsEmpty()) {
251 nsString errorString;
252 ReportUnsupportedTypesErrorToConsole(
253 NS_IsMainThread(), AllEntryTypesIgnoredMsgId, errorString);
254 return;
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)
261 mOptions.Clear();
262 mOptions.AppendElement(aOptions);
264 } else {
265 MOZ_ASSERT(mObserverType == ObserverTypeSingle);
266 bool typeValid = false;
267 nsString type = maybeType.Value();
269 /* 3.3.1.6.2 */
270 if (StaticPrefs::dom_enable_event_timing()) {
271 for (const nsLiteralString& name : kValidEventTimingNames) {
272 if (type == name) {
273 typeValid = true;
274 break;
278 for (const nsLiteralString& name : kValidTypeNames) {
279 if (type == name) {
280 typeValid = true;
281 break;
285 if (StaticPrefs::dom_enable_largest_contentful_paint()) {
286 if (type == kLargestContentfulPaintName) {
287 typeValid = true;
291 if (!typeValid) {
292 ReportUnsupportedTypesErrorToConsole(
293 NS_IsMainThread(), UnsupportedEntryTypesIgnoredMsgId, type);
294 return;
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;
304 } else {
305 updatedOptionsList.AppendElement(option);
308 if (!didUpdateOptionsList) {
309 updatedOptionsList.AppendElement(aOptions);
311 mOptions = std::move(updatedOptionsList);
313 /* 3.3.1.6.5 */
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();
331 mConnected = true;
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.
357 return;
359 aObject.set(&val.toObject());
362 bool PerformanceObserver::ObservesTypeOfEntry(PerformanceEntry* aEntry) {
363 for (auto& option : mOptions) {
364 if (aEntry->ShouldAddEntryToObserverBuffer(option)) {
365 return true;
368 return false;
371 void PerformanceObserver::Disconnect() {
372 if (mConnected) {
373 MOZ_ASSERT(mPerformance);
374 mPerformance->RemoveObserver(this);
375 mOptions.Clear();
376 mConnected = false;
380 void PerformanceObserver::TakeRecords(
381 nsTArray<RefPtr<PerformanceEntry>>& aRetval) {
382 MOZ_ASSERT(aRetval.IsEmpty());
383 aRetval = std::move(mQueuedEntries);