diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp index a314e2e865c8..870e990341d8 100644 --- a/dom/base/Document.cpp +++ b/dom/base/Document.cpp @@ -104,6 +104,7 @@ #include "mozilla/SMILTimeContainer.h" #include "mozilla/ScopeExit.h" #include "mozilla/Components.h" +#include "mozilla/SVGUtils.h" #include "mozilla/ServoStyleConsts.h" #include "mozilla/ServoTypes.h" #include "mozilla/SizeOfState.h" @@ -2503,7 +2504,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(Document) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOnloadBlocker) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLazyLoadObserver) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLastRememberedSizeObserver) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElementsObservedForLastRememberedSize) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMImplementation) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImageMaps) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOrientationPendingPromise) @@ -2625,7 +2626,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Document) NS_IMPL_CYCLE_COLLECTION_UNLINK(mSecurityInfo) NS_IMPL_CYCLE_COLLECTION_UNLINK(mDisplayDocument) NS_IMPL_CYCLE_COLLECTION_UNLINK(mLazyLoadObserver) - NS_IMPL_CYCLE_COLLECTION_UNLINK(mLastRememberedSizeObserver) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mElementsObservedForLastRememberedSize); NS_IMPL_CYCLE_COLLECTION_UNLINK(mFontFaceSet) NS_IMPL_CYCLE_COLLECTION_UNLINK(mReadyForIdle) NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentL10n) @@ -16447,27 +16448,79 @@ DOMIntersectionObserver& Document::EnsureLazyLoadObserver() { return *mLazyLoadObserver; } -ResizeObserver& Document::EnsureLastRememberedSizeObserver() { - if (!mLastRememberedSizeObserver) { - mLastRememberedSizeObserver = - ResizeObserver::CreateLastRememberedSizeObserver(*this); - } - return *mLastRememberedSizeObserver; -} - void Document::ObserveForLastRememberedSize(Element& aElement) { if (NS_WARN_IF(!IsActive())) { return; } - // Options are initialized with ResizeObserverBoxOptions::Content_box by - // default, which is what we want. - static ResizeObserverOptions options; - EnsureLastRememberedSizeObserver().Observe(aElement, options); + mElementsObservedForLastRememberedSize.Insert(&aElement); } void Document::UnobserveForLastRememberedSize(Element& aElement) { - if (mLastRememberedSizeObserver) { - mLastRememberedSizeObserver->Unobserve(aElement); + mElementsObservedForLastRememberedSize.Remove(&aElement); +} + +void Document::UpdateLastRememberedSizes() { + auto shouldRemoveElement = [&](auto* element) { + if (element->GetComposedDoc() != this) { + element->RemoveLastRememberedBSize(); + element->RemoveLastRememberedISize(); + return true; + } + return !element->GetPrimaryFrame(); + }; + + for (auto it = mElementsObservedForLastRememberedSize.begin(), + end = mElementsObservedForLastRememberedSize.end(); + it != end; ++it) { + if (shouldRemoveElement(*it)) { + mElementsObservedForLastRememberedSize.Remove(it); + continue; + } + const auto element = *it; + MOZ_ASSERT(element->GetComposedDoc() == this); + nsIFrame* frame = element->GetPrimaryFrame(); + MOZ_ASSERT(frame); + + // As for ResizeObserver, skip nodes hidden `content-visibility`. + if (frame->IsHiddenByContentVisibilityOnAnyAncestor()) { + continue; + } + + MOZ_ASSERT(!frame->IsLineParticipant() || frame->IsReplaced(), + "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).HasAuto(); + bool canUpdateISize = stylePos->ContainIntrinsicISize(wm).HasAuto(); + MOZ_ASSERT(canUpdateBSize || !element->HasLastRememberedBSize(), + "Should have removed the last remembered block size."); + MOZ_ASSERT(canUpdateISize || !element->HasLastRememberedISize(), + "Should have removed the last remembered inline size."); + MOZ_ASSERT(canUpdateBSize || canUpdateISize, + "Should have unobserved if we can't update any size."); + + AutoTArray contentSizeList = + ResizeObserver::CalculateBoxSize(element, + ResizeObserverBoxOptions::Content_box, + /* aForceFragmentHandling */ true); + MOZ_ASSERT(!contentSizeList.IsEmpty()); + + if (canUpdateBSize) { + float bSize = 0; + for (const auto& current : contentSizeList) { + bSize += current.BSize(); + } + element->SetLastRememberedBSize(bSize); + } + if (canUpdateISize) { + float iSize = 0; + for (const auto& current : contentSizeList) { + iSize = std::max(iSize, current.ISize()); + } + element->SetLastRememberedISize(iSize); + } } } @@ -17200,6 +17253,11 @@ void Document::DetermineProximityToViewportAndNotifyResizeObservers() { } } + // Last remembered sizes are recorded "at the time that ResizeObserver + // events are determined and delivered". + // https://drafts.csswg.org/css-sizing-4/#last-remembered + UpdateLastRememberedSizes(); + // To avoid infinite resize loop, we only gather all active observations // that have the depth of observed target element more than current // shallowestTargetDepth. diff --git a/dom/base/Document.h b/dom/base/Document.h index 7ac9faa00f19..33f51a5f787f 100644 --- a/dom/base/Document.h +++ b/dom/base/Document.h @@ -3723,12 +3723,12 @@ class Document : public nsINode, DOMIntersectionObserver* GetLazyLoadObserver() { return mLazyLoadObserver; } DOMIntersectionObserver& EnsureLazyLoadObserver(); - ResizeObserver* GetLastRememberedSizeObserver() { - return mLastRememberedSizeObserver; + bool HasElementsWithLastRememberedSize() const { + return !mElementsObservedForLastRememberedSize.IsEmpty(); } - ResizeObserver& EnsureLastRememberedSizeObserver(); void ObserveForLastRememberedSize(Element&); void UnobserveForLastRememberedSize(Element&); + void UpdateLastRememberedSizes(); // Dispatch a runnable related to the document. nsresult Dispatch(already_AddRefed&& aRunnable) const; @@ -5167,9 +5167,9 @@ class Document : public nsINode, RefPtr mLazyLoadObserver; - // ResizeObserver for storing and removing the last remembered size. + // Elements observed for a last remembered size. // @see {@link https://drafts.csswg.org/css-sizing-4/#last-remembered} - RefPtr mLastRememberedSizeObserver; + nsTHashSet> mElementsObservedForLastRememberedSize; // Stack of top layer elements. nsTArray mTopLayer; diff --git a/dom/base/Element.cpp b/dom/base/Element.cpp index 666505481926..cd063502d256 100644 --- a/dom/base/Element.cpp +++ b/dom/base/Element.cpp @@ -2144,11 +2144,10 @@ void Element::UnbindFromTree(UnbindContext& aContext) { } if (HasLastRememberedBSize() || HasLastRememberedISize()) { - // Need to remove the last remembered size at the next ResizeObserver - // opportunity, so observe the element. But if already observed, we still - // want the callback to be invoked even if the size was already 0x0, so - // unobserve it first. - document->UnobserveForLastRememberedSize(*this); + // Make sure the element is observed so that remembered sizes are kept + // until the next time "ResizeObserver events are determined and + // delivered". See "Disconnected element" tests from + // css/css-sizing/contain-intrinsic-size/auto-006.html document->ObserveForLastRememberedSize(*this); } } diff --git a/dom/base/ResizeObserver.cpp b/dom/base/ResizeObserver.cpp index 77472179c877..ff833f267b27 100644 --- a/dom/base/ResizeObserver.cpp +++ b/dom/base/ResizeObserver.cpp @@ -66,16 +66,9 @@ static nsSize GetContentRectSize(const nsIFrame& aFrame) { 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 CalculateBoxSize( +AutoTArray ResizeObserver::CalculateBoxSize( Element* aTarget, ResizeObserverBoxOptions aBox, - const ResizeObserver& aObserver) { + bool aForceFragmentHandling) { nsIFrame* frame = aTarget->GetPrimaryFrame(); if (!frame) { @@ -158,7 +151,7 @@ static AutoTArray CalculateBoxSize( return CSSPixel::FromAppUnits(GetContentRectSize(*aFrame)).ToUnknownSize(); }; if (!StaticPrefs::dom_resize_observer_support_fragments() && - !aObserver.HasNativeCallback()) { + !aForceFragmentHandling) { return {LogicalPixelSize(frame->GetWritingMode(), GetFrameSize(frame))}; } AutoTArray size; @@ -209,7 +202,7 @@ bool ResizeObservation::IsActive() const { } return mLastReportedSize != - CalculateBoxSize(mTarget, mObservedBox, *mObserver); + ResizeObserver::CalculateBoxSize(mTarget, mObservedBox); } void ResizeObservation::UpdateLastReportedSize( @@ -383,12 +376,12 @@ uint32_t ResizeObserver::BroadcastActiveObservations() { 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); + auto borderBoxSize = ResizeObserver::CalculateBoxSize( + target, ResizeObserverBoxOptions::Border_box); + auto contentBoxSize = ResizeObserver::CalculateBoxSize( + target, ResizeObserverBoxOptions::Content_box); + auto devicePixelContentBoxSize = ResizeObserver::CalculateBoxSize( + target, ResizeObserverBoxOptions::Device_pixel_content_box); RefPtr entry = new ResizeObserverEntry(mOwner, *target, borderBoxSize, contentBoxSize, devicePixelContentBoxSize); @@ -524,61 +517,6 @@ void ResizeObserverEntry::SetDevicePixelContentSize( } } -static void LastRememberedSizeCallback( - const Sequence>& 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->IsLineParticipant() || frame->IsReplaced(), - "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).HasAuto(); - bool canUpdateISize = stylePos->ContainIntrinsicISize(wm).HasAuto(); - 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, 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::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) diff --git a/dom/base/ResizeObserver.h b/dom/base/ResizeObserver.h index 6f1fc5b6cda3..5367c6afef2f 100644 --- a/dom/base/ResizeObserver.h +++ b/dom/base/ResizeObserver.h @@ -191,8 +191,21 @@ class ResizeObserver final : public nsISupports, public nsWrapperCache { */ MOZ_CAN_RUN_SCRIPT uint32_t BroadcastActiveObservations(); - static already_AddRefed CreateLastRememberedSizeObserver( - Document&); + /** + * 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. Otherwise, the + * relevant box is determined according to the |aBox| parameter. + * + * If dom.resize_observer.support_fragments is enabled, or if + * |aForceFragmentHandling| is true then the function reports the size of all + * fragments, and not just the first one. + * + * https://www.w3.org/TR/resize-observer-1/#calculate-box-size + */ + static AutoTArray CalculateBoxSize( + Element* aTarget, ResizeObserverBoxOptions aBox, + bool aForceFragmentHandling = false); protected: ~ResizeObserver() { Disconnect(); } diff --git a/layout/base/nsRefreshDriver.cpp b/layout/base/nsRefreshDriver.cpp index af780bb192bd..a5c2b1ded990 100644 --- a/layout/base/nsRefreshDriver.cpp +++ b/layout/base/nsRefreshDriver.cpp @@ -2264,7 +2264,8 @@ void nsRefreshDriver::DetermineProximityToViewportAndNotifyResizeObservers() { return false; } return ps->HasContentVisibilityAutoFrames() || - aDocument->HasResizeObservers(); + aDocument->HasResizeObservers() || + aDocument->HasElementsWithLastRememberedSize(); }; AutoTArray, 32> documents;