summaryrefslogtreecommitdiffstats
path: root/dom/base/ResizeObserver.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /dom/base/ResizeObserver.cpp
parentInitial commit. (diff)
downloadthunderbird-upstream.tar.xz
thunderbird-upstream.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/base/ResizeObserver.cpp')
-rw-r--r--dom/base/ResizeObserver.cpp604
1 files changed, 604 insertions, 0 deletions
diff --git a/dom/base/ResizeObserver.cpp b/dom/base/ResizeObserver.cpp
new file mode 100644
index 0000000000..cc9865ee2e
--- /dev/null
+++ b/dom/base/ResizeObserver.cpp
@@ -0,0 +1,604 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/ResizeObserver.h"
+
+#include "mozilla/dom/DOMRect.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/SVGUtils.h"
+#include "nsIContent.h"
+#include "nsIContentInlines.h"
+#include "nsIScrollableFrame.h"
+#include "nsLayoutUtils.h"
+#include <limits>
+
+namespace mozilla::dom {
+
+/**
+ * Returns the length of the parent-traversal path (in terms of the number of
+ * nodes) to an unparented/root node from aNode. An unparented/root node is
+ * considered to have a depth of 1, its children have a depth of 2, etc.
+ * aNode is expected to be non-null.
+ * Note: The shadow root is not part of the calculation because the caller,
+ * ResizeObserver, doesn't observe the shadow root, and only needs relative
+ * depths among all the observed targets. In other words, we calculate the
+ * depth of the flattened tree.
+ *
+ * However, these is a spec issue about how to handle shadow DOM case. We
+ * may need to update this function later:
+ * https://github.com/w3c/csswg-drafts/issues/3840
+ *
+ * https://drafts.csswg.org/resize-observer/#calculate-depth-for-node-h
+ */
+static uint32_t GetNodeDepth(nsINode* aNode) {
+ uint32_t depth = 1;
+
+ MOZ_ASSERT(aNode, "Node shouldn't be null");
+
+ // Use GetFlattenedTreeParentNode to bypass the shadow root and cross the
+ // shadow boundary to calculate the node depth without the shadow root.
+ while ((aNode = aNode->GetFlattenedTreeParentNode())) {
+ ++depth;
+ }
+
+ return depth;
+}
+
+static nsSize GetContentRectSize(const nsIFrame& aFrame) {
+ if (const nsIScrollableFrame* f = do_QueryFrame(&aFrame)) {
+ // We return the scrollport rect for compat with other UAs, see bug 1733042.
+ // But the scrollPort includes padding (but not border!), so remove it.
+ nsRect scrollPort = f->GetScrollPortRect();
+ nsMargin padding =
+ aFrame.GetUsedPadding().ApplySkipSides(aFrame.GetSkipSides());
+ scrollPort.Deflate(padding);
+ // This can break in some edge cases like when layout overflows sizes or
+ // what not.
+ NS_ASSERTION(
+ !aFrame.PresContext()->UseOverlayScrollbars() ||
+ scrollPort.Size() == aFrame.GetContentRectRelativeToSelf().Size(),
+ "Wrong scrollport?");
+ return scrollPort.Size();
+ }
+ return aFrame.GetContentRectRelativeToSelf().Size();
+}
+
+/**
+ * Returns |aTarget|'s size in the form of gfx::Size (in pixels).
+ * If the target is an SVG that does not participate in CSS layout,
+ * its width and height are determined from bounding box.
+ *
+ * https://www.w3.org/TR/resize-observer-1/#calculate-box-size
+ */
+static AutoTArray<LogicalPixelSize, 1> CalculateBoxSize(
+ Element* aTarget, ResizeObserverBoxOptions aBox,
+ const ResizeObserver& aObserver) {
+ nsIFrame* frame = aTarget->GetPrimaryFrame();
+
+ if (!frame) {
+ // TODO: Should this return an empty array instead?
+ // https://github.com/w3c/csswg-drafts/issues/7734
+ return {LogicalPixelSize()};
+ }
+
+ if (frame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
+ // Per the spec, this target's SVG size is always its bounding box size no
+ // matter what box option you choose, because SVG elements do not use
+ // standard CSS box model.
+ // TODO: what if the SVG is fragmented?
+ // https://github.com/w3c/csswg-drafts/issues/7736
+ const gfxRect bbox = SVGUtils::GetBBox(frame);
+ gfx::Size size(static_cast<float>(bbox.width),
+ static_cast<float>(bbox.height));
+ const WritingMode wm = frame->GetWritingMode();
+ if (aBox == ResizeObserverBoxOptions::Device_pixel_content_box) {
+ // Per spec, we calculate the inline/block sizes to target’s bounding box
+ // {inline|block} length, in integral device pixels, so we round the final
+ // result.
+ // https://drafts.csswg.org/resize-observer/#dom-resizeobserverboxoptions-device-pixel-content-box
+ const LayoutDeviceIntSize snappedSize =
+ RoundedToInt(CSSSize::FromUnknownSize(size) *
+ frame->PresContext()->CSSToDevPixelScale());
+ return {LogicalPixelSize(wm, gfx::Size(snappedSize.ToUnknownSize()))};
+ }
+ return {LogicalPixelSize(wm, size)};
+ }
+
+ // Per the spec, non-replaced inline Elements will always have an empty
+ // content rect. Therefore, we always use the same trivially-empty size
+ // for non-replaced inline elements here, and their IsActive() will
+ // always return false. (So its observation won't be fired.)
+ // TODO: Should we use an empty array instead?
+ // https://github.com/w3c/csswg-drafts/issues/7734
+ if (!frame->IsFrameOfType(nsIFrame::eReplaced) &&
+ frame->IsFrameOfType(nsIFrame::eLineParticipant)) {
+ return {LogicalPixelSize()};
+ }
+
+ auto GetFrameSize = [aBox](nsIFrame* aFrame) {
+ switch (aBox) {
+ case ResizeObserverBoxOptions::Border_box:
+ return CSSPixel::FromAppUnits(aFrame->GetSize()).ToUnknownSize();
+ case ResizeObserverBoxOptions::Device_pixel_content_box: {
+ // Simply converting from app units to device units is insufficient - we
+ // need to take subpixel snapping into account. Subpixel snapping
+ // happens with respect to the reference frame, so do the dev pixel
+ // conversion with our rectangle positioned relative to the reference
+ // frame, then get the size from there.
+ const auto* referenceFrame = nsLayoutUtils::GetReferenceFrame(aFrame);
+ // GetOffsetToCrossDoc version handles <iframe>s in addition to normal
+ // cases. We don't expect this to tight loop for additional checks to
+ // matter.
+ const auto offset = aFrame->GetOffsetToCrossDoc(referenceFrame);
+ const auto contentSize = GetContentRectSize(*aFrame);
+ // Casting to double here is deliberate to minimize rounding error in
+ // upcoming operations.
+ const auto appUnitsPerDevPixel =
+ static_cast<double>(aFrame->PresContext()->AppUnitsPerDevPixel());
+ // Calculation here is a greatly simplified version of
+ // `NSRectToSnappedRect` as 1) we're not actually drawing (i.e. no draw
+ // target), and 2) transform does not need to be taken into account.
+ gfx::Rect rect{gfx::Float(offset.X() / appUnitsPerDevPixel),
+ gfx::Float(offset.Y() / appUnitsPerDevPixel),
+ gfx::Float(contentSize.Width() / appUnitsPerDevPixel),
+ gfx::Float(contentSize.Height() / appUnitsPerDevPixel)};
+ gfx::Point tl = rect.TopLeft().Round();
+ gfx::Point br = rect.BottomRight().Round();
+
+ rect.SizeTo(gfx::Size(br.x - tl.x, br.y - tl.y));
+ rect.NudgeToIntegers();
+ return rect.Size().ToUnknownSize();
+ }
+ case ResizeObserverBoxOptions::Content_box:
+ default:
+ break;
+ }
+ return CSSPixel::FromAppUnits(GetContentRectSize(*aFrame)).ToUnknownSize();
+ };
+ if (!StaticPrefs::dom_resize_observer_support_fragments() &&
+ !aObserver.HasNativeCallback()) {
+ return {LogicalPixelSize(frame->GetWritingMode(), GetFrameSize(frame))};
+ }
+ AutoTArray<LogicalPixelSize, 1> size;
+ for (nsIFrame* cur = frame; cur; cur = cur->GetNextContinuation()) {
+ const WritingMode wm = cur->GetWritingMode();
+ size.AppendElement(LogicalPixelSize(wm, GetFrameSize(cur)));
+ }
+ return size;
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(ResizeObservation)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ResizeObservation)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTarget);
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ResizeObservation)
+ tmp->Unlink(RemoveFromObserver::Yes);
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+ResizeObservation::ResizeObservation(Element& aTarget,
+ ResizeObserver& aObserver,
+ ResizeObserverBoxOptions aBox)
+ : mTarget(&aTarget),
+ mObserver(&aObserver),
+ mObservedBox(aBox),
+ mLastReportedSize(
+ {StaticPrefs::dom_resize_observer_last_reported_size_invalid()
+ ? LogicalPixelSize(WritingMode(), gfx::Size(-1, -1))
+ : LogicalPixelSize()}) {
+ aTarget.BindObject(mObserver);
+}
+
+void ResizeObservation::Unlink(RemoveFromObserver aRemoveFromObserver) {
+ ResizeObserver* observer = std::exchange(mObserver, nullptr);
+ nsCOMPtr<Element> target = std::move(mTarget);
+ if (observer && target) {
+ if (aRemoveFromObserver == RemoveFromObserver::Yes) {
+ observer->Unobserve(*target);
+ }
+ target->UnbindObject(observer);
+ }
+}
+
+bool ResizeObservation::IsActive() const {
+ // As detailed in the css-contain specification, if the target is hidden by
+ // `content-visibility` it should not call its ResizeObservation callbacks.
+ nsIFrame* frame = mTarget->GetPrimaryFrame();
+ if (frame && frame->IsHiddenByContentVisibilityOnAnyAncestor()) {
+ return false;
+ }
+
+ return mLastReportedSize !=
+ CalculateBoxSize(mTarget, mObservedBox, *mObserver);
+}
+
+void ResizeObservation::UpdateLastReportedSize(
+ const nsTArray<LogicalPixelSize>& aSize) {
+ mLastReportedSize.Assign(aSize);
+}
+
+// Only needed for refcounted objects.
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(ResizeObserver)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ResizeObserver)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner, mDocument, mActiveTargets,
+ mObservationMap);
+ if (tmp->mCallback.is<RefPtr<ResizeObserverCallback>>()) {
+ ImplCycleCollectionTraverse(
+ cb, tmp->mCallback.as<RefPtr<ResizeObserverCallback>>(), "mCallback",
+ 0);
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ResizeObserver)
+ tmp->Disconnect();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner, mDocument, mActiveTargets,
+ mObservationMap);
+ if (tmp->mCallback.is<RefPtr<ResizeObserverCallback>>()) {
+ ImplCycleCollectionUnlink(
+ tmp->mCallback.as<RefPtr<ResizeObserverCallback>>());
+ }
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ResizeObserver)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ResizeObserver)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ResizeObserver)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+ResizeObserver::ResizeObserver(Document& aDocument, NativeCallback aCallback)
+ : mOwner(aDocument.GetInnerWindow()),
+ mDocument(&aDocument),
+ mCallback(aCallback) {
+ MOZ_ASSERT(mOwner, "Need a non-null owner window");
+ MOZ_ASSERT(mDocument == mOwner->GetExtantDoc());
+}
+
+already_AddRefed<ResizeObserver> ResizeObserver::Constructor(
+ const GlobalObject& aGlobal, ResizeObserverCallback& aCb,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!window) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ Document* doc = window->GetExtantDoc();
+ if (!doc) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ return do_AddRef(new ResizeObserver(std::move(window), doc, aCb));
+}
+
+void ResizeObserver::Observe(Element& aTarget,
+ const ResizeObserverOptions& aOptions) {
+ if (MOZ_UNLIKELY(!mDocument)) {
+ MOZ_ASSERT_UNREACHABLE("How did we call observe() after unlink?");
+ return;
+ }
+
+ // NOTE(emilio): Per spec, this is supposed to happen on construction, but the
+ // spec isn't particularly sane here, see
+ // https://github.com/w3c/csswg-drafts/issues/4518
+ if (mObservationList.isEmpty()) {
+ MOZ_ASSERT(mObservationMap.IsEmpty());
+ mDocument->AddResizeObserver(*this);
+ }
+
+ auto& observation = mObservationMap.LookupOrInsert(&aTarget);
+ if (observation) {
+ if (observation->BoxOptions() == aOptions.mBox) {
+ // Already observed this target and the observed box is the same, so
+ // return.
+ // Note: Based on the spec, we should unobserve it first. However,
+ // calling Unobserve() when we observe the same box will remove original
+ // ResizeObservation and then add a new one, this may cause an unexpected
+ // result because ResizeObservation stores the mLastReportedSize which
+ // should be kept to make sure IsActive() returns the correct result.
+ return;
+ }
+ // Remove the pre-existing entry, but without unregistering ourselves from
+ // the controller.
+ observation->remove();
+ observation = nullptr;
+ }
+
+ observation = new ResizeObservation(aTarget, *this, aOptions.mBox);
+ if (!StaticPrefs::dom_resize_observer_last_reported_size_invalid() &&
+ this == mDocument->GetLastRememberedSizeObserver()) {
+ // Resize observations are initialized with a (0, 0) mLastReportedSize,
+ // this means that the callback won't be called if the element is 0x0.
+ // But we need it called for handling the last remembered size, so set
+ // mLastReportedSize to an invalid size to ensure IsActive() is true
+ // for the current element size.
+ // See https://github.com/w3c/csswg-drafts/issues/3664 about doing this in
+ // the general case, then we won't need this hack for the last remembered
+ // size, and will have consistency with IntersectionObserver.
+ observation->UpdateLastReportedSize(
+ {LogicalPixelSize(WritingMode(), gfx::Size(-1, -1))});
+ MOZ_ASSERT(observation->IsActive());
+ }
+ mObservationList.insertBack(observation);
+
+ // Per the spec, we need to trigger notification in event loop that
+ // contains ResizeObserver observe call even when resize/reflow does
+ // not happen.
+ mDocument->ScheduleResizeObserversNotification();
+}
+
+void ResizeObserver::Unobserve(Element& aTarget) {
+ RefPtr<ResizeObservation> observation;
+ if (!mObservationMap.Remove(&aTarget, getter_AddRefs(observation))) {
+ return;
+ }
+
+ MOZ_ASSERT(!mObservationList.isEmpty(),
+ "If ResizeObservation found for an element, observation list "
+ "must be not empty.");
+ observation->remove();
+ if (mObservationList.isEmpty()) {
+ if (MOZ_LIKELY(mDocument)) {
+ mDocument->RemoveResizeObserver(*this);
+ }
+ }
+}
+
+void ResizeObserver::Disconnect() {
+ const bool registered = !mObservationList.isEmpty();
+ while (auto* observation = mObservationList.popFirst()) {
+ observation->Unlink(ResizeObservation::RemoveFromObserver::No);
+ }
+ MOZ_ASSERT(mObservationList.isEmpty());
+ mObservationMap.Clear();
+ mActiveTargets.Clear();
+ if (registered && MOZ_LIKELY(mDocument)) {
+ mDocument->RemoveResizeObserver(*this);
+ }
+}
+
+void ResizeObserver::GatherActiveObservations(uint32_t aDepth) {
+ mActiveTargets.Clear();
+ mHasSkippedTargets = false;
+
+ for (auto* observation : mObservationList) {
+ if (!observation->IsActive()) {
+ continue;
+ }
+
+ uint32_t targetDepth = GetNodeDepth(observation->Target());
+
+ if (targetDepth > aDepth) {
+ mActiveTargets.AppendElement(observation);
+ } else {
+ mHasSkippedTargets = true;
+ }
+ }
+}
+
+uint32_t ResizeObserver::BroadcastActiveObservations() {
+ uint32_t shallowestTargetDepth = std::numeric_limits<uint32_t>::max();
+
+ if (!HasActiveObservations()) {
+ return shallowestTargetDepth;
+ }
+
+ Sequence<OwningNonNull<ResizeObserverEntry>> entries;
+
+ for (auto& observation : mActiveTargets) {
+ Element* target = observation->Target();
+
+ auto borderBoxSize =
+ CalculateBoxSize(target, ResizeObserverBoxOptions::Border_box, *this);
+ auto contentBoxSize =
+ CalculateBoxSize(target, ResizeObserverBoxOptions::Content_box, *this);
+ auto devicePixelContentBoxSize = CalculateBoxSize(
+ target, ResizeObserverBoxOptions::Device_pixel_content_box, *this);
+ RefPtr<ResizeObserverEntry> entry =
+ new ResizeObserverEntry(mOwner, *target, borderBoxSize, contentBoxSize,
+ devicePixelContentBoxSize);
+
+ if (!entries.AppendElement(entry.forget(), fallible)) {
+ // Out of memory.
+ break;
+ }
+
+ // Sync the broadcast size of observation so the next size inspection
+ // will be based on the updated size from last delivered observations.
+ switch (observation->BoxOptions()) {
+ case ResizeObserverBoxOptions::Border_box:
+ observation->UpdateLastReportedSize(borderBoxSize);
+ break;
+ case ResizeObserverBoxOptions::Device_pixel_content_box:
+ observation->UpdateLastReportedSize(devicePixelContentBoxSize);
+ break;
+ case ResizeObserverBoxOptions::Content_box:
+ default:
+ observation->UpdateLastReportedSize(contentBoxSize);
+ }
+
+ uint32_t targetDepth = GetNodeDepth(observation->Target());
+
+ if (targetDepth < shallowestTargetDepth) {
+ shallowestTargetDepth = targetDepth;
+ }
+ }
+
+ if (mCallback.is<RefPtr<ResizeObserverCallback>>()) {
+ RefPtr<ResizeObserverCallback> callback(
+ mCallback.as<RefPtr<ResizeObserverCallback>>());
+ callback->Call(this, entries, *this);
+ } else {
+ mCallback.as<NativeCallback>()(entries, *this);
+ }
+
+ mActiveTargets.Clear();
+ mHasSkippedTargets = false;
+
+ return shallowestTargetDepth;
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ResizeObserverEntry, mOwner, mTarget,
+ mContentRect, mBorderBoxSize,
+ mContentBoxSize,
+ mDevicePixelContentBoxSize)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ResizeObserverEntry)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ResizeObserverEntry)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ResizeObserverEntry)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+void ResizeObserverEntry::GetBorderBoxSize(
+ nsTArray<RefPtr<ResizeObserverSize>>& aRetVal) const {
+ // In the resize-observer-1 spec, there will only be a single
+ // ResizeObserverSize returned in the FrozenArray for now.
+ //
+ // Note: the usage of FrozenArray is to support elements that have multiple
+ // fragments, which occur in multi-column scenarios.
+ // https://drafts.csswg.org/resize-observer/#resize-observer-entry-interface
+ aRetVal.Assign(mBorderBoxSize);
+}
+
+void ResizeObserverEntry::GetContentBoxSize(
+ nsTArray<RefPtr<ResizeObserverSize>>& aRetVal) const {
+ // In the resize-observer-1 spec, there will only be a single
+ // ResizeObserverSize returned in the FrozenArray for now.
+ //
+ // Note: the usage of FrozenArray is to support elements that have multiple
+ // fragments, which occur in multi-column scenarios.
+ // https://drafts.csswg.org/resize-observer/#resize-observer-entry-interface
+ aRetVal.Assign(mContentBoxSize);
+}
+
+void ResizeObserverEntry::GetDevicePixelContentBoxSize(
+ nsTArray<RefPtr<ResizeObserverSize>>& aRetVal) const {
+ // In the resize-observer-1 spec, there will only be a single
+ // ResizeObserverSize returned in the FrozenArray for now.
+ //
+ // Note: the usage of FrozenArray is to support elements that have multiple
+ // fragments, which occur in multi-column scenarios.
+ // https://drafts.csswg.org/resize-observer/#resize-observer-entry-interface
+ aRetVal.Assign(mDevicePixelContentBoxSize);
+}
+
+void ResizeObserverEntry::SetBorderBoxSize(
+ const nsTArray<LogicalPixelSize>& aSize) {
+ mBorderBoxSize.Clear();
+ mBorderBoxSize.SetCapacity(aSize.Length());
+ for (const LogicalPixelSize& size : aSize) {
+ mBorderBoxSize.AppendElement(new ResizeObserverSize(mOwner, size));
+ }
+}
+
+void ResizeObserverEntry::SetContentRectAndSize(
+ const nsTArray<LogicalPixelSize>& aSize) {
+ nsIFrame* frame = mTarget->GetPrimaryFrame();
+
+ // 1. Update mContentRect.
+ nsMargin padding = frame ? frame->GetUsedPadding() : nsMargin();
+ // Per the spec, we need to use the top-left padding offset as the origin of
+ // our contentRect.
+ gfx::Size sizeForRect;
+ MOZ_DIAGNOSTIC_ASSERT(!aSize.IsEmpty());
+ if (!aSize.IsEmpty()) {
+ const WritingMode wm = frame ? frame->GetWritingMode() : WritingMode();
+ sizeForRect = aSize[0].PhysicalSize(wm);
+ }
+ nsRect rect(nsPoint(padding.left, padding.top),
+ CSSPixel::ToAppUnits(CSSSize::FromUnknownSize(sizeForRect)));
+ RefPtr<DOMRect> contentRect = new DOMRect(mOwner);
+ contentRect->SetLayoutRect(rect);
+ mContentRect = std::move(contentRect);
+
+ // 2. Update mContentBoxSize.
+ mContentBoxSize.Clear();
+ mContentBoxSize.SetCapacity(aSize.Length());
+ for (const LogicalPixelSize& size : aSize) {
+ mContentBoxSize.AppendElement(new ResizeObserverSize(mOwner, size));
+ }
+}
+
+void ResizeObserverEntry::SetDevicePixelContentSize(
+ const nsTArray<LogicalPixelSize>& aSize) {
+ mDevicePixelContentBoxSize.Clear();
+ mDevicePixelContentBoxSize.SetCapacity(aSize.Length());
+ for (const LogicalPixelSize& size : aSize) {
+ mDevicePixelContentBoxSize.AppendElement(
+ new ResizeObserverSize(mOwner, size));
+ }
+}
+
+static void LastRememberedSizeCallback(
+ const Sequence<OwningNonNull<ResizeObserverEntry>>& aEntries,
+ ResizeObserver& aObserver) {
+ for (const auto& entry : aEntries) {
+ Element* target = entry->Target();
+ if (!target->IsInComposedDoc()) {
+ aObserver.Unobserve(*target);
+ target->RemoveLastRememberedBSize();
+ target->RemoveLastRememberedISize();
+ continue;
+ }
+ nsIFrame* frame = target->GetPrimaryFrame();
+ if (!frame) {
+ aObserver.Unobserve(*target);
+ continue;
+ }
+ MOZ_ASSERT(!frame->IsFrameOfType(nsIFrame::eLineParticipant) ||
+ frame->IsFrameOfType(nsIFrame::eReplaced),
+ "Should have unobserved non-replaced inline.");
+ MOZ_ASSERT(!frame->HidesContent(),
+ "Should have unobserved element skipping its contents.");
+ const nsStylePosition* stylePos = frame->StylePosition();
+ const WritingMode wm = frame->GetWritingMode();
+ bool canUpdateBSize = stylePos->ContainIntrinsicBSize(wm).IsAutoLength();
+ bool canUpdateISize = stylePos->ContainIntrinsicISize(wm).IsAutoLength();
+ MOZ_ASSERT(canUpdateBSize || !target->HasLastRememberedBSize(),
+ "Should have removed the last remembered block size.");
+ MOZ_ASSERT(canUpdateISize || !target->HasLastRememberedISize(),
+ "Should have removed the last remembered inline size.");
+ MOZ_ASSERT(canUpdateBSize || canUpdateISize,
+ "Should have unobserved if we can't update any size.");
+ AutoTArray<RefPtr<ResizeObserverSize>, 1> contentSizeList;
+ entry->GetContentBoxSize(contentSizeList);
+ MOZ_ASSERT(!contentSizeList.IsEmpty());
+ if (canUpdateBSize) {
+ float bSize = 0;
+ for (const auto& current : contentSizeList) {
+ bSize += current->BlockSize();
+ }
+ target->SetLastRememberedBSize(bSize);
+ }
+ if (canUpdateISize) {
+ float iSize = 0;
+ for (const auto& current : contentSizeList) {
+ iSize = std::max(iSize, current->InlineSize());
+ }
+ target->SetLastRememberedISize(iSize);
+ }
+ }
+}
+
+/* static */ already_AddRefed<ResizeObserver>
+ResizeObserver::CreateLastRememberedSizeObserver(Document& aDocument) {
+ return do_AddRef(new ResizeObserver(aDocument, LastRememberedSizeCallback));
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ResizeObserverSize, mOwner)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ResizeObserverSize)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ResizeObserverSize)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ResizeObserverSize)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+} // namespace mozilla::dom