tor-browser

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

PerformanceObserver.cpp (12066B)


      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 "PerformanceObserver.h"
      8 
      9 #include "LargestContentfulPaint.h"
     10 #include "PerformanceEntry.h"
     11 #include "PerformanceObserverEntryList.h"
     12 #include "mozilla/StaticPrefs_dom.h"
     13 #include "mozilla/dom/Performance.h"
     14 #include "mozilla/dom/PerformanceBinding.h"
     15 #include "mozilla/dom/PerformanceEntryBinding.h"
     16 #include "mozilla/dom/PerformanceObserverBinding.h"
     17 #include "mozilla/dom/WorkerScope.h"
     18 #include "nsIScriptError.h"
     19 #include "nsPIDOMWindow.h"
     20 #include "nsQueryObject.h"
     21 #include "nsString.h"
     22 
     23 using namespace mozilla;
     24 using namespace mozilla::dom;
     25 
     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
     41 
     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
     48 
     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();
     57 }
     58 
     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();
     67 }
     68 
     69 PerformanceObserver::~PerformanceObserver() {
     70  Disconnect();
     71  MOZ_ASSERT(!mConnected);
     72 }
     73 
     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;
     84    }
     85 
     86    RefPtr<PerformanceObserver> observer =
     87        new PerformanceObserver(ownerWindow, aCb);
     88    return observer.forget();
     89  }
     90 
     91  JSContext* cx = aGlobal.Context();
     92  WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx);
     93  MOZ_ASSERT(workerPrivate);
     94 
     95  RefPtr<PerformanceObserver> observer =
     96      new PerformanceObserver(workerPrivate, aCb);
     97  return observer.forget();
     98 }
     99 
    100 JSObject* PerformanceObserver::WrapObject(JSContext* aCx,
    101                                          JS::Handle<JSObject*> aGivenProto) {
    102  return PerformanceObserver_Binding::Wrap(aCx, this, aGivenProto);
    103 }
    104 
    105 void PerformanceObserver::Notify() {
    106  if (mQueuedEntries.IsEmpty()) {
    107    return;
    108  }
    109  RefPtr<PerformanceObserverEntryList> list =
    110      new PerformanceObserverEntryList(this, mQueuedEntries);
    111 
    112  mQueuedEntries.Clear();
    113 
    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();
    119  }
    120 }
    121 
    122 void PerformanceObserver::QueueEntry(PerformanceEntry* aEntry) {
    123  MOZ_ASSERT(aEntry);
    124  MOZ_ASSERT(ObservesTypeOfEntry(aEntry));
    125 
    126  mQueuedEntries.AppendElement(aEntry);
    127 }
    128 
    129 static constexpr nsLiteralString kValidEventTimingNames[2] = {
    130    u"event"_ns, u"first-input"_ns};
    131 
    132 /*
    133 * Keep this list in alphabetical order.
    134 * https://w3c.github.io/performance-timeline/#supportedentrytypes-attribute
    135 */
    136 static constexpr nsLiteralString kValidTypeNames[5] = {
    137    u"mark"_ns, u"measure"_ns, u"navigation"_ns, u"paint"_ns, u"resource"_ns,
    138 };
    139 
    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;
    145 
    146  if (!mPerformance || !mOwner) {
    147    aRv.Throw(NS_ERROR_FAILURE);
    148    return;
    149  }
    150 
    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;
    155  }
    156 
    157  if (maybeEntryTypes.WasPassed() && maybeType.WasPassed()) {
    158    /* Per spec (3.3.1.3), this, too, should be a syntax error. */
    159    /*
    160     * As per the spec we also need to throw a type error if there are both
    161     * `entryTypes` and `buffered` options, but either Blink or WebKit doesn't
    162     * throw the error so we don't throw to align the behavior with them.
    163     * https://github.com/w3c/performance-timeline/issues/215
    164     */
    165    aRv.ThrowTypeError("Can't call observe with both `type` and `entryTypes`");
    166    return;
    167  }
    168 
    169  /* 3.3.1.4.1 */
    170  if (mObserverType == ObserverTypeUndefined) {
    171    if (maybeEntryTypes.WasPassed()) {
    172      mObserverType = ObserverTypeMultiple;
    173    } else {
    174      mObserverType = ObserverTypeSingle;
    175    }
    176  }
    177 
    178  /* 3.3.1.4.2 */
    179  if (mObserverType == ObserverTypeSingle && maybeEntryTypes.WasPassed()) {
    180    aRv.Throw(NS_ERROR_DOM_INVALID_MODIFICATION_ERR);
    181    return;
    182  }
    183  /* 3.3.1.4.3 */
    184  if (mObserverType == ObserverTypeMultiple && maybeType.WasPassed()) {
    185    aRv.Throw(NS_ERROR_DOM_INVALID_MODIFICATION_ERR);
    186    return;
    187  }
    188 
    189  bool needQueueNotificationObserverTask = false;
    190  /* 3.3.1.5 */
    191  if (mObserverType == ObserverTypeMultiple) {
    192    const Sequence<nsString>& entryTypes = maybeEntryTypes.Value();
    193 
    194    if (entryTypes.IsEmpty()) {
    195      return;
    196    }
    197 
    198    /* 3.3.1.5.2 */
    199    nsTArray<nsString> validEntryTypes;
    200 
    201    if (StaticPrefs::dom_enable_event_timing()) {
    202      for (const nsLiteralString& name : kValidEventTimingNames) {
    203        if (entryTypes.Contains(name) && !validEntryTypes.Contains(name)) {
    204          validEntryTypes.AppendElement(name);
    205        }
    206      }
    207    }
    208    if (StaticPrefs::dom_enable_largest_contentful_paint()) {
    209      if (entryTypes.Contains(kLargestContentfulPaintName) &&
    210          !validEntryTypes.Contains(kLargestContentfulPaintName)) {
    211        validEntryTypes.AppendElement(kLargestContentfulPaintName);
    212      }
    213    }
    214    for (const nsLiteralString& name : kValidTypeNames) {
    215      if (entryTypes.Contains(name) && !validEntryTypes.Contains(name)) {
    216        validEntryTypes.AppendElement(name);
    217      }
    218    }
    219 
    220    nsAutoString invalidTypesJoined;
    221    bool addComma = false;
    222    for (const auto& type : entryTypes) {
    223      if (!validEntryTypes.Contains<nsString>(type)) {
    224        if (addComma) {
    225          invalidTypesJoined.AppendLiteral(", ");
    226        }
    227        addComma = true;
    228        invalidTypesJoined.Append(type);
    229      }
    230    }
    231 
    232    if (!invalidTypesJoined.IsEmpty()) {
    233      AutoTArray<nsString, 1> params = {invalidTypesJoined};
    234      mOwner->ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns,
    235                              nsContentUtils::eDOM_PROPERTIES,
    236                              "UnsupportedEntryTypesIgnored"_ns, params);
    237      // (we don't return because we're ignoring and we keep going)
    238    }
    239 
    240    /* 3.3.1.5.3 */
    241    if (validEntryTypes.IsEmpty()) {
    242      mOwner->ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns,
    243                              nsContentUtils::eDOM_PROPERTIES,
    244                              "AllEntryTypesIgnored"_ns);
    245      return;
    246    }
    247 
    248    /*
    249     * Registered or not, we clear out the list of options, and start fresh
    250     * with the one that we are using here. (3.3.1.5.4,5)
    251     */
    252    mOptions.Clear();
    253    mOptions.AppendElement(aOptions);
    254 
    255  } else {
    256    MOZ_ASSERT(mObserverType == ObserverTypeSingle);
    257    bool typeValid = false;
    258    nsString type = maybeType.Value();
    259 
    260    /* 3.3.1.6.2 */
    261    if (StaticPrefs::dom_enable_event_timing()) {
    262      for (const nsLiteralString& name : kValidEventTimingNames) {
    263        if (type == name) {
    264          typeValid = true;
    265          break;
    266        }
    267      }
    268    }
    269    for (const nsLiteralString& name : kValidTypeNames) {
    270      if (type == name) {
    271        typeValid = true;
    272        break;
    273      }
    274    }
    275 
    276    if (StaticPrefs::dom_enable_largest_contentful_paint()) {
    277      if (type == kLargestContentfulPaintName) {
    278        typeValid = true;
    279      }
    280    }
    281 
    282    if (!typeValid) {
    283      AutoTArray<nsString, 1> params = {type};
    284      mOwner->ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns,
    285                              nsContentUtils::eDOM_PROPERTIES,
    286                              "UnsupportedEntryTypesIgnored"_ns, params);
    287      return;
    288    }
    289 
    290    /* 3.3.1.6.4, 3.3.1.6.4 */
    291    bool didUpdateOptionsList = false;
    292    nsTArray<PerformanceObserverInit> updatedOptionsList;
    293    for (auto& option : mOptions) {
    294      if (option.mType.WasPassed() && option.mType.Value() == type) {
    295        updatedOptionsList.AppendElement(aOptions);
    296        didUpdateOptionsList = true;
    297      } else {
    298        updatedOptionsList.AppendElement(option);
    299      }
    300    }
    301    if (!didUpdateOptionsList) {
    302      updatedOptionsList.AppendElement(aOptions);
    303    }
    304    mOptions = std::move(updatedOptionsList);
    305 
    306    /* 3.3.1.6.5 */
    307    if (maybeBuffered.WasPassed() && maybeBuffered.Value()) {
    308      nsTArray<RefPtr<PerformanceEntry>> existingEntries;
    309      mPerformance->GetEntriesByTypeForObserver(type, existingEntries);
    310      if (!existingEntries.IsEmpty()) {
    311        mQueuedEntries.AppendElements(existingEntries);
    312        needQueueNotificationObserverTask = true;
    313      }
    314    }
    315  }
    316  /* Add ourselves to the list of registered performance
    317   * observers, if necessary. (3.3.1.5.4,5; 3.3.1.6.4)
    318   */
    319  mPerformance->AddObserver(this);
    320 
    321  if (needQueueNotificationObserverTask) {
    322    mPerformance->QueueNotificationObserversTask();
    323  }
    324  mConnected = true;
    325 }
    326 
    327 void PerformanceObserver::GetSupportedEntryTypes(
    328    const GlobalObject& aGlobal, JS::MutableHandle<JSObject*> aObject) {
    329  nsTArray<nsString> validTypes;
    330  JS::Rooted<JS::Value> val(aGlobal.Context());
    331 
    332  if (StaticPrefs::dom_enable_event_timing()) {
    333    for (const nsLiteralString& name : kValidEventTimingNames) {
    334      validTypes.AppendElement(name);
    335    }
    336  }
    337 
    338  if (StaticPrefs::dom_enable_largest_contentful_paint()) {
    339    validTypes.AppendElement(u"largest-contentful-paint"_ns);
    340  }
    341  for (const nsLiteralString& name : kValidTypeNames) {
    342    validTypes.AppendElement(name);
    343  }
    344 
    345  if (!ToJSValue(aGlobal.Context(), validTypes, &val)) {
    346    /*
    347     * If this conversion fails, we don't set a result.
    348     * The spec does not allow us to throw an exception.
    349     */
    350    return;
    351  }
    352  aObject.set(&val.toObject());
    353 }
    354 
    355 bool PerformanceObserver::ObservesTypeOfEntry(PerformanceEntry* aEntry) {
    356  for (auto& option : mOptions) {
    357    if (aEntry->ShouldAddEntryToObserverBuffer(option)) {
    358      return true;
    359    }
    360  }
    361  return false;
    362 }
    363 
    364 void PerformanceObserver::Disconnect() {
    365  if (mConnected) {
    366    MOZ_ASSERT(mPerformance);
    367    mPerformance->RemoveObserver(this);
    368    mOptions.Clear();
    369    mConnected = false;
    370  }
    371 }
    372 
    373 void PerformanceObserver::TakeRecords(
    374    nsTArray<RefPtr<PerformanceEntry>>& aRetval) {
    375  MOZ_ASSERT(aRetval.IsEmpty());
    376  aRetval = std::move(mQueuedEntries);
    377 }