Bug 1936278 - Prevent search mode chiclet from being dismissed when clicking in page...
[gecko.git] / dom / performance / PerformanceObserver.cpp
blob9674af67e5fe8b0e1514d2fb236430bf1f86fe20
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 PerformanceObserver::PerformanceObserver(nsPIDOMWindowInner* aOwner,
50 PerformanceObserverCallback& aCb)
51 : mOwner(aOwner->AsGlobal()),
52 mCallback(&aCb),
53 mObserverType(ObserverTypeUndefined),
54 mConnected(false) {
55 MOZ_ASSERT(mOwner);
56 mPerformance = aOwner->GetPerformance();
59 PerformanceObserver::PerformanceObserver(WorkerPrivate* aWorkerPrivate,
60 PerformanceObserverCallback& aCb)
61 : mOwner(aWorkerPrivate->GlobalScope()),
62 mCallback(&aCb),
63 mObserverType(ObserverTypeUndefined),
64 mConnected(false) {
65 MOZ_ASSERT(aWorkerPrivate->GlobalScope());
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::Observe(const PerformanceObserverInit& aOptions,
141 ErrorResult& aRv) {
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);
148 return;
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`");
154 return;
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`");
161 return;
164 /* 3.3.1.4.1 */
165 if (mObserverType == ObserverTypeUndefined) {
166 if (maybeEntryTypes.WasPassed()) {
167 mObserverType = ObserverTypeMultiple;
168 } else {
169 mObserverType = ObserverTypeSingle;
173 /* 3.3.1.4.2 */
174 if (mObserverType == ObserverTypeSingle && maybeEntryTypes.WasPassed()) {
175 aRv.Throw(NS_ERROR_DOM_INVALID_MODIFICATION_ERR);
176 return;
178 /* 3.3.1.4.3 */
179 if (mObserverType == ObserverTypeMultiple && maybeType.WasPassed()) {
180 aRv.Throw(NS_ERROR_DOM_INVALID_MODIFICATION_ERR);
181 return;
184 bool needQueueNotificationObserverTask = false;
185 /* 3.3.1.5 */
186 if (mObserverType == ObserverTypeMultiple) {
187 const Sequence<nsString>& entryTypes = maybeEntryTypes.Value();
189 if (entryTypes.IsEmpty()) {
190 return;
193 /* 3.3.1.5.2 */
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)) {
219 if (addComma) {
220 invalidTypesJoined.AppendLiteral(", ");
222 addComma = true;
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)
235 /* 3.3.1.5.3 */
236 if (validEntryTypes.IsEmpty()) {
237 mOwner->ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns,
238 nsContentUtils::eDOM_PROPERTIES,
239 "AllEntryTypesIgnored"_ns);
240 return;
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)
247 mOptions.Clear();
248 mOptions.AppendElement(aOptions);
250 } else {
251 MOZ_ASSERT(mObserverType == ObserverTypeSingle);
252 bool typeValid = false;
253 nsString type = maybeType.Value();
255 /* 3.3.1.6.2 */
256 if (StaticPrefs::dom_enable_event_timing()) {
257 for (const nsLiteralString& name : kValidEventTimingNames) {
258 if (type == name) {
259 typeValid = true;
260 break;
264 for (const nsLiteralString& name : kValidTypeNames) {
265 if (type == name) {
266 typeValid = true;
267 break;
271 if (StaticPrefs::dom_enable_largest_contentful_paint()) {
272 if (type == kLargestContentfulPaintName) {
273 typeValid = true;
277 if (!typeValid) {
278 AutoTArray<nsString, 1> params = {type};
279 mOwner->ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns,
280 nsContentUtils::eDOM_PROPERTIES,
281 "UnsupportedEntryTypesIgnored"_ns, params);
282 return;
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;
292 } else {
293 updatedOptionsList.AppendElement(option);
296 if (!didUpdateOptionsList) {
297 updatedOptionsList.AppendElement(aOptions);
299 mOptions = std::move(updatedOptionsList);
301 /* 3.3.1.6.5 */
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();
319 mConnected = true;
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.
345 return;
347 aObject.set(&val.toObject());
350 bool PerformanceObserver::ObservesTypeOfEntry(PerformanceEntry* aEntry) {
351 for (auto& option : mOptions) {
352 if (aEntry->ShouldAddEntryToObserverBuffer(option)) {
353 return true;
356 return false;
359 void PerformanceObserver::Disconnect() {
360 if (mConnected) {
361 MOZ_ASSERT(mPerformance);
362 mPerformance->RemoveObserver(this);
363 mOptions.Clear();
364 mConnected = false;
368 void PerformanceObserver::TakeRecords(
369 nsTArray<RefPtr<PerformanceEntry>>& aRetval) {
370 MOZ_ASSERT(aRetval.IsEmpty());
371 aRetval = std::move(mQueuedEntries);