Bug 1812499 [wpt PR 38184] - Simplify handling of name-to-subdir mapping in canvas...
[gecko.git] / dom / u2f / U2F.cpp
blobc5ee8c41c59a925bb2f4fede80e9967e64e715d1
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 file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/U2F.h"
8 #include "mozilla/dom/WebCryptoCommon.h"
9 #include "mozilla/ipc/PBackgroundChild.h"
10 #include "mozilla/ipc/BackgroundChild.h"
11 #include "mozilla/dom/Document.h"
12 #include "mozilla/dom/WebAuthnTransactionChild.h"
13 #include "mozilla/dom/WebAuthnUtil.h"
14 #include "nsContentUtils.h"
15 #include "nsNetUtil.h"
16 #include "nsURLParsers.h"
18 #ifdef OS_WIN
19 # include "WinWebAuthnManager.h"
20 #endif
22 using namespace mozilla::ipc;
24 class JSJitInfo;
26 // Forward decl because of nsHTMLDocument.h's complex dependency on
27 // /layout/style
28 class nsHTMLDocument {
29 public:
30 bool IsRegistrableDomainSuffixOfOrEqualTo(const nsAString& aHostSuffixString,
31 const nsACString& aOrigHost);
34 namespace mozilla::dom {
36 constexpr auto kFinishEnrollment = u"navigator.id.finishEnrollment"_ns;
37 constexpr auto kGetAssertion = u"navigator.id.getAssertion"_ns;
39 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(U2F)
40 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
41 NS_INTERFACE_MAP_END_INHERITING(WebAuthnManagerBase)
43 NS_IMPL_ADDREF_INHERITED(U2F, WebAuthnManagerBase)
44 NS_IMPL_RELEASE_INHERITED(U2F, WebAuthnManagerBase)
46 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(U2F)
47 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(U2F, WebAuthnManagerBase)
48 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTransaction)
49 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
50 tmp->mTransaction.reset();
51 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
52 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(U2F, WebAuthnManagerBase)
53 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTransaction)
54 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
56 /***********************************************************************
57 * Utility Functions
58 **********************************************************************/
60 static ErrorCode ConvertNSResultToErrorCode(const nsresult& aError) {
61 if (aError == NS_ERROR_DOM_TIMEOUT_ERR) {
62 return ErrorCode::TIMEOUT;
64 /* Emitted by U2F{Soft,HID}TokenManager when we really mean ineligible */
65 if (aError == NS_ERROR_DOM_INVALID_STATE_ERR) {
66 return ErrorCode::DEVICE_INELIGIBLE;
68 return ErrorCode::OTHER_ERROR;
71 static uint32_t AdjustedTimeoutMillis(
72 const Optional<Nullable<int32_t>>& opt_aSeconds) {
73 uint32_t adjustedTimeoutMillis = 30000u;
74 if (opt_aSeconds.WasPassed() && !opt_aSeconds.Value().IsNull()) {
75 adjustedTimeoutMillis = opt_aSeconds.Value().Value() * 1000u;
76 adjustedTimeoutMillis = std::max(15000u, adjustedTimeoutMillis);
77 adjustedTimeoutMillis = std::min(120000u, adjustedTimeoutMillis);
79 return adjustedTimeoutMillis;
82 static nsresult AssembleClientData(const nsAString& aOrigin,
83 const nsAString& aTyp,
84 const nsAString& aChallenge,
85 /* out */ nsString& aClientData) {
86 MOZ_ASSERT(NS_IsMainThread());
87 U2FClientData clientDataObject;
88 clientDataObject.mTyp.Construct(aTyp); // "Typ" from the U2F specification
89 clientDataObject.mChallenge.Construct(aChallenge);
90 clientDataObject.mOrigin.Construct(aOrigin);
92 if (NS_WARN_IF(!clientDataObject.ToJSON(aClientData))) {
93 return NS_ERROR_FAILURE;
96 return NS_OK;
99 static void RegisteredKeysToScopedCredentialList(
100 const nsAString& aAppId, const nsTArray<RegisteredKey>& aKeys,
101 nsTArray<WebAuthnScopedCredential>& aList) {
102 for (const RegisteredKey& key : aKeys) {
103 // Check for required attributes
104 if (!key.mVersion.WasPassed() || !key.mKeyHandle.WasPassed() ||
105 key.mVersion.Value() != kRequiredU2FVersion) {
106 continue;
109 // If this key's mAppId doesn't match the invocation, we can't handle it.
110 if (key.mAppId.WasPassed() && !key.mAppId.Value().Equals(aAppId)) {
111 continue;
114 CryptoBuffer keyHandle;
115 nsresult rv = keyHandle.FromJwkBase64(key.mKeyHandle.Value());
116 if (NS_WARN_IF(NS_FAILED(rv))) {
117 continue;
120 WebAuthnScopedCredential c;
121 c.id() = keyHandle;
122 aList.AppendElement(c);
126 /***********************************************************************
127 * U2F JavaScript API Implementation
128 **********************************************************************/
130 U2F::~U2F() {
131 MOZ_ASSERT(NS_IsMainThread());
133 if (mTransaction.isSome()) {
134 ClearTransaction();
137 if (mChild) {
138 RefPtr<WebAuthnTransactionChild> c;
139 mChild.swap(c);
140 c->Disconnect();
144 void U2F::Init(ErrorResult& aRv) {
145 MOZ_ASSERT(mParent);
147 nsCOMPtr<Document> doc = mParent->GetDoc();
148 MOZ_ASSERT(doc);
149 if (!doc) {
150 aRv.Throw(NS_ERROR_FAILURE);
151 return;
154 nsIPrincipal* principal = doc->NodePrincipal();
155 aRv = nsContentUtils::GetUTFOrigin(principal, mOrigin);
156 if (NS_WARN_IF(aRv.Failed())) {
157 return;
160 if (NS_WARN_IF(mOrigin.IsEmpty())) {
161 aRv.Throw(NS_ERROR_FAILURE);
162 return;
166 /* virtual */
167 JSObject* U2F::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
168 return U2F_Binding::Wrap(aCx, this, aGivenProto);
171 template <typename T, typename C>
172 void U2F::ExecuteCallback(T& aResp, nsMainThreadPtrHandle<C>& aCb) {
173 MOZ_ASSERT(NS_IsMainThread());
174 MOZ_ASSERT(aCb);
176 ErrorResult error;
177 RefPtr<C> temp = aCb.get(); // Make sure it stays alive
178 temp->Call(aResp, error);
179 NS_WARNING_ASSERTION(!error.Failed(), "dom::U2F::Promise callback failed");
180 error.SuppressException(); // Useful exceptions already emitted
183 void U2F::Register(const nsAString& aAppId,
184 const Sequence<RegisterRequest>& aRegisterRequests,
185 const Sequence<RegisteredKey>& aRegisteredKeys,
186 U2FRegisterCallback& aCallback,
187 const Optional<Nullable<int32_t>>& opt_aTimeoutSeconds,
188 ErrorResult& aRv) {
189 MOZ_ASSERT(NS_IsMainThread());
191 nsMainThreadPtrHandle<U2FRegisterCallback> callback(
192 new nsMainThreadPtrHolder<U2FRegisterCallback>("U2F::Register::callback",
193 &aCallback));
195 // Ensure we have a callback.
196 if (NS_WARN_IF(!callback)) {
197 return;
200 if (mTransaction.isSome()) {
201 // If there hasn't been a visibility change during the current
202 // transaction, then let's let that one complete rather than
203 // cancelling it on a subsequent call.
204 if (!mTransaction.ref().mVisibilityChanged) {
205 RegisterResponse response;
206 response.mErrorCode.Construct(
207 static_cast<uint32_t>(ErrorCode::OTHER_ERROR));
208 ExecuteCallback(response, callback);
209 return;
212 // Otherwise, the user may well have clicked away, so let's
213 // abort the old transaction and take over control from here.
214 CancelTransaction(NS_ERROR_ABORT);
217 // Evaluate the AppID
218 nsString adjustedAppId(aAppId);
219 if (!EvaluateAppID(mParent, mOrigin, adjustedAppId)) {
220 RegisterResponse response;
221 response.mErrorCode.Construct(
222 static_cast<uint32_t>(ErrorCode::BAD_REQUEST));
223 ExecuteCallback(response, callback);
224 return;
227 nsAutoString clientDataJSON;
229 // Pick the first valid RegisterRequest; we can only work with one.
230 CryptoBuffer challenge;
231 for (const RegisterRequest& req : aRegisterRequests) {
232 if (!req.mChallenge.WasPassed() || !req.mVersion.WasPassed() ||
233 req.mVersion.Value() != kRequiredU2FVersion) {
234 continue;
236 if (!challenge.Assign(NS_ConvertUTF16toUTF8(req.mChallenge.Value()))) {
237 continue;
240 nsresult rv = AssembleClientData(mOrigin, kFinishEnrollment,
241 req.mChallenge.Value(), clientDataJSON);
242 if (NS_WARN_IF(NS_FAILED(rv))) {
243 continue;
247 // Did we not get a valid RegisterRequest? Abort.
248 if (clientDataJSON.IsEmpty()) {
249 RegisterResponse response;
250 response.mErrorCode.Construct(
251 static_cast<uint32_t>(ErrorCode::BAD_REQUEST));
252 ExecuteCallback(response, callback);
253 return;
256 // Build the exclusion list, if any
257 nsTArray<WebAuthnScopedCredential> excludeList;
258 RegisteredKeysToScopedCredentialList(adjustedAppId, aRegisteredKeys,
259 excludeList);
261 if (!MaybeCreateBackgroundActor()) {
262 RegisterResponse response;
263 response.mErrorCode.Construct(
264 static_cast<uint32_t>(ErrorCode::OTHER_ERROR));
265 ExecuteCallback(response, callback);
266 return;
269 #ifdef OS_WIN
270 if (!WinWebAuthnManager::AreWebAuthNApisAvailable()) {
271 ListenForVisibilityEvents();
273 #else
274 ListenForVisibilityEvents();
275 #endif
277 NS_ConvertUTF16toUTF8 clientData(clientDataJSON);
278 uint32_t adjustedTimeoutMillis = AdjustedTimeoutMillis(opt_aTimeoutSeconds);
280 BrowsingContext* context = mParent->GetBrowsingContext();
281 if (!context) {
282 RegisterResponse response;
283 response.mErrorCode.Construct(
284 static_cast<uint32_t>(ErrorCode::OTHER_ERROR));
285 ExecuteCallback(response, callback);
286 return;
289 WebAuthnMakeCredentialInfo info(mOrigin, adjustedAppId, challenge, clientData,
290 adjustedTimeoutMillis, excludeList,
291 Nothing(), /* no extra info for U2F */
292 context->Id());
294 MOZ_ASSERT(mTransaction.isNothing());
295 mTransaction = Some(U2FTransaction(AsVariant(callback)));
296 mChild->SendRequestRegister(mTransaction.ref().mId, info);
299 using binding_detail::GenericMethod;
300 using binding_detail::NormalThisPolicy;
301 using binding_detail::ThrowExceptions;
303 // register_impl_methodinfo is generated by bindings.
304 namespace U2F_Binding {
305 extern const JSJitInfo register_impl_methodinfo;
306 } // namespace U2F_Binding
308 // We have 4 non-optional args.
309 static const JSFunctionSpec register_spec = JS_FNSPEC(
310 "register", (GenericMethod<NormalThisPolicy, ThrowExceptions>),
311 &U2F_Binding::register_impl_methodinfo, 4, JSPROP_ENUMERATE, nullptr);
313 void U2F::GetRegister(JSContext* aCx,
314 JS::MutableHandle<JSObject*> aRegisterFunc,
315 ErrorResult& aRv) {
316 JSFunction* fun = JS::NewFunctionFromSpec(aCx, &register_spec);
317 if (!fun) {
318 aRv.NoteJSContextException(aCx);
319 return;
322 aRegisterFunc.set(JS_GetFunctionObject(fun));
325 void U2F::FinishMakeCredential(const uint64_t& aTransactionId,
326 const WebAuthnMakeCredentialResult& aResult) {
327 MOZ_ASSERT(NS_IsMainThread());
329 // Check for a valid transaction.
330 if (mTransaction.isNothing() || mTransaction.ref().mId != aTransactionId) {
331 return;
334 if (NS_WARN_IF(!mTransaction.ref().HasRegisterCallback())) {
335 RejectTransaction(NS_ERROR_ABORT);
336 return;
339 // A CTAP2 response.
340 if (aResult.RegistrationData().Length() == 0) {
341 RejectTransaction(NS_ERROR_ABORT);
342 return;
345 CryptoBuffer clientDataBuf;
346 if (NS_WARN_IF(!clientDataBuf.Assign(aResult.ClientDataJSON()))) {
347 RejectTransaction(NS_ERROR_ABORT);
348 return;
351 CryptoBuffer regBuf;
352 if (NS_WARN_IF(!regBuf.Assign(aResult.RegistrationData()))) {
353 RejectTransaction(NS_ERROR_ABORT);
354 return;
357 nsString clientDataBase64;
358 nsString registrationDataBase64;
359 nsresult rvClientData = clientDataBuf.ToJwkBase64(clientDataBase64);
360 nsresult rvRegistrationData = regBuf.ToJwkBase64(registrationDataBase64);
362 if (NS_WARN_IF(NS_FAILED(rvClientData)) ||
363 NS_WARN_IF(NS_FAILED(rvRegistrationData))) {
364 RejectTransaction(NS_ERROR_ABORT);
365 return;
368 // Assemble a response object to return
369 RegisterResponse response;
370 response.mVersion.Construct(kRequiredU2FVersion);
371 response.mClientData.Construct(clientDataBase64);
372 response.mRegistrationData.Construct(registrationDataBase64);
373 response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OK));
375 // Keep the callback pointer alive.
376 nsMainThreadPtrHandle<U2FRegisterCallback> callback(
377 mTransaction.ref().GetRegisterCallback());
379 ClearTransaction();
380 ExecuteCallback(response, callback);
383 void U2F::Sign(const nsAString& aAppId, const nsAString& aChallenge,
384 const Sequence<RegisteredKey>& aRegisteredKeys,
385 U2FSignCallback& aCallback,
386 const Optional<Nullable<int32_t>>& opt_aTimeoutSeconds,
387 ErrorResult& aRv) {
388 MOZ_ASSERT(NS_IsMainThread());
390 nsMainThreadPtrHandle<U2FSignCallback> callback(
391 new nsMainThreadPtrHolder<U2FSignCallback>("U2F::Sign::callback",
392 &aCallback));
394 // Ensure we have a callback.
395 if (NS_WARN_IF(!callback)) {
396 return;
399 if (mTransaction.isSome()) {
400 // If there hasn't been a visibility change during the current
401 // transaction, then let's let that one complete rather than
402 // cancelling it on a subsequent call.
403 if (!mTransaction.ref().mVisibilityChanged) {
404 SignResponse response;
405 response.mErrorCode.Construct(
406 static_cast<uint32_t>(ErrorCode::OTHER_ERROR));
407 ExecuteCallback(response, callback);
408 return;
411 // Otherwise, the user may well have clicked away, so let's
412 // abort the old transaction and take over control from here.
413 CancelTransaction(NS_ERROR_ABORT);
416 // Evaluate the AppID
417 nsString adjustedAppId(aAppId);
418 if (!EvaluateAppID(mParent, mOrigin, adjustedAppId)) {
419 SignResponse response;
420 response.mErrorCode.Construct(
421 static_cast<uint32_t>(ErrorCode::BAD_REQUEST));
422 ExecuteCallback(response, callback);
423 return;
426 // Produce the AppParam from the current AppID
427 nsCString cAppId = NS_ConvertUTF16toUTF8(adjustedAppId);
429 nsAutoString clientDataJSON;
430 nsresult rv =
431 AssembleClientData(mOrigin, kGetAssertion, aChallenge, clientDataJSON);
432 if (NS_WARN_IF(NS_FAILED(rv))) {
433 SignResponse response;
434 response.mErrorCode.Construct(
435 static_cast<uint32_t>(ErrorCode::BAD_REQUEST));
436 ExecuteCallback(response, callback);
437 return;
440 CryptoBuffer challenge;
441 if (!challenge.Assign(NS_ConvertUTF16toUTF8(aChallenge))) {
442 SignResponse response;
443 response.mErrorCode.Construct(
444 static_cast<uint32_t>(ErrorCode::OTHER_ERROR));
445 ExecuteCallback(response, callback);
446 return;
449 // Build the key list, if any
450 nsTArray<WebAuthnScopedCredential> permittedList;
451 RegisteredKeysToScopedCredentialList(adjustedAppId, aRegisteredKeys,
452 permittedList);
454 if (!MaybeCreateBackgroundActor()) {
455 SignResponse response;
456 response.mErrorCode.Construct(
457 static_cast<uint32_t>(ErrorCode::OTHER_ERROR));
458 ExecuteCallback(response, callback);
459 return;
462 #ifdef OS_WIN
463 if (!WinWebAuthnManager::AreWebAuthNApisAvailable()) {
464 ListenForVisibilityEvents();
466 #else
467 ListenForVisibilityEvents();
468 #endif
470 // Always blank for U2F
471 nsTArray<WebAuthnExtension> extensions;
473 NS_ConvertUTF16toUTF8 clientData(clientDataJSON);
474 uint32_t adjustedTimeoutMillis = AdjustedTimeoutMillis(opt_aTimeoutSeconds);
476 BrowsingContext* context = mParent->GetBrowsingContext();
477 if (!context) {
478 SignResponse response;
479 response.mErrorCode.Construct(
480 static_cast<uint32_t>(ErrorCode::OTHER_ERROR));
481 ExecuteCallback(response, callback);
482 return;
485 WebAuthnGetAssertionInfo info(mOrigin, adjustedAppId, challenge, clientData,
486 adjustedTimeoutMillis, permittedList,
487 Nothing(), /* no extra info for U2F */
488 context->Id());
490 MOZ_ASSERT(mTransaction.isNothing());
491 mTransaction = Some(U2FTransaction(AsVariant(callback)));
492 mChild->SendRequestSign(mTransaction.ref().mId, info);
495 // sign_impl_methodinfo is generated by bindings.
496 namespace U2F_Binding {
497 extern const JSJitInfo sign_impl_methodinfo;
498 } // namespace U2F_Binding
500 // We have 4 non-optional args.
501 static const JSFunctionSpec sign_spec =
502 JS_FNSPEC("sign", (GenericMethod<NormalThisPolicy, ThrowExceptions>),
503 &U2F_Binding::sign_impl_methodinfo, 4, JSPROP_ENUMERATE, nullptr);
505 void U2F::GetSign(JSContext* aCx, JS::MutableHandle<JSObject*> aSignFunc,
506 ErrorResult& aRv) {
507 JSFunction* fun = JS::NewFunctionFromSpec(aCx, &sign_spec);
508 if (!fun) {
509 aRv.NoteJSContextException(aCx);
510 return;
513 aSignFunc.set(JS_GetFunctionObject(fun));
516 void U2F::FinishGetAssertion(const uint64_t& aTransactionId,
517 const WebAuthnGetAssertionResult& aResult) {
518 MOZ_ASSERT(NS_IsMainThread());
520 // Check for a valid transaction.
521 if (mTransaction.isNothing() || mTransaction.ref().mId != aTransactionId) {
522 return;
525 if (NS_WARN_IF(!mTransaction.ref().HasSignCallback())) {
526 RejectTransaction(NS_ERROR_ABORT);
527 return;
530 // A CTAP2 response.
531 if (aResult.SignatureData().Length() == 0) {
532 RejectTransaction(NS_ERROR_ABORT);
533 return;
536 CryptoBuffer clientDataBuf;
537 if (NS_WARN_IF(!clientDataBuf.Assign(aResult.ClientDataJSON()))) {
538 RejectTransaction(NS_ERROR_ABORT);
539 return;
542 CryptoBuffer credBuf;
543 if (NS_WARN_IF(!credBuf.Assign(aResult.KeyHandle()))) {
544 RejectTransaction(NS_ERROR_ABORT);
545 return;
548 CryptoBuffer sigBuf;
549 if (NS_WARN_IF(!sigBuf.Assign(aResult.SignatureData()))) {
550 RejectTransaction(NS_ERROR_ABORT);
551 return;
554 // Assemble a response object to return
555 nsString clientDataBase64;
556 nsString signatureDataBase64;
557 nsString keyHandleBase64;
558 nsresult rvClientData = clientDataBuf.ToJwkBase64(clientDataBase64);
559 nsresult rvSignatureData = sigBuf.ToJwkBase64(signatureDataBase64);
560 nsresult rvKeyHandle = credBuf.ToJwkBase64(keyHandleBase64);
561 if (NS_WARN_IF(NS_FAILED(rvClientData)) ||
562 NS_WARN_IF(NS_FAILED(rvSignatureData) ||
563 NS_WARN_IF(NS_FAILED(rvKeyHandle)))) {
564 RejectTransaction(NS_ERROR_ABORT);
565 return;
568 SignResponse response;
569 response.mKeyHandle.Construct(keyHandleBase64);
570 response.mClientData.Construct(clientDataBase64);
571 response.mSignatureData.Construct(signatureDataBase64);
572 response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OK));
574 // Keep the callback pointer alive.
575 nsMainThreadPtrHandle<U2FSignCallback> callback(
576 mTransaction.ref().GetSignCallback());
578 ClearTransaction();
579 ExecuteCallback(response, callback);
582 void U2F::ClearTransaction() {
583 if (!mTransaction.isNothing()) {
584 StopListeningForVisibilityEvents();
587 mTransaction.reset();
590 void U2F::RejectTransaction(const nsresult& aError) {
591 if (NS_WARN_IF(mTransaction.isNothing())) {
592 return;
595 StopListeningForVisibilityEvents();
597 // Clear out mTransaction before calling ExecuteCallback() below to allow
598 // reentrancy from microtask checkpoints.
599 Maybe<U2FTransaction> maybeTransaction(std::move(mTransaction));
600 MOZ_ASSERT(mTransaction.isNothing() && maybeTransaction.isSome());
602 U2FTransaction& transaction = maybeTransaction.ref();
603 ErrorCode code = ConvertNSResultToErrorCode(aError);
605 if (transaction.HasRegisterCallback()) {
606 RegisterResponse response;
607 response.mErrorCode.Construct(static_cast<uint32_t>(code));
608 // MOZ_KnownLive because "transaction" lives on the stack.
609 ExecuteCallback(response, MOZ_KnownLive(transaction.GetRegisterCallback()));
612 if (transaction.HasSignCallback()) {
613 SignResponse response;
614 response.mErrorCode.Construct(static_cast<uint32_t>(code));
615 // MOZ_KnownLive because "transaction" lives on the stack.
616 ExecuteCallback(response, MOZ_KnownLive(transaction.GetSignCallback()));
620 void U2F::CancelTransaction(const nsresult& aError) {
621 if (!NS_WARN_IF(!mChild || mTransaction.isNothing())) {
622 mChild->SendRequestCancel(mTransaction.ref().mId);
625 RejectTransaction(aError);
628 void U2F::RequestAborted(const uint64_t& aTransactionId,
629 const nsresult& aError) {
630 MOZ_ASSERT(NS_IsMainThread());
632 if (mTransaction.isSome() && mTransaction.ref().mId == aTransactionId) {
633 RejectTransaction(aError);
637 void U2F::HandleVisibilityChange() {
638 if (mTransaction.isSome()) {
639 mTransaction.ref().mVisibilityChanged = true;
643 } // namespace mozilla::dom