Line data Source code
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/. */
6 :
7 : #include "Performance.h"
8 :
9 : #include "GeckoProfiler.h"
10 : #include "nsRFPService.h"
11 : #include "ProfilerMarkerPayload.h"
12 : #include "PerformanceEntry.h"
13 : #include "PerformanceMainThread.h"
14 : #include "PerformanceMark.h"
15 : #include "PerformanceMeasure.h"
16 : #include "PerformanceObserver.h"
17 : #include "PerformanceResourceTiming.h"
18 : #include "PerformanceService.h"
19 : #include "PerformanceWorker.h"
20 : #include "mozilla/ErrorResult.h"
21 : #include "mozilla/dom/PerformanceBinding.h"
22 : #include "mozilla/dom/PerformanceEntryEvent.h"
23 : #include "mozilla/dom/PerformanceNavigationBinding.h"
24 : #include "mozilla/dom/PerformanceObserverBinding.h"
25 : #include "mozilla/IntegerPrintfMacros.h"
26 : #include "mozilla/Preferences.h"
27 : #include "WorkerPrivate.h"
28 : #include "WorkerRunnable.h"
29 :
30 : #ifdef MOZ_WIDGET_GONK
31 : #define PERFLOG(msg, ...) __android_log_print(ANDROID_LOG_INFO, "PerformanceTiming", msg, ##__VA_ARGS__)
32 : #else
33 : #define PERFLOG(msg, ...) printf_stderr(msg, ##__VA_ARGS__)
34 : #endif
35 :
36 : namespace mozilla {
37 : namespace dom {
38 :
39 : using namespace workers;
40 :
41 : namespace {
42 :
43 : // Helper classes
44 : class MOZ_STACK_CLASS PerformanceEntryComparator final
45 : {
46 : public:
47 0 : bool Equals(const PerformanceEntry* aElem1,
48 : const PerformanceEntry* aElem2) const
49 : {
50 0 : MOZ_ASSERT(aElem1 && aElem2,
51 : "Trying to compare null performance entries");
52 0 : return aElem1->StartTime() == aElem2->StartTime();
53 : }
54 :
55 1 : bool LessThan(const PerformanceEntry* aElem1,
56 : const PerformanceEntry* aElem2) const
57 : {
58 1 : MOZ_ASSERT(aElem1 && aElem2,
59 : "Trying to compare null performance entries");
60 1 : return aElem1->StartTime() < aElem2->StartTime();
61 : }
62 : };
63 :
64 6 : class PrefEnabledRunnable final
65 : : public WorkerCheckAPIExposureOnMainThreadRunnable
66 : {
67 : public:
68 2 : PrefEnabledRunnable(WorkerPrivate* aWorkerPrivate,
69 : const nsCString& aPrefName)
70 2 : : WorkerCheckAPIExposureOnMainThreadRunnable(aWorkerPrivate)
71 : , mEnabled(false)
72 2 : , mPrefName(aPrefName)
73 2 : { }
74 :
75 2 : bool MainThreadRun() override
76 : {
77 2 : MOZ_ASSERT(NS_IsMainThread());
78 2 : mEnabled = Preferences::GetBool(mPrefName.get(), false);
79 2 : return true;
80 : }
81 :
82 2 : bool IsEnabled() const
83 : {
84 2 : return mEnabled;
85 : }
86 :
87 : private:
88 : bool mEnabled;
89 : nsCString mPrefName;
90 : };
91 :
92 : } // anonymous namespace
93 :
94 0 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(Performance)
95 0 : NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
96 :
97 7 : NS_IMPL_CYCLE_COLLECTION_INHERITED(Performance,
98 : DOMEventTargetHelper,
99 : mUserEntries,
100 : mResourceEntries);
101 :
102 19 : NS_IMPL_ADDREF_INHERITED(Performance, DOMEventTargetHelper)
103 2 : NS_IMPL_RELEASE_INHERITED(Performance, DOMEventTargetHelper)
104 :
105 : /* static */ already_AddRefed<Performance>
106 7 : Performance::CreateForMainThread(nsPIDOMWindowInner* aWindow,
107 : nsDOMNavigationTiming* aDOMTiming,
108 : nsITimedChannel* aChannel)
109 : {
110 7 : MOZ_ASSERT(NS_IsMainThread());
111 :
112 : RefPtr<Performance> performance =
113 14 : new PerformanceMainThread(aWindow, aDOMTiming, aChannel);
114 14 : return performance.forget();
115 : }
116 :
117 : /* static */ already_AddRefed<Performance>
118 0 : Performance::CreateForWorker(workers::WorkerPrivate* aWorkerPrivate)
119 : {
120 0 : MOZ_ASSERT(aWorkerPrivate);
121 0 : aWorkerPrivate->AssertIsOnWorkerThread();
122 :
123 0 : RefPtr<Performance> performance = new PerformanceWorker(aWorkerPrivate);
124 0 : return performance.forget();
125 : }
126 :
127 0 : Performance::Performance()
128 : : mResourceTimingBufferSize(kDefaultResourceTimingBufferSize)
129 0 : , mPendingNotificationObserversTask(false)
130 : {
131 0 : MOZ_ASSERT(!NS_IsMainThread());
132 0 : }
133 :
134 7 : Performance::Performance(nsPIDOMWindowInner* aWindow)
135 : : DOMEventTargetHelper(aWindow)
136 : , mResourceTimingBufferSize(kDefaultResourceTimingBufferSize)
137 7 : , mPendingNotificationObserversTask(false)
138 : {
139 7 : MOZ_ASSERT(NS_IsMainThread());
140 7 : }
141 :
142 0 : Performance::~Performance()
143 0 : {}
144 :
145 : DOMHighResTimeStamp
146 58 : Performance::Now() const
147 : {
148 58 : TimeDuration duration = TimeStamp::Now() - CreationTimeStamp();
149 58 : return RoundTime(duration.ToMilliseconds());
150 : }
151 :
152 : DOMHighResTimeStamp
153 0 : Performance::TimeOrigin()
154 : {
155 0 : if (!mPerformanceService) {
156 0 : mPerformanceService = PerformanceService::GetOrCreate();
157 : }
158 :
159 0 : MOZ_ASSERT(mPerformanceService);
160 0 : return mPerformanceService->TimeOrigin(CreationTimeStamp());
161 : }
162 :
163 : JSObject*
164 7 : Performance::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
165 : {
166 7 : return PerformanceBinding::Wrap(aCx, this, aGivenProto);
167 : }
168 :
169 : void
170 0 : Performance::GetEntries(nsTArray<RefPtr<PerformanceEntry>>& aRetval)
171 : {
172 : // We return an empty list when 'privacy.resistFingerprinting' is on.
173 0 : if (nsContentUtils::ShouldResistFingerprinting()) {
174 0 : aRetval.Clear();
175 0 : return;
176 : }
177 :
178 0 : aRetval = mResourceEntries;
179 0 : aRetval.AppendElements(mUserEntries);
180 0 : aRetval.Sort(PerformanceEntryComparator());
181 : }
182 :
183 : void
184 0 : Performance::GetEntriesByType(const nsAString& aEntryType,
185 : nsTArray<RefPtr<PerformanceEntry>>& aRetval)
186 : {
187 : // We return an empty list when 'privacy.resistFingerprinting' is on.
188 0 : if (nsContentUtils::ShouldResistFingerprinting()) {
189 0 : aRetval.Clear();
190 0 : return;
191 : }
192 :
193 0 : if (aEntryType.EqualsLiteral("resource")) {
194 0 : aRetval = mResourceEntries;
195 0 : return;
196 : }
197 :
198 0 : aRetval.Clear();
199 :
200 0 : if (aEntryType.EqualsLiteral("mark") ||
201 0 : aEntryType.EqualsLiteral("measure")) {
202 0 : for (PerformanceEntry* entry : mUserEntries) {
203 0 : if (entry->GetEntryType().Equals(aEntryType)) {
204 0 : aRetval.AppendElement(entry);
205 : }
206 : }
207 : }
208 : }
209 :
210 : void
211 0 : Performance::GetEntriesByName(const nsAString& aName,
212 : const Optional<nsAString>& aEntryType,
213 : nsTArray<RefPtr<PerformanceEntry>>& aRetval)
214 : {
215 0 : aRetval.Clear();
216 :
217 : // We return an empty list when 'privacy.resistFingerprinting' is on.
218 0 : if (nsContentUtils::ShouldResistFingerprinting()) {
219 0 : return;
220 : }
221 :
222 0 : for (PerformanceEntry* entry : mResourceEntries) {
223 0 : if (entry->GetName().Equals(aName) &&
224 0 : (!aEntryType.WasPassed() ||
225 0 : entry->GetEntryType().Equals(aEntryType.Value()))) {
226 0 : aRetval.AppendElement(entry);
227 : }
228 : }
229 :
230 0 : for (PerformanceEntry* entry : mUserEntries) {
231 0 : if (entry->GetName().Equals(aName) &&
232 0 : (!aEntryType.WasPassed() ||
233 0 : entry->GetEntryType().Equals(aEntryType.Value()))) {
234 0 : aRetval.AppendElement(entry);
235 : }
236 : }
237 :
238 0 : aRetval.Sort(PerformanceEntryComparator());
239 : }
240 :
241 : void
242 0 : Performance::ClearUserEntries(const Optional<nsAString>& aEntryName,
243 : const nsAString& aEntryType)
244 : {
245 0 : for (uint32_t i = 0; i < mUserEntries.Length();) {
246 0 : if ((!aEntryName.WasPassed() ||
247 0 : mUserEntries[i]->GetName().Equals(aEntryName.Value())) &&
248 0 : (aEntryType.IsEmpty() ||
249 0 : mUserEntries[i]->GetEntryType().Equals(aEntryType))) {
250 0 : mUserEntries.RemoveElementAt(i);
251 : } else {
252 0 : ++i;
253 : }
254 : }
255 0 : }
256 :
257 : void
258 0 : Performance::ClearResourceTimings()
259 : {
260 0 : MOZ_ASSERT(NS_IsMainThread());
261 0 : mResourceEntries.Clear();
262 0 : }
263 :
264 : DOMHighResTimeStamp
265 58 : Performance::RoundTime(double aTime) const
266 : {
267 : // Round down to the nearest 5us, because if the timer is too accurate people
268 : // can do nasty timing attacks with it. See similar code in the worker
269 : // Performance implementation.
270 58 : const double maxResolutionMs = 0.005;
271 58 : return nsRFPService::ReduceTimePrecisionAsMSecs(
272 116 : floor(aTime / maxResolutionMs) * maxResolutionMs);
273 : }
274 :
275 :
276 : void
277 0 : Performance::Mark(const nsAString& aName, ErrorResult& aRv)
278 : {
279 : // We add nothing when 'privacy.resistFingerprinting' is on.
280 0 : if (nsContentUtils::ShouldResistFingerprinting()) {
281 0 : return;
282 : }
283 :
284 : // Don't add the entry if the buffer is full. XXX should be removed by bug 1159003.
285 0 : if (mUserEntries.Length() >= mResourceTimingBufferSize) {
286 0 : return;
287 : }
288 :
289 0 : if (IsPerformanceTimingAttribute(aName)) {
290 0 : aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
291 0 : return;
292 : }
293 :
294 : RefPtr<PerformanceMark> performanceMark =
295 0 : new PerformanceMark(GetAsISupports(), aName, Now());
296 0 : InsertUserEntry(performanceMark);
297 :
298 0 : if (profiler_is_active()) {
299 0 : profiler_add_marker(
300 : "UserTiming",
301 0 : MakeUnique<UserTimingMarkerPayload>(aName, TimeStamp::Now()));
302 : }
303 : }
304 :
305 : void
306 0 : Performance::ClearMarks(const Optional<nsAString>& aName)
307 : {
308 0 : ClearUserEntries(aName, NS_LITERAL_STRING("mark"));
309 0 : }
310 :
311 : DOMHighResTimeStamp
312 0 : Performance::ResolveTimestampFromName(const nsAString& aName,
313 : ErrorResult& aRv)
314 : {
315 0 : AutoTArray<RefPtr<PerformanceEntry>, 1> arr;
316 : DOMHighResTimeStamp ts;
317 0 : Optional<nsAString> typeParam;
318 0 : nsAutoString str;
319 0 : str.AssignLiteral("mark");
320 0 : typeParam = &str;
321 0 : GetEntriesByName(aName, typeParam, arr);
322 0 : if (!arr.IsEmpty()) {
323 0 : return arr.LastElement()->StartTime();
324 : }
325 :
326 0 : if (!IsPerformanceTimingAttribute(aName)) {
327 0 : aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
328 0 : return 0;
329 : }
330 :
331 0 : ts = GetPerformanceTimingFromString(aName);
332 0 : if (!ts) {
333 0 : aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
334 0 : return 0;
335 : }
336 :
337 0 : return ts - CreationTime();
338 : }
339 :
340 : void
341 0 : Performance::Measure(const nsAString& aName,
342 : const Optional<nsAString>& aStartMark,
343 : const Optional<nsAString>& aEndMark,
344 : ErrorResult& aRv)
345 : {
346 : // We add nothing when 'privacy.resistFingerprinting' is on.
347 0 : if (nsContentUtils::ShouldResistFingerprinting()) {
348 0 : return;
349 : }
350 :
351 : // Don't add the entry if the buffer is full. XXX should be removed by bug
352 : // 1159003.
353 0 : if (mUserEntries.Length() >= mResourceTimingBufferSize) {
354 0 : return;
355 : }
356 :
357 : DOMHighResTimeStamp startTime;
358 : DOMHighResTimeStamp endTime;
359 :
360 0 : if (IsPerformanceTimingAttribute(aName)) {
361 0 : aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
362 0 : return;
363 : }
364 :
365 0 : if (aStartMark.WasPassed()) {
366 0 : startTime = ResolveTimestampFromName(aStartMark.Value(), aRv);
367 0 : if (NS_WARN_IF(aRv.Failed())) {
368 0 : return;
369 : }
370 : } else {
371 : // Navigation start is used in this case, but since DOMHighResTimeStamp is
372 : // in relation to navigation start, this will be zero if a name is not
373 : // passed.
374 0 : startTime = 0;
375 : }
376 :
377 0 : if (aEndMark.WasPassed()) {
378 0 : endTime = ResolveTimestampFromName(aEndMark.Value(), aRv);
379 0 : if (NS_WARN_IF(aRv.Failed())) {
380 0 : return;
381 : }
382 : } else {
383 0 : endTime = Now();
384 : }
385 :
386 : RefPtr<PerformanceMeasure> performanceMeasure =
387 0 : new PerformanceMeasure(GetAsISupports(), aName, startTime, endTime);
388 0 : InsertUserEntry(performanceMeasure);
389 :
390 0 : if (profiler_is_active()) {
391 0 : TimeStamp startTimeStamp = CreationTimeStamp() +
392 0 : TimeDuration::FromMilliseconds(startTime);
393 0 : TimeStamp endTimeStamp = CreationTimeStamp() +
394 0 : TimeDuration::FromMilliseconds(endTime);
395 0 : profiler_add_marker(
396 : "UserTiming",
397 0 : MakeUnique<UserTimingMarkerPayload>(aName, startTimeStamp, endTimeStamp));
398 : }
399 : }
400 :
401 : void
402 0 : Performance::ClearMeasures(const Optional<nsAString>& aName)
403 : {
404 0 : ClearUserEntries(aName, NS_LITERAL_STRING("measure"));
405 0 : }
406 :
407 : void
408 0 : Performance::LogEntry(PerformanceEntry* aEntry, const nsACString& aOwner) const
409 : {
410 0 : PERFLOG("Performance Entry: %s|%s|%s|%f|%f|%" PRIu64 "\n",
411 : aOwner.BeginReading(),
412 : NS_ConvertUTF16toUTF8(aEntry->GetEntryType()).get(),
413 : NS_ConvertUTF16toUTF8(aEntry->GetName()).get(),
414 : aEntry->StartTime(),
415 : aEntry->Duration(),
416 0 : static_cast<uint64_t>(PR_Now() / PR_USEC_PER_MSEC));
417 0 : }
418 :
419 : void
420 0 : Performance::TimingNotification(PerformanceEntry* aEntry,
421 : const nsACString& aOwner, uint64_t aEpoch)
422 : {
423 0 : PerformanceEntryEventInit init;
424 0 : init.mBubbles = false;
425 0 : init.mCancelable = false;
426 0 : init.mName = aEntry->GetName();
427 0 : init.mEntryType = aEntry->GetEntryType();
428 0 : init.mStartTime = aEntry->StartTime();
429 0 : init.mDuration = aEntry->Duration();
430 0 : init.mEpoch = aEpoch;
431 0 : init.mOrigin = NS_ConvertUTF8toUTF16(aOwner.BeginReading());
432 :
433 : RefPtr<PerformanceEntryEvent> perfEntryEvent =
434 0 : PerformanceEntryEvent::Constructor(this, NS_LITERAL_STRING("performanceentry"), init);
435 :
436 0 : nsCOMPtr<EventTarget> et = do_QueryInterface(GetOwner());
437 0 : if (et) {
438 0 : bool dummy = false;
439 0 : et->DispatchEvent(perfEntryEvent, &dummy);
440 : }
441 0 : }
442 :
443 : void
444 0 : Performance::InsertUserEntry(PerformanceEntry* aEntry)
445 : {
446 0 : mUserEntries.InsertElementSorted(aEntry,
447 0 : PerformanceEntryComparator());
448 :
449 0 : QueueEntry(aEntry);
450 0 : }
451 :
452 : void
453 0 : Performance::SetResourceTimingBufferSize(uint64_t aMaxSize)
454 : {
455 0 : mResourceTimingBufferSize = aMaxSize;
456 0 : }
457 :
458 : void
459 2 : Performance::InsertResourceEntry(PerformanceEntry* aEntry)
460 : {
461 2 : MOZ_ASSERT(aEntry);
462 2 : MOZ_ASSERT(mResourceEntries.Length() < mResourceTimingBufferSize);
463 :
464 : // We won't add an entry when 'privacy.resistFingerprint' is true.
465 2 : if (nsContentUtils::ShouldResistFingerprinting()) {
466 0 : return;
467 : }
468 :
469 2 : if (mResourceEntries.Length() >= mResourceTimingBufferSize) {
470 0 : return;
471 : }
472 :
473 2 : mResourceEntries.InsertElementSorted(aEntry,
474 2 : PerformanceEntryComparator());
475 2 : if (mResourceEntries.Length() == mResourceTimingBufferSize) {
476 : // call onresourcetimingbufferfull
477 0 : DispatchBufferFullEvent();
478 : }
479 2 : QueueEntry(aEntry);
480 : }
481 :
482 : void
483 0 : Performance::AddObserver(PerformanceObserver* aObserver)
484 : {
485 0 : mObservers.AppendElementUnlessExists(aObserver);
486 0 : }
487 :
488 : void
489 0 : Performance::RemoveObserver(PerformanceObserver* aObserver)
490 : {
491 0 : mObservers.RemoveElement(aObserver);
492 0 : }
493 :
494 : void
495 0 : Performance::NotifyObservers()
496 : {
497 0 : mPendingNotificationObserversTask = false;
498 0 : NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mObservers,
499 : PerformanceObserver,
500 : Notify, ());
501 0 : }
502 :
503 : void
504 0 : Performance::CancelNotificationObservers()
505 : {
506 0 : mPendingNotificationObserversTask = false;
507 0 : }
508 :
509 : class NotifyObserversTask final : public CancelableRunnable
510 : {
511 : public:
512 0 : explicit NotifyObserversTask(Performance* aPerformance)
513 0 : : CancelableRunnable("dom::NotifyObserversTask")
514 0 : , mPerformance(aPerformance)
515 : {
516 0 : MOZ_ASSERT(mPerformance);
517 0 : }
518 :
519 0 : NS_IMETHOD Run() override
520 : {
521 0 : MOZ_ASSERT(mPerformance);
522 0 : mPerformance->NotifyObservers();
523 0 : return NS_OK;
524 : }
525 :
526 0 : nsresult Cancel() override
527 : {
528 0 : mPerformance->CancelNotificationObservers();
529 0 : mPerformance = nullptr;
530 0 : return NS_OK;
531 : }
532 :
533 : private:
534 0 : ~NotifyObserversTask()
535 0 : {
536 0 : }
537 :
538 : RefPtr<Performance> mPerformance;
539 : };
540 :
541 : void
542 0 : Performance::RunNotificationObserversTask()
543 : {
544 0 : mPendingNotificationObserversTask = true;
545 0 : nsCOMPtr<nsIRunnable> task = new NotifyObserversTask(this);
546 0 : nsresult rv = NS_DispatchToCurrentThread(task);
547 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
548 0 : mPendingNotificationObserversTask = false;
549 : }
550 0 : }
551 :
552 : void
553 2 : Performance::QueueEntry(PerformanceEntry* aEntry)
554 : {
555 2 : if (mObservers.IsEmpty()) {
556 2 : return;
557 : }
558 0 : NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mObservers,
559 : PerformanceObserver,
560 : QueueEntry, (aEntry));
561 :
562 0 : if (!mPendingNotificationObserversTask) {
563 0 : RunNotificationObserversTask();
564 : }
565 : }
566 :
567 : /* static */ bool
568 2 : Performance::IsObserverEnabled(JSContext* aCx, JSObject* aGlobal)
569 : {
570 2 : if (NS_IsMainThread()) {
571 0 : return Preferences::GetBool("dom.enable_performance_observer", false);
572 : }
573 :
574 2 : WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
575 2 : MOZ_ASSERT(workerPrivate);
576 2 : workerPrivate->AssertIsOnWorkerThread();
577 :
578 : RefPtr<PrefEnabledRunnable> runnable =
579 : new PrefEnabledRunnable(workerPrivate,
580 6 : NS_LITERAL_CSTRING("dom.enable_performance_observer"));
581 :
582 2 : return runnable->Dispatch() && runnable->IsEnabled();
583 : }
584 :
585 : } // dom namespace
586 : } // mozilla namespace
|