tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

PerformanceMainThread.cpp (26980B)


      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 "PerformanceMainThread.h"
      8 
      9 #include "LargestContentfulPaint.h"
     10 #include "PerformanceEventTiming.h"
     11 #include "PerformanceInteractionMetrics.h"
     12 #include "PerformanceNavigation.h"
     13 #include "PerformancePaintTiming.h"
     14 #include "js/GCAPI.h"
     15 #include "js/PropertyAndElement.h"  // JS_DefineProperty
     16 #include "jsapi.h"
     17 #include "mozilla/HoldDropJSObjects.h"
     18 #include "mozilla/PresShell.h"
     19 #include "mozilla/StaticPrefs_dom.h"
     20 #include "mozilla/TextEvents.h"
     21 #include "mozilla/dom/Document.h"
     22 #include "mozilla/dom/Event.h"
     23 #include "mozilla/dom/EventCounts.h"
     24 #include "mozilla/dom/FragmentDirective.h"
     25 #include "mozilla/dom/PerformanceEventTimingBinding.h"
     26 #include "mozilla/dom/PerformanceNavigationTiming.h"
     27 #include "mozilla/dom/PerformanceResourceTiming.h"
     28 #include "mozilla/dom/PerformanceTiming.h"
     29 #include "nsContainerFrame.h"
     30 #include "nsGkAtoms.h"
     31 #include "nsGlobalWindowInner.h"
     32 #include "nsIChannel.h"
     33 #include "nsIDocShell.h"
     34 #include "nsIHttpChannel.h"
     35 
     36 namespace mozilla::dom {
     37 
     38 extern mozilla::LazyLogModule gLCPLogging;
     39 
     40 namespace {
     41 
     42 void GetURLSpecFromChannel(nsITimedChannel* aChannel, nsAString& aSpec) {
     43  aSpec.AssignLiteral("document");
     44 
     45  nsCOMPtr<nsIChannel> channel = do_QueryInterface(aChannel);
     46  if (!channel) {
     47    return;
     48  }
     49 
     50  nsCOMPtr<nsIURI> uri;
     51  nsresult rv = channel->GetURI(getter_AddRefs(uri));
     52  if (NS_WARN_IF(NS_FAILED(rv)) || !uri) {
     53    return;
     54  }
     55 
     56  nsAutoCString spec;
     57  rv = FragmentDirective::GetSpecIgnoringFragmentDirective(uri, spec);
     58  if (NS_WARN_IF(NS_FAILED(rv))) {
     59    return;
     60  }
     61 
     62  CopyUTF8toUTF16(spec, aSpec);
     63 }
     64 
     65 }  // namespace
     66 
     67 NS_IMPL_CYCLE_COLLECTION_CLASS(PerformanceMainThread)
     68 
     69 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(PerformanceMainThread,
     70                                                Performance)
     71  NS_IMPL_CYCLE_COLLECTION_UNLINK(
     72      mTiming, mNavigation, mDocEntry, mFCPTiming, mEventTimingEntries,
     73      mLargestContentfulPaintEntries, mFirstInputEvent, mPendingPointerDown,
     74      mPendingEventTimingEntries, mEventCounts, mInteractionMetrics)
     75  tmp->mTextFrameUnions.Clear();
     76  mozilla::DropJSObjects(tmp);
     77 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
     78 
     79 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PerformanceMainThread,
     80                                                  Performance)
     81  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(
     82      mTiming, mNavigation, mDocEntry, mFCPTiming, mEventTimingEntries,
     83      mLargestContentfulPaintEntries, mFirstInputEvent, mPendingPointerDown,
     84      mPendingEventTimingEntries, mEventCounts, mTextFrameUnions,
     85      mInteractionMetrics)
     86 
     87 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
     88 
     89 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(PerformanceMainThread,
     90                                               Performance)
     91  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mMozMemory)
     92 NS_IMPL_CYCLE_COLLECTION_TRACE_END
     93 
     94 NS_IMPL_ADDREF_INHERITED(PerformanceMainThread, Performance)
     95 NS_IMPL_RELEASE_INHERITED(PerformanceMainThread, Performance)
     96 
     97 // QueryInterface implementation for PerformanceMainThread
     98 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PerformanceMainThread)
     99  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
    100  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, EventTarget)
    101 NS_INTERFACE_MAP_END_INHERITING(Performance)
    102 
    103 PerformanceMainThread::PerformanceMainThread(nsPIDOMWindowInner* aWindow,
    104                                             nsDOMNavigationTiming* aDOMTiming,
    105                                             nsITimedChannel* aChannel)
    106    : Performance(aWindow->AsGlobal()),
    107      mDOMTiming(aDOMTiming),
    108      mChannel(aChannel) {
    109  MOZ_ASSERT(aWindow, "Parent window object should be provided");
    110  if (StaticPrefs::dom_enable_event_timing()) {
    111    mEventCounts = new class EventCounts(GetParentObject());
    112  }
    113  CreateNavigationTimingEntry();
    114 
    115  if (StaticPrefs::dom_enable_largest_contentful_paint()) {
    116    nsGlobalWindowInner* owner = GetOwnerWindow();
    117    MarkerInnerWindowId innerWindowID =
    118        owner ? MarkerInnerWindowId(owner->WindowID())
    119              : MarkerInnerWindowId::NoId();
    120    // There might be multiple LCP entries and we only care about the latest one
    121    // which is also the biggest value. That's why we need to record these
    122    // markers in two different places:
    123    // - During the Document unload, so we can record the closed pages.
    124    // - During the profile capture, so we can record the open pages.
    125    // We are capturing the second one here.
    126    // Our static analysis doesn't allow capturing ref-counted pointers in
    127    // lambdas, so we need to hide it in a uintptr_t. This is safe because this
    128    // lambda will be destroyed in ~PerformanceMainThread().
    129    uintptr_t self = reinterpret_cast<uintptr_t>(this);
    130    profiler_add_state_change_callback(
    131        // Using the "Pausing" state as "GeneratingProfile" profile happens too
    132        // late; we can not record markers if the profiler is already paused.
    133        ProfilingState::Pausing,
    134        [self, innerWindowID](ProfilingState aProfilingState) {
    135          const PerformanceMainThread* selfPtr =
    136              reinterpret_cast<const PerformanceMainThread*>(self);
    137 
    138          selfPtr->GetDOMTiming()->MaybeAddLCPProfilerMarker(innerWindowID);
    139        },
    140        self);
    141  }
    142 }
    143 
    144 PerformanceMainThread::~PerformanceMainThread() {
    145  profiler_remove_state_change_callback(reinterpret_cast<uintptr_t>(this));
    146  mozilla::DropJSObjects(this);
    147 }
    148 
    149 void PerformanceMainThread::GetMozMemory(JSContext* aCx,
    150                                         JS::MutableHandle<JSObject*> aObj) {
    151  if (!mMozMemory) {
    152    JS::Rooted<JSObject*> mozMemoryObj(aCx, JS_NewPlainObject(aCx));
    153    JS::Rooted<JSObject*> gcMemoryObj(aCx, js::gc::NewMemoryInfoObject(aCx));
    154    if (!mozMemoryObj || !gcMemoryObj) {
    155      MOZ_CRASH("out of memory creating performance.mozMemory");
    156    }
    157    if (!JS_DefineProperty(aCx, mozMemoryObj, "gc", gcMemoryObj,
    158                           JSPROP_ENUMERATE)) {
    159      MOZ_CRASH("out of memory creating performance.mozMemory");
    160    }
    161    mMozMemory = mozMemoryObj;
    162    mozilla::HoldJSObjects(this);
    163  }
    164 
    165  aObj.set(mMozMemory);
    166 }
    167 
    168 PerformanceTiming* PerformanceMainThread::Timing() {
    169  if (!mTiming) {
    170    // For navigation timing, the third argument (an nsIHttpChannel) is null
    171    // since the cross-domain redirect were already checked.  The last
    172    // argument (zero time) for performance.timing is the navigation start
    173    // value.
    174    mTiming = new PerformanceTiming(this, mChannel, nullptr,
    175                                    mDOMTiming->GetNavigationStart());
    176  }
    177 
    178  return mTiming;
    179 }
    180 
    181 void PerformanceMainThread::DispatchResourceTimingBufferFullEvent() {
    182  RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
    183  // it bubbles, and it isn't cancelable
    184  event->InitEvent(u"resourcetimingbufferfull"_ns, true, false);
    185  event->SetTrusted(true);
    186  DispatchEvent(*event);
    187 }
    188 
    189 PerformanceNavigation* PerformanceMainThread::Navigation() {
    190  if (!mNavigation) {
    191    mNavigation = new PerformanceNavigation(this);
    192  }
    193 
    194  return mNavigation;
    195 }
    196 
    197 /**
    198 * An entry should be added only after the resource is loaded.
    199 * This method is not thread safe and can only be called on the main thread.
    200 */
    201 void PerformanceMainThread::AddEntry(nsIHttpChannel* channel,
    202                                     nsITimedChannel* timedChannel) {
    203  MOZ_ASSERT(NS_IsMainThread());
    204 
    205  nsAutoString initiatorType;
    206  nsAutoString entryName;
    207 
    208  UniquePtr<PerformanceTimingData> performanceTimingData(
    209      PerformanceTimingData::Create(timedChannel, channel, 0, initiatorType,
    210                                    entryName));
    211  if (!performanceTimingData) {
    212    return;
    213  }
    214  AddRawEntry(std::move(performanceTimingData), initiatorType, entryName);
    215 }
    216 
    217 void PerformanceMainThread::AddEntry(const nsString& entryName,
    218                                     const nsString& initiatorType,
    219                                     UniquePtr<PerformanceTimingData>&& aData) {
    220  AddRawEntry(std::move(aData), initiatorType, entryName);
    221 }
    222 
    223 void PerformanceMainThread::AddRawEntry(UniquePtr<PerformanceTimingData> aData,
    224                                        const nsAString& aInitiatorType,
    225                                        const nsAString& aEntryName) {
    226  // The PerformanceResourceTiming object will use the PerformanceTimingData
    227  // object to get all the required timings.
    228  auto entry =
    229      MakeRefPtr<PerformanceResourceTiming>(std::move(aData), this, aEntryName);
    230  entry->SetInitiatorType(aInitiatorType);
    231  InsertResourceEntry(entry);
    232 }
    233 
    234 void PerformanceMainThread::SetFCPTimingEntry(PerformancePaintTiming* aEntry) {
    235  MOZ_ASSERT(aEntry);
    236  if (!mFCPTiming) {
    237    mFCPTiming = aEntry;
    238    QueueEntry(aEntry);
    239  }
    240 }
    241 
    242 void PerformanceMainThread::InsertEventTimingEntry(
    243    PerformanceEventTiming* aEventEntry) {
    244  mPendingEventTimingEntries.insertBack(aEventEntry);
    245 
    246  if (mHasQueuedRefreshdriverObserver) {
    247    return;
    248  }
    249 
    250  PresShell* presShell = GetPresShell();
    251  if (!presShell) {
    252    return;
    253  }
    254 
    255  nsPresContext* presContext = presShell->GetPresContext();
    256  if (!presContext) {
    257    return;
    258  }
    259 
    260  // Using PostRefreshObserver is fine because we don't
    261  // run any JS between the `mark paint timing` step and the
    262  // `pending Event Timing entries` step. So mixing the order
    263  // here is fine.
    264  mHasQueuedRefreshdriverObserver = true;
    265  presContext->RegisterManagedPostRefreshObserver(
    266      new ManagedPostRefreshObserver(
    267          presContext, [performance = RefPtr<PerformanceMainThread>(this)](
    268                           bool aWasCanceled) {
    269            if (!aWasCanceled) {
    270              // XXX Should we do this even if canceled?
    271              performance->DispatchPendingEventTimingEntries();
    272            }
    273            performance->mHasQueuedRefreshdriverObserver = false;
    274            return ManagedPostRefreshObserver::Unregister::Yes;
    275          }));
    276 }
    277 
    278 void PerformanceMainThread::BufferEventTimingEntryIfNeeded(
    279    PerformanceEventTiming* aEventEntry) {
    280  if (mEventTimingEntries.Length() < kDefaultEventTimingBufferSize) {
    281    mEventTimingEntries.AppendElement(aEventEntry);
    282  }
    283 }
    284 
    285 void PerformanceMainThread::BufferLargestContentfulPaintEntryIfNeeded(
    286    LargestContentfulPaint* aEntry) {
    287  MOZ_ASSERT(StaticPrefs::dom_enable_largest_contentful_paint());
    288  if (mLargestContentfulPaintEntries.Length() <
    289      kMaxLargestContentfulPaintBufferSize) {
    290    mLargestContentfulPaintEntries.AppendElement(aEntry);
    291  }
    292 }
    293 
    294 void PerformanceMainThread::DispatchPendingEventTimingEntries() {
    295  DOMHighResTimeStamp renderingTime = NowUnclamped();
    296 
    297  auto entriesToBeQueuedEnd = mPendingEventTimingEntries.end();
    298  for (auto it = mPendingEventTimingEntries.begin();
    299       it != mPendingEventTimingEntries.end(); ++it) {
    300    // Set its duration if it's not set already.
    301    PerformanceEventTiming* entry = *it;
    302    if (entry->RawDuration().isNothing()) {
    303      entry->SetDuration(renderingTime - entry->RawStartTime());
    304    }
    305 
    306    if (!(mPendingEventTimingEntries.end() != entriesToBeQueuedEnd) &&
    307        !entry->HasKnownInteractionId()) {
    308      entriesToBeQueuedEnd = it;
    309    }
    310  }
    311 
    312  if (!StaticPrefs::dom_performance_event_timing_enable_interactionid() ||
    313      mPendingEventTimingEntries.begin() != entriesToBeQueuedEnd) {
    314    while (mPendingEventTimingEntries.begin() != entriesToBeQueuedEnd) {
    315      RefPtr<PerformanceEventTiming> entry =
    316          mPendingEventTimingEntries.popFirst();
    317      if (entry->RawDuration().valueOr(0) >= kDefaultEventTimingMinDuration) {
    318        QueueEntry(entry);
    319      }
    320 
    321      // Perform the following steps to update the event counts:
    322      IncEventCount(entry->GetName());
    323 
    324      // If window’s has dispatched input event is false, run the following
    325      // steps:
    326      if (StaticPrefs::dom_performance_event_timing_enable_interactionid()) {
    327        if (!mHasDispatchedInputEvent && entry->InteractionId() != 0) {
    328          mFirstInputEvent = entry->Clone();
    329          mFirstInputEvent->SetEntryType(nsGkAtoms::firstInput);
    330          QueueEntry(mFirstInputEvent);
    331          SetHasDispatchedInputEvent();
    332        }
    333      } else {
    334        if (!mHasDispatchedInputEvent) {
    335          switch (entry->GetMessage()) {
    336            case ePointerDown: {
    337              mPendingPointerDown = entry->Clone();
    338              mPendingPointerDown->SetEntryType(nsGkAtoms::firstInput);
    339              break;
    340            }
    341            case ePointerUp: {
    342              if (mPendingPointerDown) {
    343                MOZ_ASSERT(!mFirstInputEvent);
    344                mFirstInputEvent = mPendingPointerDown.forget();
    345                QueueEntry(mFirstInputEvent);
    346                SetHasDispatchedInputEvent();
    347              }
    348              break;
    349            }
    350            case ePointerClick:
    351            case eKeyDown:
    352            case eMouseDown: {
    353              mFirstInputEvent = entry->Clone();
    354              mFirstInputEvent->SetEntryType(nsGkAtoms::firstInput);
    355              QueueEntry(mFirstInputEvent);
    356              SetHasDispatchedInputEvent();
    357              break;
    358            }
    359            default:
    360              break;
    361          }
    362        }
    363      }
    364    }
    365  }
    366 }
    367 
    368 PerformanceInteractionMetrics&
    369 PerformanceMainThread::GetPerformanceInteractionMetrics() {
    370  return mInteractionMetrics;
    371 }
    372 
    373 void PerformanceMainThread::SetInteractionId(
    374    PerformanceEventTiming* aEventTiming, const WidgetEvent* aEvent) {
    375  MOZ_ASSERT(NS_IsMainThread());
    376  if (!StaticPrefs::dom_performance_event_timing_enable_interactionid() ||
    377      aEvent->mFlags.mOnlyChromeDispatch || !aEvent->IsTrusted()) {
    378    aEventTiming->SetInteractionId(0);
    379    return;
    380  }
    381 
    382  aEventTiming->SetInteractionId(
    383      mInteractionMetrics.ComputeInteractionId(aEventTiming, aEvent));
    384 }
    385 
    386 DOMHighResTimeStamp PerformanceMainThread::GetPerformanceTimingFromString(
    387    const nsAString& aProperty) {
    388  // ::Measure expects the values returned from this function to be passed
    389  // through ReduceTimePrecision already.
    390  if (!IsPerformanceTimingAttribute(aProperty)) {
    391    return 0;
    392  }
    393  // Values from Timing() are already reduced
    394  if (aProperty.EqualsLiteral("redirectStart")) {
    395    return Timing()->RedirectStart();
    396  }
    397  if (aProperty.EqualsLiteral("redirectEnd")) {
    398    return Timing()->RedirectEnd();
    399  }
    400  if (aProperty.EqualsLiteral("fetchStart")) {
    401    return Timing()->FetchStart();
    402  }
    403  if (aProperty.EqualsLiteral("domainLookupStart")) {
    404    return Timing()->DomainLookupStart();
    405  }
    406  if (aProperty.EqualsLiteral("domainLookupEnd")) {
    407    return Timing()->DomainLookupEnd();
    408  }
    409  if (aProperty.EqualsLiteral("connectStart")) {
    410    return Timing()->ConnectStart();
    411  }
    412  if (aProperty.EqualsLiteral("secureConnectionStart")) {
    413    return Timing()->SecureConnectionStart();
    414  }
    415  if (aProperty.EqualsLiteral("connectEnd")) {
    416    return Timing()->ConnectEnd();
    417  }
    418  if (aProperty.EqualsLiteral("requestStart")) {
    419    return Timing()->RequestStart();
    420  }
    421  if (aProperty.EqualsLiteral("responseStart")) {
    422    return Timing()->ResponseStart();
    423  }
    424  if (aProperty.EqualsLiteral("responseEnd")) {
    425    return Timing()->ResponseEnd();
    426  }
    427  // Values from GetDOMTiming() are not.
    428  DOMHighResTimeStamp retValue;
    429  if (aProperty.EqualsLiteral("navigationStart")) {
    430    // DOMHighResTimeStamp is in relation to navigationStart, so this will be
    431    // zero.
    432    retValue = GetDOMTiming()->GetNavigationStart();
    433  } else if (aProperty.EqualsLiteral("unloadEventStart")) {
    434    retValue = GetDOMTiming()->GetUnloadEventStart();
    435  } else if (aProperty.EqualsLiteral("unloadEventEnd")) {
    436    retValue = GetDOMTiming()->GetUnloadEventEnd();
    437  } else if (aProperty.EqualsLiteral("domLoading")) {
    438    retValue = GetDOMTiming()->GetDomLoading();
    439  } else if (aProperty.EqualsLiteral("domInteractive")) {
    440    retValue = GetDOMTiming()->GetDomInteractive();
    441  } else if (aProperty.EqualsLiteral("domContentLoadedEventStart")) {
    442    retValue = GetDOMTiming()->GetDomContentLoadedEventStart();
    443  } else if (aProperty.EqualsLiteral("domContentLoadedEventEnd")) {
    444    retValue = GetDOMTiming()->GetDomContentLoadedEventEnd();
    445  } else if (aProperty.EqualsLiteral("domComplete")) {
    446    retValue = GetDOMTiming()->GetDomComplete();
    447  } else if (aProperty.EqualsLiteral("loadEventStart")) {
    448    retValue = GetDOMTiming()->GetLoadEventStart();
    449  } else if (aProperty.EqualsLiteral("loadEventEnd")) {
    450    retValue = GetDOMTiming()->GetLoadEventEnd();
    451  } else {
    452    MOZ_CRASH(
    453        "IsPerformanceTimingAttribute and GetPerformanceTimingFromString are "
    454        "out "
    455        "of sync");
    456  }
    457  return nsRFPService::ReduceTimePrecisionAsMSecs(
    458      retValue, GetRandomTimelineSeed(), mRTPCallerType);
    459 }
    460 
    461 void PerformanceMainThread::InsertUserEntry(PerformanceEntry* aEntry) {
    462  MOZ_ASSERT(NS_IsMainThread());
    463 
    464  nsAutoCString uri;
    465  double markCreationEpoch = 0;
    466 
    467  if (StaticPrefs::dom_performance_enable_user_timing_logging() ||
    468      StaticPrefs::dom_performance_enable_notify_performance_timing()) {
    469    nsresult rv = NS_ERROR_FAILURE;
    470    nsGlobalWindowInner* owner = GetOwnerWindow();
    471    if (owner && owner->GetDocumentURI()) {
    472      rv = owner->GetDocumentURI()->GetHost(uri);
    473    }
    474 
    475    if (NS_FAILED(rv)) {
    476      // If we have no URI, just put in "none".
    477      uri.AssignLiteral("none");
    478    }
    479 
    480    // PR_Now() returns a signed 64-bit integer. Since it represents a
    481    // timestamp, only ~32-bits will represent the value which should safely fit
    482    // into a double.
    483    markCreationEpoch = static_cast<double>(PR_Now() / PR_USEC_PER_MSEC);
    484 
    485    if (StaticPrefs::dom_performance_enable_user_timing_logging()) {
    486      Performance::LogEntry(aEntry, uri);
    487    }
    488  }
    489 
    490  if (StaticPrefs::dom_performance_enable_notify_performance_timing()) {
    491    TimingNotification(aEntry, uri, markCreationEpoch);
    492  }
    493 
    494  Performance::InsertUserEntry(aEntry);
    495 }
    496 
    497 TimeStamp PerformanceMainThread::CreationTimeStamp() const {
    498  return GetDOMTiming()->GetNavigationStartTimeStamp();
    499 }
    500 
    501 DOMHighResTimeStamp PerformanceMainThread::CreationTime() const {
    502  return GetDOMTiming()->GetNavigationStart();
    503 }
    504 
    505 void PerformanceMainThread::CreateNavigationTimingEntry() {
    506  MOZ_ASSERT(!mDocEntry, "mDocEntry should be null.");
    507 
    508  if (!StaticPrefs::dom_enable_performance_navigation_timing()) {
    509    return;
    510  }
    511 
    512  nsAutoString name;
    513  GetURLSpecFromChannel(mChannel, name);
    514 
    515  UniquePtr<PerformanceTimingData> timing(
    516      new PerformanceTimingData(mChannel, nullptr, 0));
    517 
    518  nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
    519  if (httpChannel) {
    520    timing->SetPropertiesFromHttpChannel(httpChannel, mChannel);
    521  }
    522 
    523  mDocEntry = new PerformanceNavigationTiming(std::move(timing), this, name);
    524 }
    525 
    526 void PerformanceMainThread::UpdateNavigationTimingEntry() {
    527  if (!mDocEntry) {
    528    return;
    529  }
    530 
    531  // Let's update some values.
    532  nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
    533  if (httpChannel) {
    534    mDocEntry->UpdatePropertiesFromHttpChannel(httpChannel, mChannel);
    535  }
    536 }
    537 
    538 void PerformanceMainThread::QueueNavigationTimingEntry() {
    539  if (!mDocEntry) {
    540    return;
    541  }
    542 
    543  UpdateNavigationTimingEntry();
    544 
    545  QueueEntry(mDocEntry);
    546 }
    547 
    548 void PerformanceMainThread::QueueLargestContentfulPaintEntry(
    549    LargestContentfulPaint* aEntry) {
    550  MOZ_ASSERT(StaticPrefs::dom_enable_largest_contentful_paint());
    551  QueueEntry(aEntry);
    552 }
    553 
    554 EventCounts* PerformanceMainThread::EventCounts() {
    555  MOZ_ASSERT(StaticPrefs::dom_enable_event_timing());
    556  return mEventCounts;
    557 }
    558 
    559 uint64_t PerformanceMainThread::InteractionCount() {
    560  MOZ_ASSERT(StaticPrefs::dom_performance_event_timing_enable_interactionid());
    561  return mInteractionMetrics.InteractionCount();
    562 }
    563 
    564 void PerformanceMainThread::GetEntries(
    565    nsTArray<RefPtr<PerformanceEntry>>& aRetval) {
    566  aRetval = mResourceEntries.Clone();
    567  aRetval.AppendElements(mUserEntries);
    568 
    569  if (mDocEntry) {
    570    aRetval.AppendElement(mDocEntry);
    571  }
    572 
    573  if (mFCPTiming) {
    574    aRetval.AppendElement(mFCPTiming);
    575  }
    576  aRetval.Sort(PerformanceEntryComparator());
    577 }
    578 
    579 void PerformanceMainThread::GetEntriesByType(
    580    const nsAString& aEntryType, nsTArray<RefPtr<PerformanceEntry>>& aRetval) {
    581  RefPtr<nsAtom> type = NS_Atomize(aEntryType);
    582  if (type == nsGkAtoms::navigation) {
    583    aRetval.Clear();
    584 
    585    if (mDocEntry) {
    586      aRetval.AppendElement(mDocEntry);
    587    }
    588    return;
    589  }
    590 
    591  if (type == nsGkAtoms::paint) {
    592    if (mFCPTiming) {
    593      aRetval.AppendElement(mFCPTiming);
    594      return;
    595    }
    596  }
    597 
    598  if (type == nsGkAtoms::firstInput && mFirstInputEvent) {
    599    aRetval.AppendElement(mFirstInputEvent);
    600    return;
    601  }
    602 
    603  Performance::GetEntriesByType(aEntryType, aRetval);
    604 }
    605 void PerformanceMainThread::GetEntriesByTypeForObserver(
    606    const nsAString& aEntryType, nsTArray<RefPtr<PerformanceEntry>>& aRetval) {
    607  if (aEntryType.EqualsLiteral("event")) {
    608    aRetval.AppendElements(mEventTimingEntries);
    609    return;
    610  }
    611 
    612  if (StaticPrefs::dom_enable_largest_contentful_paint()) {
    613    if (aEntryType.EqualsLiteral("largest-contentful-paint")) {
    614      aRetval.AppendElements(mLargestContentfulPaintEntries);
    615      return;
    616    }
    617  }
    618 
    619  return GetEntriesByType(aEntryType, aRetval);
    620 }
    621 
    622 void PerformanceMainThread::GetEntriesByName(
    623    const nsAString& aName, const Optional<nsAString>& aEntryType,
    624    nsTArray<RefPtr<PerformanceEntry>>& aRetval) {
    625  Performance::GetEntriesByName(aName, aEntryType, aRetval);
    626 
    627  if (mFCPTiming && mFCPTiming->GetName()->Equals(aName) &&
    628      (!aEntryType.WasPassed() ||
    629       mFCPTiming->GetEntryType()->Equals(aEntryType.Value()))) {
    630    aRetval.AppendElement(mFCPTiming);
    631    return;
    632  }
    633 
    634  // The navigation entry is the first one. If it exists and the name matches,
    635  // let put it in front.
    636  if (mDocEntry && mDocEntry->GetName()->Equals(aName)) {
    637    aRetval.InsertElementAt(0, mDocEntry);
    638    return;
    639  }
    640 }
    641 
    642 mozilla::PresShell* PerformanceMainThread::GetPresShell() {
    643  nsIGlobalObject* ownerGlobal = GetOwnerGlobal();
    644  if (!ownerGlobal) {
    645    return nullptr;
    646  }
    647  if (Document* doc = ownerGlobal->GetAsInnerWindow()->GetExtantDoc()) {
    648    return doc->GetPresShell();
    649  }
    650  return nullptr;
    651 }
    652 
    653 void PerformanceMainThread::IncEventCount(const nsAtom* aType) {
    654  MOZ_ASSERT(StaticPrefs::dom_enable_event_timing());
    655 
    656  // This occurs when the pref was false when the performance
    657  // object was first created, and became true later. It's
    658  // okay to return early because eventCounts is not exposed.
    659  if (!mEventCounts) {
    660    return;
    661  }
    662 
    663  IgnoredErrorResult rv;
    664  uint64_t count = EventCounts_Binding::MaplikeHelpers::Get(
    665      mEventCounts, nsDependentAtomString(aType), rv);
    666  if (rv.Failed()) {
    667    return;
    668  }
    669  EventCounts_Binding::MaplikeHelpers::Set(
    670      mEventCounts, nsDependentAtomString(aType), ++count, rv);
    671 }
    672 
    673 size_t PerformanceMainThread::SizeOfEventEntries(
    674    mozilla::MallocSizeOf aMallocSizeOf) const {
    675  size_t eventEntries = 0;
    676  for (const PerformanceEventTiming* entry : mEventTimingEntries) {
    677    eventEntries += entry->SizeOfIncludingThis(aMallocSizeOf);
    678  }
    679  return eventEntries;
    680 }
    681 
    682 void PerformanceMainThread::ProcessElementTiming() {
    683  if (!StaticPrefs::dom_enable_largest_contentful_paint()) {
    684    return;
    685  }
    686  const bool shouldLCPDataEmpty =
    687      HasDispatchedInputEvent() || HasDispatchedScrollEvent();
    688  MOZ_ASSERT_IF(shouldLCPDataEmpty, mTextFrameUnions.IsEmpty());
    689 
    690  if (shouldLCPDataEmpty) {
    691    return;
    692  }
    693 
    694  nsPresContext* presContext = GetPresShell()->GetPresContext();
    695  MOZ_ASSERT(presContext);
    696 
    697  // After https://github.com/w3c/largest-contentful-paint/issues/104 is
    698  // resolved, LargestContentfulPaint and FirstContentfulPaint should
    699  // be using the same timestamp, which should be the same timestamp
    700  // as to what https://w3c.github.io/paint-timing/#mark-paint-timing step 2
    701  // defines.
    702  // TODO(sefeng): Check the timestamp after this issue is resolved.
    703  TimeStamp rawNowTime = presContext->GetMarkPaintTimingStart();
    704 
    705  MOZ_ASSERT(GetOwnerGlobal());
    706  Document* document = GetOwnerGlobal()->GetAsInnerWindow()->GetExtantDoc();
    707  if (!document ||
    708      !nsContentUtils::GetInProcessSubtreeRootDocument(document)->IsActive()) {
    709    return;
    710  }
    711 
    712  nsTArray<ImagePendingRendering> imagesPendingRendering =
    713      std::move(mImagesPendingRendering);
    714  for (const auto& imagePendingRendering : imagesPendingRendering) {
    715    RefPtr<Element> element = imagePendingRendering.GetElement();
    716    if (!element) {
    717      continue;
    718    }
    719 
    720    MOZ_ASSERT(imagePendingRendering.mLoadTime <= rawNowTime);
    721    if (imgRequestProxy* requestProxy =
    722            imagePendingRendering.GetImgRequestProxy()) {
    723      requestProxy->GetLCPTimings().Set(imagePendingRendering.mLoadTime,
    724                                        rawNowTime);
    725    }
    726  }
    727 
    728  MOZ_ASSERT(mImagesPendingRendering.IsEmpty());
    729 }
    730 
    731 void PerformanceMainThread::FinalizeLCPEntriesForText() {
    732  nsPresContext* presContext = GetPresShell()->GetPresContext();
    733  MOZ_ASSERT(presContext);
    734 
    735  bool canFinalize = StaticPrefs::dom_enable_largest_contentful_paint() &&
    736                     !presContext->HasStoppedGeneratingLCP();
    737  nsTHashMap<nsRefPtrHashKey<Element>, nsRect> textFrameUnion =
    738      std::move(GetTextFrameUnions());
    739  if (canFinalize) {
    740    for (const auto& textFrameUnion : textFrameUnion) {
    741      LCPHelpers::FinalizeLCPEntryForText(
    742          this, presContext->GetMarkPaintTimingStart(), textFrameUnion.GetKey(),
    743          textFrameUnion.GetData(), presContext);
    744    }
    745  }
    746  MOZ_ASSERT(GetTextFrameUnions().IsEmpty());
    747 }
    748 
    749 bool PerformanceMainThread::IsPendingLCPCandidate(
    750    Element* aElement, imgRequestProxy* aImgRequestProxy) {
    751  Document* doc = aElement->GetComposedDoc();
    752  MOZ_ASSERT(doc, "Element should be connected when it's painted");
    753  if (!aElement->HasFlag(ELEMENT_IN_CONTENT_IDENTIFIER_FOR_LCP)) {
    754    MOZ_ASSERT(!doc->ContentIdentifiersForLCP().Contains(aElement));
    755    return false;
    756  }
    757 
    758  if (auto entry = doc->ContentIdentifiersForLCP().Lookup(aElement)) {
    759    return entry.Data().Contains(aImgRequestProxy);
    760  }
    761 
    762  MOZ_ASSERT_UNREACHABLE("we should always have an entry when the flag exists");
    763  return false;
    764 }
    765 
    766 bool PerformanceMainThread::UpdateLargestContentfulPaintSize(double aSize) {
    767  if (aSize > mLargestContentfulPaintSize) {
    768    mLargestContentfulPaintSize = aSize;
    769    return true;
    770  }
    771  return false;
    772 }
    773 
    774 void PerformanceMainThread::SetHasDispatchedScrollEvent() {
    775  mHasDispatchedScrollEvent = true;
    776  ClearGeneratedTempDataForLCP();
    777 }
    778 
    779 void PerformanceMainThread::SetHasDispatchedInputEvent() {
    780  mHasDispatchedInputEvent = true;
    781  ClearGeneratedTempDataForLCP();
    782 }
    783 
    784 void PerformanceMainThread::ClearGeneratedTempDataForLCP() {
    785  mTextFrameUnions.Clear();
    786  mImagesPendingRendering.Clear();
    787 
    788  nsIGlobalObject* ownerGlobal = GetOwnerGlobal();
    789  if (!ownerGlobal) {
    790    return;
    791  }
    792 
    793  if (Document* document = ownerGlobal->GetAsInnerWindow()->GetExtantDoc()) {
    794    document->ContentIdentifiersForLCP().Clear();
    795  }
    796 }
    797 }  // namespace mozilla::dom