mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-02 18:08:58 +00:00
Bug 1880928 - Remove last remembered size observer, handle it in Document::DetermineProximityToViewportAndNotifyResizeObservers. r=emilio,layout-reviewers
The CSS Box Sizing specification indicates that last remembered sizes are recorded "at the time that ResizeObserver events are determined and delivered" [1]. In bug 1807253, we changed the implementation of when proximity to the viewport of `content-visibility: auto` nodes are determined and of when resize observations are broadcast, in order to align with the latest version of the HTML specification [2]. We continue to use an internal `Document::mLastRememberedSizeObserver` to update last remembered sizes but it has been causing issues (e.g. bug 1867090 and bug 1880928) and could be replaced by a direct update before broadcasting resize observations. This is what the current patch is doing. The elements currently observed by `Document::mLastRememberedSizeObserver` are now stored on a `Document::mElementsWithLastRememberedSize` hashset and a new function `Document::UpdateLastRememberedSizes` is called before broadcasting resize observations, and peforms the work of `LastRememberedSizeCallback` and of `CalculateBoxSize` (with `aBox=Content_box`). The only behavior change is in the `while(true)` loop from `DetermineProximityToViewportAndNotifyResizeObservers`: at each step we update the last remember sizes for elements of arbitrary depth, and don't use these depths for calculating `shallowestTargetDepth`. This is fine, since our `LastRememberedSizeCallback` only records current box sizes without causing significant side effects (e.g. execution of JavaScript code) that may require a relayout. [1] https://drafts.csswg.org/css-sizing-4/#last-remembered [2] https://html.spec.whatwg.org/#update-the-rendering Differential Revision: https://phabricator.services.mozilla.com/D202571
This commit is contained in:
parent
d748552981
commit
c592b5761b
@ -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<LogicalPixelSize, 1> 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.
|
||||
|
@ -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<nsIRunnable>&& aRunnable) const;
|
||||
@ -5167,9 +5167,9 @@ class Document : public nsINode,
|
||||
|
||||
RefPtr<DOMIntersectionObserver> 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<ResizeObserver> mLastRememberedSizeObserver;
|
||||
nsTHashSet<RefPtr<Element>> mElementsObservedForLastRememberedSize;
|
||||
|
||||
// Stack of top layer elements.
|
||||
nsTArray<nsWeakPtr> mTopLayer;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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<LogicalPixelSize, 1> CalculateBoxSize(
|
||||
AutoTArray<LogicalPixelSize, 1> ResizeObserver::CalculateBoxSize(
|
||||
Element* aTarget, ResizeObserverBoxOptions aBox,
|
||||
const ResizeObserver& aObserver) {
|
||||
bool aForceFragmentHandling) {
|
||||
nsIFrame* frame = aTarget->GetPrimaryFrame();
|
||||
|
||||
if (!frame) {
|
||||
@ -158,7 +151,7 @@ static AutoTArray<LogicalPixelSize, 1> CalculateBoxSize(
|
||||
return CSSPixel::FromAppUnits(GetContentRectSize(*aFrame)).ToUnknownSize();
|
||||
};
|
||||
if (!StaticPrefs::dom_resize_observer_support_fragments() &&
|
||||
!aObserver.HasNativeCallback()) {
|
||||
!aForceFragmentHandling) {
|
||||
return {LogicalPixelSize(frame->GetWritingMode(), GetFrameSize(frame))};
|
||||
}
|
||||
AutoTArray<LogicalPixelSize, 1> 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<ResizeObserverEntry> entry =
|
||||
new ResizeObserverEntry(mOwner, *target, borderBoxSize, contentBoxSize,
|
||||
devicePixelContentBoxSize);
|
||||
@ -524,61 +517,6 @@ void ResizeObserverEntry::SetDevicePixelContentSize(
|
||||
}
|
||||
}
|
||||
|
||||
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->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<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)
|
||||
|
@ -191,8 +191,21 @@ class ResizeObserver final : public nsISupports, public nsWrapperCache {
|
||||
*/
|
||||
MOZ_CAN_RUN_SCRIPT uint32_t BroadcastActiveObservations();
|
||||
|
||||
static already_AddRefed<ResizeObserver> 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<LogicalPixelSize, 1> CalculateBoxSize(
|
||||
Element* aTarget, ResizeObserverBoxOptions aBox,
|
||||
bool aForceFragmentHandling = false);
|
||||
|
||||
protected:
|
||||
~ResizeObserver() { Disconnect(); }
|
||||
|
@ -2264,7 +2264,8 @@ void nsRefreshDriver::DetermineProximityToViewportAndNotifyResizeObservers() {
|
||||
return false;
|
||||
}
|
||||
return ps->HasContentVisibilityAutoFrames() ||
|
||||
aDocument->HasResizeObservers();
|
||||
aDocument->HasResizeObservers() ||
|
||||
aDocument->HasElementsWithLastRememberedSize();
|
||||
};
|
||||
|
||||
AutoTArray<RefPtr<Document>, 32> documents;
|
||||
|
Loading…
Reference in New Issue
Block a user