tor-browser

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

JSActor.cpp (18536B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
      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 "mozilla/dom/JSActor.h"
      8 
      9 #include "chrome/common/ipc_channel.h"
     10 #include "js/Promise.h"
     11 #include "mozilla/ProfilerMarkers.h"
     12 #include "mozilla/dom/AutoEntryScript.h"
     13 #include "mozilla/dom/ClonedErrorHolder.h"
     14 #include "mozilla/dom/DOMException.h"
     15 #include "mozilla/dom/DOMExceptionBinding.h"
     16 #include "mozilla/dom/JSActorBinding.h"
     17 #include "mozilla/dom/JSActorManager.h"
     18 #include "mozilla/dom/JSIPCValue.h"
     19 #include "mozilla/dom/JSIPCValueUtils.h"
     20 #include "mozilla/dom/MessageManagerBinding.h"
     21 #include "mozilla/dom/PWindowGlobal.h"
     22 #include "mozilla/dom/Promise.h"
     23 #include "mozilla/dom/RootedDictionary.h"
     24 #include "mozilla/dom/ipc/StructuredCloneData.h"
     25 #include "nsFrameMessageManager.h"
     26 #include "nsICrashReporter.h"
     27 #include "xpcprivate.h"
     28 
     29 namespace mozilla::dom {
     30 
     31 struct JSActorMessageMarker {
     32  static constexpr Span<const char> MarkerTypeName() {
     33    return MakeStringSpan("JSActorMessage");
     34  }
     35  static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter& aWriter,
     36                                   const ProfilerString8View& aActorName,
     37                                   const ProfilerString16View& aMessageName) {
     38    aWriter.StringProperty("actor", aActorName);
     39    aWriter.StringProperty("name", NS_ConvertUTF16toUTF8(aMessageName));
     40  }
     41  static MarkerSchema MarkerTypeDisplay() {
     42    using MS = MarkerSchema;
     43    MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable};
     44    schema.AddKeyLabelFormat("actor", "Actor Name", MS::Format::String,
     45                             MS::PayloadFlags::Searchable);
     46    schema.AddKeyLabelFormat("name", "Message Name", MS::Format::String,
     47                             MS::PayloadFlags::Searchable);
     48    schema.SetTooltipLabel("JSActor - {marker.name}");
     49    schema.SetTableLabel("[{marker.data.actor}] {marker.data.name}");
     50    return schema;
     51  }
     52 };
     53 
     54 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JSActor)
     55  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
     56  NS_INTERFACE_MAP_ENTRY(nsISupports)
     57 NS_INTERFACE_MAP_END
     58 
     59 NS_IMPL_CYCLE_COLLECTING_ADDREF(JSActor)
     60 NS_IMPL_CYCLE_COLLECTING_RELEASE(JSActor)
     61 
     62 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(JSActor)
     63 
     64 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(JSActor)
     65  NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal)
     66  NS_IMPL_CYCLE_COLLECTION_UNLINK(mWrappedJS)
     67  tmp->mPendingQueries.Clear();
     68  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
     69 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
     70 
     71 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(JSActor)
     72  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal)
     73  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWrappedJS)
     74  for (const auto& query : tmp->mPendingQueries.Values()) {
     75    CycleCollectionNoteChild(cb, query.mPromise.get(), "Pending Query Promise");
     76  }
     77 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
     78 
     79 JSActor::JSActor(nsISupports* aGlobal) {
     80  mGlobal = do_QueryInterface(aGlobal);
     81  if (!mGlobal) {
     82    mGlobal = xpc::NativeGlobal(xpc::PrivilegedJunkScope());
     83  }
     84 }
     85 
     86 void JSActor::StartDestroy() { mCanSend = false; }
     87 
     88 void JSActor::AfterDestroy() {
     89  mCanSend = false;
     90 
     91  // Take our queries out, in case somehow rejecting promises can trigger
     92  // additions or removals.
     93  const nsTHashMap<nsUint64HashKey, PendingQuery> pendingQueries =
     94      std::move(mPendingQueries);
     95  for (const auto& entry : pendingQueries.Values()) {
     96    nsPrintfCString message(
     97        "Actor '%s' destroyed before query '%s' was resolved", mName.get(),
     98        NS_LossyConvertUTF16toASCII(entry.mMessageName).get());
     99    entry.mPromise->MaybeRejectWithAbortError(message);
    100  }
    101 
    102  InvokeCallback(CallbackFunction::DidDestroy);
    103  ClearManager();
    104 }
    105 
    106 void JSActor::InvokeCallback(CallbackFunction callback) {
    107  MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
    108 
    109  AutoEntryScript aes(GetParentObject(), "JSActor destroy callback");
    110  JSContext* cx = aes.cx();
    111  MozJSActorCallbacks callbacksHolder;
    112  JS::Rooted<JS::Value> val(cx, JS::ObjectOrNullValue(GetWrapper()));
    113  if (NS_WARN_IF(!callbacksHolder.Init(cx, val))) {
    114    return;
    115  }
    116 
    117  // Destroy callback is optional.
    118  if (callback == CallbackFunction::DidDestroy) {
    119    if (callbacksHolder.mDidDestroy.WasPassed()) {
    120      callbacksHolder.mDidDestroy.Value()->Call(this);
    121    }
    122  } else {
    123    if (callbacksHolder.mActorCreated.WasPassed()) {
    124      callbacksHolder.mActorCreated.Value()->Call(this);
    125    }
    126  }
    127 }
    128 
    129 nsresult JSActor::QueryInterfaceActor(const nsIID& aIID, void** aPtr) {
    130  MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
    131  if (!GetWrapperPreserveColor()) {
    132    // If we have no preserved wrapper, we won't implement any interfaces.
    133    return NS_NOINTERFACE;
    134  }
    135 
    136  if (!mWrappedJS) {
    137    AutoEntryScript aes(GetParentObject(), "JSActor query interface");
    138    JSContext* cx = aes.cx();
    139 
    140    JS::Rooted<JSObject*> self(cx, GetWrapper());
    141    JSAutoRealm ar(cx, self);
    142 
    143    RefPtr<nsXPCWrappedJS> wrappedJS;
    144    nsresult rv = nsXPCWrappedJS::GetNewOrUsed(
    145        cx, self, NS_GET_IID(nsISupports), getter_AddRefs(wrappedJS));
    146    NS_ENSURE_SUCCESS(rv, rv);
    147 
    148    mWrappedJS = do_QueryInterface(wrappedJS);
    149    MOZ_ASSERT(mWrappedJS);
    150  }
    151 
    152  return mWrappedJS->QueryInterface(aIID, aPtr);
    153 }
    154 
    155 void JSActor::Init(const nsACString& aName, bool aSendTyped) {
    156  MOZ_ASSERT(mName.IsEmpty(), "Cannot set name twice!");
    157  mName = aName;
    158  mSendTyped = aSendTyped;
    159  InvokeCallback(CallbackFunction::ActorCreated);
    160 }
    161 
    162 void JSActor::ThrowStateErrorForGetter(const char* aName,
    163                                       ErrorResult& aRv) const {
    164  if (mName.IsEmpty()) {
    165    aRv.ThrowInvalidStateError(nsPrintfCString(
    166        "Cannot access property '%s' before actor is initialized", aName));
    167  } else {
    168    aRv.ThrowInvalidStateError(nsPrintfCString(
    169        "Cannot access property '%s' after actor '%s' has been destroyed",
    170        aName, mName.get()));
    171  }
    172 }
    173 
    174 static UniquePtr<ipc::StructuredCloneData> TryClone(
    175    JSContext* aCx, JS::Handle<JS::Value> aValue) {
    176  auto data = mozilla::MakeUnique<ipc::StructuredCloneData>();
    177 
    178  // Try to directly serialize the passed-in data, and return it to our caller.
    179  IgnoredErrorResult rv;
    180  data->Write(aCx, aValue, rv);
    181  if (rv.Failed()) {
    182    // Serialization failed, return `Nothing()` instead.
    183    JS_ClearPendingException(aCx);
    184    data.reset();
    185  }
    186  return data;
    187 }
    188 
    189 static UniquePtr<ipc::StructuredCloneData> CloneJSStack(
    190    JSContext* aCx, JS::Handle<JSObject*> aStack) {
    191  JS::Rooted<JS::Value> stackVal(aCx, JS::ObjectOrNullValue(aStack));
    192  return TryClone(aCx, stackVal);
    193 }
    194 
    195 static UniquePtr<ipc::StructuredCloneData> CaptureJSStack(JSContext* aCx) {
    196  JS::Rooted<JSObject*> stack(aCx, nullptr);
    197  if (JS::IsAsyncStackCaptureEnabledForRealm(aCx) &&
    198      !JS::CaptureCurrentStack(aCx, &stack)) {
    199    JS_ClearPendingException(aCx);
    200  }
    201 
    202  return CloneJSStack(aCx, stack);
    203 }
    204 
    205 void JSActor::SendAsyncMessage(JSContext* aCx, const nsAString& aMessageName,
    206                               JS::Handle<JS::Value> aObj,
    207                               JS::Handle<JS::Value> aTransfers,
    208                               ErrorResult& aRv) {
    209  profiler_add_marker("SendAsyncMessage", geckoprofiler::category::IPC, {},
    210                      JSActorMessageMarker{}, mName, aMessageName);
    211  JSIPCValueUtils::Context cx(aCx, /* aStrict = */ false);
    212  IgnoredErrorResult error;
    213  auto data =
    214      JSIPCValueUtils::FromJSVal(cx, aObj, aTransfers, mSendTyped, error);
    215  if (error.Failed()) {
    216    aRv.ThrowDataCloneError(nsPrintfCString(
    217        "Failed to serialize message '%s::%s'",
    218        NS_LossyConvertUTF16toASCII(aMessageName).get(), mName.get()));
    219    return;
    220  }
    221 
    222  JSActorMessageMeta meta;
    223  meta.actorName() = mName;
    224  meta.messageName() = aMessageName;
    225  meta.kind() = JSActorMessageKind::Message;
    226 
    227  SendRawMessage(meta, std::move(data), CaptureJSStack(aCx), aRv);
    228 }
    229 
    230 already_AddRefed<Promise> JSActor::SendQuery(JSContext* aCx,
    231                                             const nsAString& aMessageName,
    232                                             JS::Handle<JS::Value> aObj,
    233                                             ErrorResult& aRv) {
    234  profiler_add_marker("SendQuery", geckoprofiler::category::IPC, {},
    235                      JSActorMessageMarker{}, mName, aMessageName);
    236  JSIPCValueUtils::Context cx(aCx, /* aStrict = */ false);
    237  IgnoredErrorResult error;
    238  auto data = JSIPCValueUtils::FromJSVal(cx, aObj, mSendTyped, error);
    239  if (error.Failed()) {
    240    aRv.ThrowDataCloneError(nsPrintfCString(
    241        "Failed to serialize message '%s::%s'",
    242        NS_LossyConvertUTF16toASCII(aMessageName).get(), mName.get()));
    243    return nullptr;
    244  }
    245 
    246  nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx);
    247  if (NS_WARN_IF(!global)) {
    248    aRv.ThrowUnknownError("Unable to get current native global");
    249    return nullptr;
    250  }
    251 
    252  RefPtr<Promise> promise = Promise::Create(global, aRv);
    253  if (NS_WARN_IF(aRv.Failed())) {
    254    return nullptr;
    255  }
    256 
    257  JSActorMessageMeta meta;
    258  meta.actorName() = mName;
    259  meta.messageName() = aMessageName;
    260  meta.queryId() = mNextQueryId++;
    261  meta.kind() = JSActorMessageKind::Query;
    262 
    263  SendRawMessage(meta, std::move(data), CaptureJSStack(aCx), aRv);
    264  if (aRv.Failed()) {
    265    return nullptr;
    266  }
    267 
    268  mPendingQueries.InsertOrUpdate(meta.queryId(),
    269                                 PendingQuery{promise, meta.messageName()});
    270 
    271  return promise.forget();
    272 }
    273 
    274 void JSActor::CallReceiveMessage(JSContext* aCx,
    275                                 const JSActorMessageMeta& aMetadata,
    276                                 JS::Handle<JS::Value> aData,
    277                                 JS::MutableHandle<JS::Value> aRetVal,
    278                                 ErrorResult& aRv) {
    279  // The argument which we want to pass to IPC.
    280  RootedDictionary<ReceiveMessageArgument> argument(aCx);
    281  argument.mTarget = this;
    282  argument.mName = aMetadata.messageName();
    283  argument.mData = aData;
    284  argument.mJson = aData;
    285  argument.mSync = false;
    286 
    287  if (GetWrapperPreserveColor()) {
    288    // Invoke the actual callback.
    289    JS::Rooted<JSObject*> global(aCx, JS::GetNonCCWObjectGlobal(GetWrapper()));
    290    RefPtr<MessageListener> messageListener =
    291        new MessageListener(GetWrapper(), global, nullptr, nullptr);
    292    messageListener->ReceiveMessage(argument, aRetVal, aRv,
    293                                    "JSActor receive message",
    294                                    MessageListener::eRethrowExceptions);
    295  } else {
    296    aRv.ThrowTypeError<MSG_NOT_CALLABLE>("Property 'receiveMessage'");
    297  }
    298 }
    299 
    300 void JSActor::ReceiveMessage(JSContext* aCx,
    301                             const JSActorMessageMeta& aMetadata,
    302                             JS::Handle<JS::Value> aData, ErrorResult& aRv) {
    303  MOZ_ASSERT(aMetadata.kind() == JSActorMessageKind::Message);
    304  profiler_add_marker("ReceiveMessage", geckoprofiler::category::IPC, {},
    305                      JSActorMessageMarker{}, mName, aMetadata.messageName());
    306 
    307  JS::Rooted<JS::Value> retval(aCx);
    308  CallReceiveMessage(aCx, aMetadata, aData, &retval, aRv);
    309 }
    310 
    311 void JSActor::ReceiveQuery(JSContext* aCx, const JSActorMessageMeta& aMetadata,
    312                           JS::Handle<JS::Value> aData, ErrorResult& aRv) {
    313  MOZ_ASSERT(aMetadata.kind() == JSActorMessageKind::Query);
    314  profiler_add_marker("ReceiveQuery", geckoprofiler::category::IPC, {},
    315                      JSActorMessageMarker{}, mName, aMetadata.messageName());
    316 
    317  // This promise will be resolved or rejected once the listener has been
    318  // called. Our listener on this promise will then send the reply.
    319  RefPtr<Promise> promise = Promise::Create(GetParentObject(), aRv);
    320  if (NS_WARN_IF(aRv.Failed())) {
    321    return;
    322  }
    323 
    324  RefPtr<QueryHandler> handler = new QueryHandler(this, aMetadata, promise);
    325  promise->AppendNativeHandler(handler);
    326 
    327  ErrorResult error;
    328  JS::Rooted<JS::Value> retval(aCx);
    329  CallReceiveMessage(aCx, aMetadata, aData, &retval, error);
    330 
    331  // If we have a promise, resolve or reject it respectively.
    332  if (error.Failed()) {
    333    if (error.IsUncatchableException()) {
    334      promise->MaybeRejectWithTimeoutError(
    335          "Message handler threw uncatchable exception");
    336    } else {
    337      promise->MaybeReject(std::move(error));
    338    }
    339  } else {
    340    promise->MaybeResolve(retval);
    341  }
    342  error.SuppressException();
    343 }
    344 
    345 void JSActor::ReceiveQueryReply(JSContext* aCx,
    346                                const JSActorMessageMeta& aMetadata,
    347                                JS::Handle<JS::Value> aData, ErrorResult& aRv) {
    348  if (NS_WARN_IF(aMetadata.actorName() != mName)) {
    349    aRv.ThrowUnknownError("Mismatched actor name for query reply");
    350    return;
    351  }
    352 
    353  Maybe<PendingQuery> query = mPendingQueries.Extract(aMetadata.queryId());
    354  if (NS_WARN_IF(!query)) {
    355    aRv.ThrowUnknownError("Received reply for non-pending query");
    356    return;
    357  }
    358 
    359  profiler_add_marker("ReceiveQueryReply", geckoprofiler::category::IPC, {},
    360                      JSActorMessageMarker{}, mName, aMetadata.messageName());
    361 
    362  Promise* promise = query->mPromise;
    363  JSAutoRealm ar(aCx, promise->PromiseObj());
    364  JS::RootedValue data(aCx, aData);
    365  if (NS_WARN_IF(!JS_WrapValue(aCx, &data))) {
    366    aRv.NoteJSContextException(aCx);
    367    return;
    368  }
    369 
    370  if (aMetadata.kind() == JSActorMessageKind::QueryResolve) {
    371    promise->MaybeResolve(data);
    372  } else {
    373    promise->MaybeReject(data);
    374  }
    375 }
    376 
    377 void JSActor::SendRawMessageInProcess(
    378    const JSActorMessageMeta& aMeta, JSIPCValue&& aData,
    379    UniquePtr<ipc::StructuredCloneData> aStack,
    380    OtherSideCallback&& aGetOtherSide) {
    381  MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
    382  NS_DispatchToMainThread(NS_NewRunnableFunction(
    383      "JSActor Async Message",
    384      [aMeta, data{std::move(aData)}, stack{std::move(aStack)},
    385       getOtherSide{std::move(aGetOtherSide)}]() mutable {
    386        if (RefPtr<JSActorManager> otherSide = getOtherSide()) {
    387          otherSide->ReceiveRawMessage(aMeta, std::move(data),
    388                                       std::move(stack));
    389        }
    390      }));
    391 }
    392 
    393 // Native handler for our generated promise which is used to handle Queries and
    394 // send the reply when their promises have been resolved.
    395 JSActor::QueryHandler::QueryHandler(JSActor* aActor,
    396                                    const JSActorMessageMeta& aMetadata,
    397                                    Promise* aPromise)
    398    : mActor(aActor),
    399      mPromise(aPromise),
    400      mMessageName(aMetadata.messageName()),
    401      mQueryId(aMetadata.queryId()) {}
    402 
    403 void JSActor::QueryHandler::RejectedCallback(JSContext* aCx,
    404                                             JS::Handle<JS::Value> aValue,
    405                                             ErrorResult& aRv) {
    406  if (!mActor) {
    407    // Make sure that this rejection is reported. See comment below.
    408    if (!JS::CallOriginalPromiseReject(aCx, aValue)) {
    409      JS_ClearPendingException(aCx);
    410    }
    411    return;
    412  }
    413 
    414  JS::Rooted<JS::Value> value(aCx, aValue);
    415  if (value.isObject()) {
    416    JS::Rooted<JSObject*> error(aCx, &value.toObject());
    417    if (UniquePtr<ClonedErrorHolder> ceh =
    418            ClonedErrorHolder::Create(aCx, error, IgnoreErrors())) {
    419      if (!ToJSValue(aCx, std::move(ceh), &value)) {
    420        JS_ClearPendingException(aCx);
    421      }
    422    } else {
    423      JS_ClearPendingException(aCx);
    424    }
    425  }
    426 
    427  // The only valid type a QueryReject message can have is "any", so serialize
    428  // it as an untyped value, and never log anything for it. Ideally, we would
    429  // require that this is an error object (bug 1907175).
    430  JSIPCValueUtils::Context cx(aCx);
    431  IgnoredErrorResult error;
    432  auto data =
    433      JSIPCValueUtils::FromJSVal(cx, value, /* aSendTyped = */ false, error);
    434 
    435  if (error.Failed()) {
    436    // Failed to clone the rejection value. Make sure that this
    437    // rejection is reported, despite being "handled". This is done by
    438    // creating a new promise in the rejected state, and throwing it
    439    // away. This will be reported as an unhandled rejected promise.
    440    if (!JS::CallOriginalPromiseReject(aCx, aValue)) {
    441      JS_ClearPendingException(aCx);
    442    }
    443 
    444    // Unlike other cases, we want to send a reject reply message even if
    445    // serialization failed, so send the JS value undefined, rather than
    446    // returning.
    447    data = JSIPCValue(void_t());
    448  }
    449 
    450  const JSActorMessageKind kind = JSActorMessageKind::QueryReject;
    451  SendReply(aCx, kind, std::move(data));
    452 }
    453 
    454 void JSActor::QueryHandler::ResolvedCallback(JSContext* aCx,
    455                                             JS::Handle<JS::Value> aValue,
    456                                             ErrorResult& aRv) {
    457  if (!mActor) {
    458    return;
    459  }
    460 
    461  JSIPCValueUtils::Context cx(aCx);
    462  IgnoredErrorResult error;
    463  auto data = JSIPCValueUtils::FromJSVal(cx, aValue, mActor->mSendTyped, error);
    464  if (error.Failed()) {
    465    nsAutoCString msg;
    466    msg.Append(mActor->Name());
    467    msg.Append(':');
    468    msg.Append(NS_LossyConvertUTF16toASCII(mMessageName));
    469    msg.AppendLiteral(": message reply cannot be cloned.");
    470 
    471    auto exc = MakeRefPtr<Exception>(msg, NS_ERROR_FAILURE, "DataCloneError"_ns,
    472                                     nullptr, nullptr);
    473 
    474    JS::Rooted<JS::Value> val(aCx);
    475    if (ToJSValue(aCx, exc, &val)) {
    476      RejectedCallback(aCx, val, aRv);
    477    } else {
    478      JS_ClearPendingException(aCx);
    479    }
    480    return;
    481  }
    482 
    483  const JSActorMessageKind kind = JSActorMessageKind::QueryResolve;
    484  SendReply(aCx, kind, std::move(data));
    485 }
    486 
    487 void JSActor::QueryHandler::SendReply(JSContext* aCx, JSActorMessageKind aKind,
    488                                      JSIPCValue&& aData) {
    489  MOZ_ASSERT(mActor);
    490  profiler_add_marker("SendQueryReply", geckoprofiler::category::IPC, {},
    491                      JSActorMessageMarker{}, mActor->Name(), mMessageName);
    492 
    493  JSActorMessageMeta meta;
    494  meta.actorName() = mActor->Name();
    495  meta.messageName() = mMessageName;
    496  meta.queryId() = mQueryId;
    497  meta.kind() = aKind;
    498 
    499  JS::Rooted<JSObject*> promise(aCx, mPromise->PromiseObj());
    500  JS::Rooted<JSObject*> stack(aCx, JS::GetPromiseResolutionSite(promise));
    501 
    502  mActor->SendRawMessage(meta, std::move(aData), CloneJSStack(aCx, stack),
    503                         IgnoreErrors());
    504  mActor = nullptr;
    505  mPromise = nullptr;
    506 }
    507 
    508 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JSActor::QueryHandler)
    509  NS_INTERFACE_MAP_ENTRY(nsISupports)
    510 NS_INTERFACE_MAP_END
    511 
    512 NS_IMPL_CYCLE_COLLECTING_ADDREF(JSActor::QueryHandler)
    513 NS_IMPL_CYCLE_COLLECTING_RELEASE(JSActor::QueryHandler)
    514 
    515 NS_IMPL_CYCLE_COLLECTION(JSActor::QueryHandler, mActor, mPromise)
    516 
    517 }  // namespace mozilla::dom