summaryrefslogtreecommitdiffstats
path: root/dom/performance/LargestContentfulPaint.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /dom/performance/LargestContentfulPaint.cpp
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/performance/LargestContentfulPaint.cpp')
-rw-r--r--dom/performance/LargestContentfulPaint.cpp565
1 files changed, 565 insertions, 0 deletions
diff --git a/dom/performance/LargestContentfulPaint.cpp b/dom/performance/LargestContentfulPaint.cpp
new file mode 100644
index 0000000000..3382a2fbad
--- /dev/null
+++ b/dom/performance/LargestContentfulPaint.cpp
@@ -0,0 +1,565 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "mozilla/dom/Element.h"
+#include "nsContentUtils.h"
+#include "nsLayoutUtils.h"
+#include "nsRFPService.h"
+#include "Performance.h"
+#include "imgRequest.h"
+#include "PerformanceMainThread.h"
+#include "LargestContentfulPaint.h"
+
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/DOMIntersectionObserver.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Element.h"
+
+#include "mozilla/PresShell.h"
+#include "mozilla/Logging.h"
+#include "mozilla/nsVideoFrame.h"
+
+namespace mozilla::dom {
+
+static LazyLogModule gLCPLogging("LargestContentfulPaint");
+
+#define LOG(...) MOZ_LOG(gLCPLogging, LogLevel::Debug, (__VA_ARGS__))
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(LargestContentfulPaint, PerformanceEntry,
+ mPerformance, mURI, mElement)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(LargestContentfulPaint)
+NS_INTERFACE_MAP_END_INHERITING(PerformanceEntry)
+
+NS_IMPL_ADDREF_INHERITED(LargestContentfulPaint, PerformanceEntry)
+NS_IMPL_RELEASE_INHERITED(LargestContentfulPaint, PerformanceEntry)
+
+static double GetAreaInDoublePixelsFromAppUnits(const nsSize& aSize) {
+ return NSAppUnitsToDoublePixels(aSize.Width(), AppUnitsPerCSSPixel()) *
+ NSAppUnitsToDoublePixels(aSize.Height(), AppUnitsPerCSSPixel());
+}
+
+static double GetAreaInDoublePixelsFromAppUnits(const nsRect& aRect) {
+ return NSAppUnitsToDoublePixels(aRect.Width(), AppUnitsPerCSSPixel()) *
+ NSAppUnitsToDoublePixels(aRect.Height(), AppUnitsPerCSSPixel());
+}
+
+static DOMHighResTimeStamp GetReducedTimePrecisionDOMHighRes(
+ Performance* aPerformance, const TimeStamp& aRawTimeStamp) {
+ MOZ_ASSERT(aPerformance);
+ DOMHighResTimeStamp rawValue =
+ aPerformance->GetDOMTiming()->TimeStampToDOMHighRes(aRawTimeStamp);
+ return nsRFPService::ReduceTimePrecisionAsMSecs(
+ rawValue, aPerformance->GetRandomTimelineSeed(),
+ aPerformance->GetRTPCallerType());
+}
+
+ImagePendingRendering::ImagePendingRendering(
+ const LCPImageEntryKey& aLCPImageEntryKey, const TimeStamp& aLoadTime)
+ : mLCPImageEntryKey(aLCPImageEntryKey), mLoadTime(aLoadTime) {}
+
+LargestContentfulPaint::LargestContentfulPaint(
+ PerformanceMainThread* aPerformance, const TimeStamp& aRenderTime,
+ const Maybe<TimeStamp>& aLoadTime, const unsigned long aSize, nsIURI* aURI,
+ Element* aElement, const Maybe<const LCPImageEntryKey>& aLCPImageEntryKey,
+ bool aShouldExposeRenderTime)
+ : PerformanceEntry(aPerformance->GetParentObject(), u""_ns,
+ kLargestContentfulPaintName),
+ mPerformance(aPerformance),
+ mRenderTime(aRenderTime),
+ mLoadTime(aLoadTime),
+ mShouldExposeRenderTime(aShouldExposeRenderTime),
+ mSize(aSize),
+ mURI(aURI),
+ mLCPImageEntryKey(aLCPImageEntryKey) {
+ MOZ_ASSERT(mPerformance);
+ MOZ_ASSERT(aElement);
+ // The element could be a pseudo-element
+ if (aElement->ChromeOnlyAccess()) {
+ mElement = do_GetWeakReference(Element::FromNodeOrNull(
+ aElement->FindFirstNonChromeOnlyAccessContent()));
+ } else {
+ mElement = do_GetWeakReference(aElement);
+ }
+
+ if (const Element* element = GetElement()) {
+ mId = element->GetID();
+ }
+}
+
+JSObject* LargestContentfulPaint::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return LargestContentfulPaint_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+Element* LargestContentfulPaint::GetElement() const {
+ nsCOMPtr<Element> element = do_QueryReferent(mElement);
+ return element ? nsContentUtils::GetAnElementForTiming(
+ element, element->GetComposedDoc(), nullptr)
+ : nullptr;
+}
+
+void LargestContentfulPaint::BufferEntryIfNeeded() {
+ MOZ_ASSERT(mLCPImageEntryKey.isNothing());
+ mPerformance->BufferLargestContentfulPaintEntryIfNeeded(this);
+}
+
+/* static*/
+bool LCPHelpers::IsQualifiedImageRequest(imgRequest* aRequest,
+ Element* aContainingElement) {
+ MOZ_ASSERT(aContainingElement);
+ if (!aRequest) {
+ return false;
+ }
+
+ if (aRequest->IsChrome()) {
+ return false;
+ }
+
+ if (!aContainingElement->ChromeOnlyAccess()) {
+ return true;
+ }
+
+ // Exception: this is a poster image of video element
+ if (nsIContent* parent = aContainingElement->GetParent()) {
+ nsVideoFrame* videoFrame = do_QueryFrame(parent->GetPrimaryFrame());
+ if (videoFrame && videoFrame->GetPosterImage() == aContainingElement) {
+ return true;
+ }
+ }
+
+ // Exception: CSS generated images
+ if (aContainingElement->IsInNativeAnonymousSubtree()) {
+ if (nsINode* rootParentOrHost =
+ aContainingElement
+ ->GetClosestNativeAnonymousSubtreeRootParentOrHost()) {
+ if (!rootParentOrHost->ChromeOnlyAccess()) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+void LargestContentfulPaint::MaybeProcessImageForElementTiming(
+ imgRequestProxy* aRequest, Element* aElement) {
+ if (!StaticPrefs::dom_enable_largest_contentful_paint()) {
+ return;
+ }
+
+ MOZ_ASSERT(aRequest);
+ imgRequest* request = aRequest->GetOwner();
+ if (!LCPHelpers::IsQualifiedImageRequest(request, aElement)) {
+ return;
+ }
+
+ Document* document = aElement->GetComposedDoc();
+ if (!document) {
+ return;
+ }
+
+ nsPresContext* pc =
+ aElement->GetPresContext(Element::PresContextFor::eForComposedDoc);
+ if (!pc) {
+ return;
+ }
+
+ PerformanceMainThread* performance = pc->GetPerformanceMainThread();
+ if (!performance) {
+ return;
+ }
+
+ if (MOZ_UNLIKELY(MOZ_LOG_TEST(gLCPLogging, LogLevel::Debug))) {
+ nsCOMPtr<nsIURI> uri;
+ aRequest->GetURI(getter_AddRefs(uri));
+ LOG("MaybeProcessImageForElementTiming, Element=%p, URI=%s, "
+ "performance=%p ",
+ aElement, uri ? uri->GetSpecOrDefault().get() : "", performance);
+ }
+
+ const LCPImageEntryKey entryKey = LCPImageEntryKey(aElement, aRequest);
+ if (!document->ContentIdentifiersForLCP().EnsureInserted(entryKey)) {
+ LOG(" The content identifier existed for element=%p and request=%p, "
+ "return.",
+ aElement, aRequest);
+ return;
+ }
+
+#ifdef DEBUG
+ uint32_t status = imgIRequest::STATUS_NONE;
+ aRequest->GetImageStatus(&status);
+ MOZ_ASSERT(status & imgIRequest::STATUS_LOAD_COMPLETE);
+#endif
+
+ // At this point, the loadTime of the image is known, but
+ // the renderTime is unknown, so it's added to ImagesPendingRendering
+ // as a placeholder, and the corresponding LCP entry will be created
+ // when the renderTime is known.
+ // Here we are exposing the load time of the image which could be
+ // a privacy concern. The spec talks about it at
+ // https://wicg.github.io/element-timing/#sec-security
+ // TLDR: The similar metric can be obtained by ResourceTiming
+ // API and onload handlers already, so this is not exposing anything
+ // new.
+ LOG(" Added a pending image rendering");
+ performance->AddImagesPendingRendering(
+ ImagePendingRendering{entryKey, TimeStamp::Now()});
+}
+
+bool LCPHelpers::CanFinalizeLCPEntry(const nsIFrame* aFrame) {
+ if (!StaticPrefs::dom_enable_largest_contentful_paint()) {
+ return false;
+ }
+
+ if (!aFrame) {
+ return false;
+ }
+
+ nsPresContext* presContext = aFrame->PresContext();
+ return !presContext->HasStoppedGeneratingLCP() &&
+ presContext->GetPerformanceMainThread();
+}
+
+void LCPHelpers::FinalizeLCPEntryForImage(
+ Element* aContainingBlock, imgRequestProxy* aImgRequestProxy,
+ const nsRect& aTargetRectRelativeToSelf) {
+ LOG("FinalizeLCPEntryForImage element=%p", aContainingBlock);
+ if (!aImgRequestProxy) {
+ return;
+ }
+
+ if (!IsQualifiedImageRequest(aImgRequestProxy->GetOwner(),
+ aContainingBlock)) {
+ return;
+ }
+
+ nsIFrame* frame = aContainingBlock->GetPrimaryFrame();
+
+ if (!CanFinalizeLCPEntry(frame)) {
+ return;
+ }
+
+ PerformanceMainThread* performance =
+ frame->PresContext()->GetPerformanceMainThread();
+ MOZ_ASSERT(performance);
+
+ RefPtr<LargestContentfulPaint> entry =
+ performance->GetImageLCPEntry(aContainingBlock, aImgRequestProxy);
+ if (!entry) {
+ LOG(" No Image Entry");
+ return;
+ }
+ entry->UpdateSize(aContainingBlock, aTargetRectRelativeToSelf, performance,
+ true);
+ // If area is less than or equal to document’s largest contentful paint size,
+ // return.
+ if (!performance->UpdateLargestContentfulPaintSize(entry->Size())) {
+ LOG(
+
+ " This paint(%lu) is not greater than the largest paint (%lf)that "
+ "we've "
+ "reported so far, return",
+ entry->Size(), performance->GetLargestContentfulPaintSize());
+ return;
+ }
+
+ entry->QueueEntry();
+}
+
+DOMHighResTimeStamp LargestContentfulPaint::RenderTime() const {
+ if (!mShouldExposeRenderTime) {
+ return 0;
+ }
+ return GetReducedTimePrecisionDOMHighRes(mPerformance, mRenderTime);
+}
+
+DOMHighResTimeStamp LargestContentfulPaint::LoadTime() const {
+ if (mLoadTime.isNothing()) {
+ return 0;
+ }
+
+ return GetReducedTimePrecisionDOMHighRes(mPerformance, mLoadTime.ref());
+}
+
+DOMHighResTimeStamp LargestContentfulPaint::StartTime() const {
+ if (mShouldExposeRenderTime) {
+ return GetReducedTimePrecisionDOMHighRes(mPerformance, mRenderTime);
+ }
+
+ if (mLoadTime.isNothing()) {
+ return 0;
+ }
+
+ return GetReducedTimePrecisionDOMHighRes(mPerformance, mLoadTime.ref());
+}
+
+/* static */
+Element* LargestContentfulPaint::GetContainingBlockForTextFrame(
+ const nsTextFrame* aTextFrame) {
+ nsIFrame* containingFrame = aTextFrame->GetContainingBlock();
+ MOZ_ASSERT(containingFrame);
+ return Element::FromNodeOrNull(containingFrame->GetContent());
+}
+
+void LargestContentfulPaint::QueueEntry() {
+ LOG("QueueEntry entry=%p", this);
+ mPerformance->QueueLargestContentfulPaintEntry(this);
+
+ ReportLCPToNavigationTimings();
+}
+
+void LargestContentfulPaint::GetUrl(nsAString& aUrl) {
+ if (mURI) {
+ CopyUTF8toUTF16(mURI->GetSpecOrDefault(), aUrl);
+ }
+}
+
+void LargestContentfulPaint::UpdateSize(
+ const Element* aContainingBlock, const nsRect& aTargetRectRelativeToSelf,
+ const PerformanceMainThread* aPerformance, bool aIsImage) {
+ nsIFrame* frame = aContainingBlock->GetPrimaryFrame();
+ MOZ_ASSERT(frame);
+
+ nsIFrame* rootFrame = frame->PresShell()->GetRootFrame();
+ if (!rootFrame) {
+ return;
+ }
+
+ if (frame->Style()->IsInOpacityZeroSubtree()) {
+ LOG(" Opacity:0 return");
+ return;
+ }
+
+ // The following size computation is based on a pending pull request
+ // https://github.com/w3c/largest-contentful-paint/pull/99
+
+ // Let visibleDimensions be concreteDimensions, adjusted for positioning
+ // by object-position or background-position and element’s content box.
+ const nsRect& visibleDimensions = aTargetRectRelativeToSelf;
+
+ // Let clientContentRect be the smallest DOMRectReadOnly containing
+ // visibleDimensions with element’s transforms applied.
+ nsRect clientContentRect = nsLayoutUtils::TransformFrameRectToAncestor(
+ frame, visibleDimensions, rootFrame);
+
+ // Let intersectionRect be the value returned by the intersection rect
+ // algorithm using element as the target and viewport as the root.
+ // (From https://wicg.github.io/element-timing/#sec-report-image-element)
+ IntersectionInput input = DOMIntersectionObserver::ComputeInput(
+ *frame->PresContext()->Document(), rootFrame->GetContent(), nullptr);
+ const IntersectionOutput output =
+ DOMIntersectionObserver::Intersect(input, *aContainingBlock);
+
+ Maybe<nsRect> intersectionRect = output.mIntersectionRect;
+
+ if (intersectionRect.isNothing()) {
+ LOG(" The intersectionRect is nothing for Element=%p. return.",
+ aContainingBlock);
+ return;
+ }
+
+ // Let intersectingClientContentRect be the intersection of clientContentRect
+ // with intersectionRect.
+ Maybe<nsRect> intersectionWithContentRect =
+ clientContentRect.EdgeInclusiveIntersection(intersectionRect.value());
+
+ if (intersectionWithContentRect.isNothing()) {
+ LOG(" The intersectionWithContentRect is nothing for Element=%p. return.",
+ aContainingBlock);
+ return;
+ }
+
+ nsRect renderedRect = intersectionWithContentRect.value();
+
+ double area = GetAreaInDoublePixelsFromAppUnits(renderedRect);
+
+ double viewport = GetAreaInDoublePixelsFromAppUnits(input.mRootRect);
+
+ LOG(" Viewport = %f, RenderRect = %f.", viewport, area);
+ // We don't want to report things that take the entire viewport.
+ if (area >= viewport) {
+ LOG(" The renderedRect is at least same as the area of the "
+ "viewport for Element=%p, return.",
+ aContainingBlock);
+ return;
+ }
+
+ Maybe<nsSize> intrinsicSize = frame->GetIntrinsicSize().ToSize();
+ const bool hasIntrinsicSize = intrinsicSize && !intrinsicSize->IsEmpty();
+
+ if (aIsImage && hasIntrinsicSize) {
+ // Let (naturalWidth, naturalHeight) be imageRequest’s natural dimension.
+ // Let naturalArea be naturalWidth * naturalHeight.
+ double naturalArea =
+ GetAreaInDoublePixelsFromAppUnits(intrinsicSize.value());
+
+ LOG(" naturalArea = %f", naturalArea);
+
+ // Let boundingClientArea be clientContentRect’s width * clientContentRect’s
+ // height.
+ double boundingClientArea =
+ NSAppUnitsToDoublePixels(clientContentRect.Width(),
+ AppUnitsPerCSSPixel()) *
+ NSAppUnitsToDoublePixels(clientContentRect.Height(),
+ AppUnitsPerCSSPixel());
+ LOG(" boundingClientArea = %f", boundingClientArea);
+
+ // Let scaleFactor be boundingClientArea / naturalArea.
+ double scaleFactor = boundingClientArea / naturalArea;
+ LOG(" scaleFactor = %f", scaleFactor);
+
+ // If scaleFactor is greater than 1, then divide area by scaleFactor.
+ if (scaleFactor > 1) {
+ LOG(" area before sacled doown %f", area);
+ area = area / scaleFactor;
+ }
+ }
+
+ MOZ_ASSERT(!mSize);
+ mSize = area;
+}
+
+void LCPTextFrameHelper::MaybeUnionTextFrame(
+ nsTextFrame* aTextFrame, const nsRect& aRelativeToSelfRect) {
+ if (!StaticPrefs::dom_enable_largest_contentful_paint() ||
+ aTextFrame->PresContext()->HasStoppedGeneratingLCP()) {
+ return;
+ }
+
+ Element* containingBlock =
+ LargestContentfulPaint::GetContainingBlockForTextFrame(aTextFrame);
+ if (!containingBlock ||
+ // If element is contained in doc’s set of elements with rendered text,
+ // continue
+ containingBlock->HasFlag(ELEMENT_PROCESSED_BY_LCP_FOR_TEXT) ||
+ containingBlock->ChromeOnlyAccess()) {
+ return;
+ }
+
+ MOZ_ASSERT(containingBlock->GetPrimaryFrame());
+
+ PerformanceMainThread* perf =
+ aTextFrame->PresContext()->GetPerformanceMainThread();
+ if (!perf) {
+ return;
+ }
+
+ auto& unionRect = perf->GetTextFrameUnions().LookupOrInsert(containingBlock);
+ unionRect = unionRect.Union(aRelativeToSelfRect);
+}
+
+void LCPHelpers::CreateLCPEntryForImage(
+ PerformanceMainThread* aPerformance, Element* aElement,
+ imgRequestProxy* aRequestProxy, const TimeStamp& aLoadTime,
+ const TimeStamp& aRenderTime, const LCPImageEntryKey& aImageEntryKey) {
+ MOZ_ASSERT(StaticPrefs::dom_enable_largest_contentful_paint());
+ MOZ_ASSERT(aRequestProxy);
+ MOZ_ASSERT(aPerformance);
+ if (MOZ_UNLIKELY(MOZ_LOG_TEST(gLCPLogging, LogLevel::Debug))) {
+ nsCOMPtr<nsIURI> uri;
+ aRequestProxy->GetURI(getter_AddRefs(uri));
+ LOG("CreateLCPEntryForImage "
+ "Element=%p, aRequestProxy=%p, URI=%s loadTime=%f, "
+ "aRenderTime=%f\n",
+ aElement, aRequestProxy, uri->GetSpecOrDefault().get(),
+ GetReducedTimePrecisionDOMHighRes(aPerformance, aLoadTime),
+ GetReducedTimePrecisionDOMHighRes(aPerformance, aRenderTime));
+ }
+ if (aPerformance->HasDispatchedInputEvent() ||
+ aPerformance->HasDispatchedScrollEvent()) {
+ return;
+ }
+
+ // Let url be the empty string.
+ // If imageRequest is not null, set url to be imageRequest’s request URL.
+ nsCOMPtr<nsIURI> requestURI;
+ aRequestProxy->GetURI(getter_AddRefs(requestURI));
+
+ imgRequest* request = aRequestProxy->GetOwner();
+ // We should never get here unless request is valid.
+ MOZ_ASSERT(request);
+
+ bool taoPassed = request->ShouldReportRenderTimeForLCP() || request->IsData();
+ // https://wicg.github.io/element-timing/#report-image-element-timing
+ // For TAO failed requests, the renderTime is exposed as 0 for
+ // security reasons.
+ //
+ // At this point, we have all the information about the entry
+ // except the size.
+ RefPtr<LargestContentfulPaint> entry = new LargestContentfulPaint(
+ aPerformance, aRenderTime, Some(aLoadTime), 0, requestURI, aElement,
+ Some(aImageEntryKey), taoPassed);
+
+ LOG(" Upsert a LargestContentfulPaint entry=%p to LCPEntryMap.",
+ entry.get());
+ aPerformance->StoreImageLCPEntry(aElement, aRequestProxy, entry);
+}
+
+void LCPHelpers::FinalizeLCPEntryForText(
+ PerformanceMainThread* aPerformance, const TimeStamp& aRenderTime,
+ Element* aContainingBlock, const nsRect& aTargetRectRelativeToSelf,
+ const nsPresContext* aPresContext) {
+ MOZ_ASSERT(aPerformance);
+ LOG("FinalizeLCPEntryForText element=%p", aContainingBlock);
+
+ if (!aContainingBlock->GetPrimaryFrame()) {
+ return;
+ }
+ MOZ_ASSERT(CanFinalizeLCPEntry(aContainingBlock->GetPrimaryFrame()));
+ MOZ_ASSERT(!aContainingBlock->HasFlag(ELEMENT_PROCESSED_BY_LCP_FOR_TEXT));
+ MOZ_ASSERT(!aContainingBlock->ChromeOnlyAccess());
+
+ aContainingBlock->SetFlags(ELEMENT_PROCESSED_BY_LCP_FOR_TEXT);
+
+ RefPtr<LargestContentfulPaint> entry =
+ new LargestContentfulPaint(aPerformance, aRenderTime, Nothing(), 0,
+ nullptr, aContainingBlock, Nothing(), true);
+
+ entry->UpdateSize(aContainingBlock, aTargetRectRelativeToSelf, aPerformance,
+ false);
+ // If area is less than or equal to document’s largest contentful paint size,
+ // return.
+ if (!aPerformance->UpdateLargestContentfulPaintSize(entry->Size())) {
+ LOG(" This paint(%lu) is not greater than the largest paint (%lf)that "
+ "we've "
+ "reported so far, return",
+ entry->Size(), aPerformance->GetLargestContentfulPaintSize());
+ return;
+ }
+ entry->QueueEntry();
+}
+
+void LargestContentfulPaint::ReportLCPToNavigationTimings() {
+ nsCOMPtr<Element> element = do_QueryReferent(mElement);
+ if (!element) {
+ return;
+ }
+
+ const Document* document = element->OwnerDoc();
+
+ MOZ_ASSERT(document);
+
+ nsDOMNavigationTiming* timing = document->GetNavigationTiming();
+
+ if (MOZ_UNLIKELY(!timing)) {
+ return;
+ }
+
+ if (document->IsResourceDoc()) {
+ return;
+ }
+
+ if (BrowsingContext* browsingContext = document->GetBrowsingContext()) {
+ if (browsingContext->GetEmbeddedInContentDocument()) {
+ return;
+ }
+ }
+
+ if (!document->IsTopLevelContentDocument()) {
+ return;
+ }
+ timing->NotifyLargestContentfulRenderForRootContentDocument(
+ GetReducedTimePrecisionDOMHighRes(mPerformance, mRenderTime));
+}
+} // namespace mozilla::dom