Skip to content

Commit 05b31c4

Browse files
committed
Bug 1974382: Avoid mixing anchors in different child lists. r=layout-anchor-positioning-reviewers,dshin
Anchors with identical names are organized in tree order. During restyling, the tree undergoes restructuring and the ordering of the frames may go wrong. With this patch, the adjustments during restyling are recorded and applied only after the tree is ready. Differential Revision: https://phabricator.services.mozilla.com/D255502
1 parent 61e3989 commit 05b31c4

File tree

10 files changed

+118
-45
lines changed

10 files changed

+118
-45
lines changed

layout/base/PresShell.cpp

Lines changed: 69 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1789,6 +1789,8 @@ nsresult PresShell::Initialize() {
17891789
return NS_ERROR_OUT_OF_MEMORY;
17901790
}
17911791

1792+
mPresContext->PresShell()->AssertNoAnchorPosAnchorChanges();
1793+
17921794
if (Element* root = mDocument->GetRootElement()) {
17931795
{
17941796
nsAutoCauseReflowNotifier reflowNotifier(this);
@@ -1863,6 +1865,8 @@ nsresult PresShell::Initialize() {
18631865
mShouldUnsuppressPainting = true;
18641866
}
18651867

1868+
mPresContext->PresShell()->MergeAnchorPosAnchorChanges();
1869+
18661870
return NS_OK; // XXX this needs to be real. MMP
18671871
}
18681872

@@ -4519,7 +4523,6 @@ void PresShell::DoFlushPendingNotifications(mozilla::ChangesToFlush aFlush) {
45194523

45204524
// Per our API contract, hold a strong ref to ourselves until we return.
45214525
RefPtr<PresShell> kungFuDeathGrip = this;
4522-
45234526
/**
45244527
* VERY IMPORTANT: If you add some sort of new flushing to this
45254528
* method, make sure to add the relevant SetNeedLayoutFlush or
@@ -4840,6 +4843,7 @@ MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::ContentWillBeRemoved(
48404843
// Notify the ESM that the content has been removed, so that
48414844
// it can clean up any state related to the content.
48424845

4846+
AssertNoAnchorPosAnchorChanges();
48434847
mPresContext->EventStateManager()->ContentRemoved(mDocument, aChild);
48444848

48454849
nsAutoCauseReflowNotifier crNotifier(this);
@@ -4859,6 +4863,7 @@ MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::ContentWillBeRemoved(
48594863
// display: contents / display: none, because we'd have cleared all the style
48604864
// data from there.
48614865
mPresContext->RestyleManager()->ContentWillBeRemoved(aChild);
4866+
MergeAnchorPosAnchorChanges();
48624867
}
48634868

48644869
void PresShell::NotifyCounterStylesAreDirty() {
@@ -10986,6 +10991,7 @@ bool PresShell::ProcessReflowCommands(bool aInterruptible) {
1098610991
printf("ProcessReflowCommands: begin incremental reflow\n");
1098710992
}
1098810993
#endif
10994+
AssertNoAnchorPosAnchorChanges();
1098910995

1099010996
// If reflow is interruptible, then make a note of our deadline.
1099110997
const PRIntervalTime deadline =
@@ -11019,6 +11025,8 @@ bool PresShell::ProcessReflowCommands(bool aInterruptible) {
1101911025
} while (!interrupted && !mDirtyRoots.IsEmpty() &&
1102011026
(!aInterruptible || PR_IntervalNow() < deadline));
1102111027

11028+
MergeAnchorPosAnchorChanges();
11029+
1102211030
interrupted = !mDirtyRoots.IsEmpty();
1102311031

1102411032
overflowTracker.Flush();
@@ -12017,6 +12025,8 @@ nsIFrame* PresShell::GetAbsoluteContainingBlock(nsIFrame* aFrame) {
1201712025
nsIFrame* PresShell::GetAnchorPosAnchor(
1201812026
const nsAtom* aName, const nsIFrame* aPositionedFrame) const {
1201912027
MOZ_ASSERT(aName);
12028+
MOZ_ASSERT(mLazyAnchorPosAnchorChanges.IsEmpty());
12029+
1202012030
if (const auto& entry = mAnchorPosAnchors.Lookup(aName)) {
1202112031
return AnchorPositioningUtils::FindFirstAcceptableAnchor(aPositionedFrame,
1202212032
entry.Data());
@@ -12025,41 +12035,10 @@ nsIFrame* PresShell::GetAnchorPosAnchor(
1202512035
return nullptr;
1202612036
}
1202712037

12028-
void PresShell::AddAnchorPosAnchor(const nsAtom* aName, nsIFrame* aFrame) {
12029-
MOZ_ASSERT(aName);
12030-
12031-
auto& entry = mAnchorPosAnchors.LookupOrInsertWith(
12032-
aName, []() { return nsTArray<nsIFrame*>(); });
12033-
12034-
if (entry.IsEmpty()) {
12035-
entry.AppendElement(aFrame);
12036-
return;
12037-
}
12038-
12039-
struct FrameTreeComparator {
12040-
nsIFrame* mFrame;
12041-
12042-
int32_t operator()(nsIFrame* aOther) const {
12043-
return nsLayoutUtils::CompareTreePosition(mFrame, aOther, nullptr);
12044-
}
12045-
};
12046-
12047-
FrameTreeComparator cmp{aFrame};
12048-
12049-
size_t matchOrInsertionIdx = entry.Length();
12050-
// If the same element is already in the array,
12051-
// someone forgot to call RemoveAnchorPosAnchor.
12052-
if (BinarySearchIf(entry, 0, entry.Length(), cmp, &matchOrInsertionIdx)) {
12053-
MOZ_ASSERT_UNREACHABLE("Anchor added already");
12054-
return;
12055-
}
12056-
12057-
*entry.InsertElementAt(matchOrInsertionIdx) = aFrame;
12058-
}
12059-
12060-
void PresShell::RemoveAnchorPosAnchor(const nsAtom* aName, nsIFrame* aFrame) {
12061-
MOZ_ASSERT(aName);
12062-
auto entry = mAnchorPosAnchors.Lookup(aName);
12038+
static void RemoveAnchorPosAnchorFromOrderedCache(
12039+
const RefPtr<const nsAtom>& aName, nsIFrame* aFrame,
12040+
nsTHashMap<RefPtr<const nsAtom>, nsTArray<nsIFrame*>>& aAnchorPosAnchors) {
12041+
auto entry = aAnchorPosAnchors.Lookup(aName);
1206312042
if (!entry) {
1206412043
return; // Nothing to remove.
1206512044
}
@@ -12069,13 +12048,67 @@ void PresShell::RemoveAnchorPosAnchor(const nsAtom* aName, nsIFrame* aFrame) {
1206912048
// XXX: Once the implementation is more complete,
1207012049
// we should probably assert here that anchorArray
1207112050
// is not empty and aFrame is in it.
12051+
MOZ_ASSERT(!anchorArray.IsEmpty());
1207212052

1207312053
anchorArray.RemoveElement(aFrame);
1207412054
if (anchorArray.IsEmpty()) {
1207512055
entry.Remove();
1207612056
}
1207712057
}
1207812058

12059+
void PresShell::RemoveAnchorPosAnchorNow(const RefPtr<const nsAtom>& aName,
12060+
nsIFrame* aFrame) {
12061+
// nsCSSFrameConstructor will destroy, remove and add frames back,
12062+
// in no particular order during reflow. Removing the elements from
12063+
// mLazyAnchorPosAnchorChanges here is necessary and for performance
12064+
// reasons it would be an advantage to not let mLazyAnchorPosAnchorChanges
12065+
// grow too much.
12066+
mLazyAnchorPosAnchorChanges.RemoveElementsBy(
12067+
[&](const AnchorPosAnchorChange& change) {
12068+
return change.mFrame == aFrame;
12069+
});
12070+
12071+
RemoveAnchorPosAnchorFromOrderedCache(aName, aFrame, mAnchorPosAnchors);
12072+
}
12073+
12074+
void PresShell::MergeAnchorPosAnchorChanges() {
12075+
for (const auto& [name, frame, isRemoval] : mLazyAnchorPosAnchorChanges) {
12076+
if (isRemoval) {
12077+
RemoveAnchorPosAnchorFromOrderedCache(name, frame, mAnchorPosAnchors);
12078+
} else {
12079+
auto& entry = mAnchorPosAnchors.LookupOrInsertWith(
12080+
name, []() { return nsTArray<nsIFrame*>(); });
12081+
12082+
if (entry.IsEmpty()) {
12083+
entry.AppendElement(frame);
12084+
continue;
12085+
}
12086+
12087+
struct FrameTreeComparator {
12088+
nsIFrame* mFrame;
12089+
12090+
int32_t operator()(nsIFrame* aOther) const {
12091+
return nsLayoutUtils::CompareTreePosition(mFrame, aOther, nullptr);
12092+
}
12093+
};
12094+
12095+
FrameTreeComparator cmp{frame};
12096+
12097+
size_t matchOrInsertionIdx = entry.Length();
12098+
// If the same element is already in the array,
12099+
// someone forgot to call RemoveAnchorPosAnchor.
12100+
if (BinarySearchIf(entry, 0, entry.Length(), cmp, &matchOrInsertionIdx)) {
12101+
MOZ_ASSERT_UNREACHABLE("Anchor added already");
12102+
continue;
12103+
}
12104+
12105+
*entry.InsertElementAt(matchOrInsertionIdx) = frame;
12106+
}
12107+
}
12108+
12109+
mLazyAnchorPosAnchorChanges.Clear();
12110+
}
12111+
1207912112
void PresShell::ActivenessMaybeChanged() {
1208012113
if (!mDocument) {
1208112114
return;

layout/base/PresShell.h

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -751,8 +751,34 @@ class PresShell final : public nsStubDocumentObserver,
751751
// https://drafts.csswg.org/css-anchor-position-1/#target
752752
nsIFrame* GetAnchorPosAnchor(const nsAtom* aName,
753753
const nsIFrame* aPositionedFrame) const;
754-
void AddAnchorPosAnchor(const nsAtom* aName, nsIFrame* aFrame);
755-
void RemoveAnchorPosAnchor(const nsAtom* aName, nsIFrame* aFrame);
754+
755+
/**
756+
* When frame tree is reconstructed, we need to avoid ordering the frames
757+
* while they may not be part of the same child list.
758+
*/
759+
inline void AddAnchorPosAnchor(const nsAtom* aName, nsIFrame* aFrame) {
760+
MOZ_ASSERT(aName);
761+
mLazyAnchorPosAnchorChanges.AppendElement(
762+
AnchorPosAnchorChange{RefPtr<const nsAtom>(aName), aFrame, false});
763+
}
764+
765+
/**
766+
* When frame tree is reconstructed, we need to avoid ordering the frames
767+
* while they may not be part of the same child list.
768+
*/
769+
inline void RemoveAnchorPosAnchor(const nsAtom* aName, nsIFrame* aFrame) {
770+
MOZ_ASSERT(aName);
771+
mLazyAnchorPosAnchorChanges.AppendElement(
772+
AnchorPosAnchorChange{RefPtr<const nsAtom>(aName), aFrame, true});
773+
;
774+
}
775+
776+
/**
777+
* When frame is about to be destroyed, we need to remove all references to it
778+
* without delay.
779+
*/
780+
void RemoveAnchorPosAnchorNow(const RefPtr<const nsAtom>& aName,
781+
nsIFrame* aFrame);
756782

757783
#ifdef MOZ_REFLOW_PERF
758784
void DumpReflows();
@@ -1808,6 +1834,12 @@ class PresShell final : public nsStubDocumentObserver,
18081834
*/
18091835
static Modifiers GetCurrentModifiers() { return sCurrentModifiers; }
18101836

1837+
inline void AssertNoAnchorPosAnchorChanges() const {
1838+
MOZ_ASSERT(mLazyAnchorPosAnchorChanges.IsEmpty());
1839+
}
1840+
1841+
void MergeAnchorPosAnchorChanges();
1842+
18111843
private:
18121844
~PresShell();
18131845

@@ -3197,6 +3229,13 @@ class PresShell final : public nsStubDocumentObserver,
31973229

31983230
nsTHashMap<RefPtr<const nsAtom>, nsTArray<nsIFrame*>> mAnchorPosAnchors;
31993231

3232+
struct AnchorPosAnchorChange {
3233+
RefPtr<const nsAtom> mName;
3234+
nsIFrame* mFrame;
3235+
bool mIsRemoval;
3236+
};
3237+
nsTArray<AnchorPosAnchorChange> mLazyAnchorPosAnchorChanges;
3238+
32003239
// Reflow roots that need to be reflowed.
32013240
DepthOrderedFrameList mDirtyRoots;
32023241

layout/base/RestyleManager.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1616,7 +1616,7 @@ void RestyleManager::ProcessRestyledFrames(nsStyleChangeList& aChangeList) {
16161616
nsCSSFrameConstructor* frameConstructor = presContext->FrameConstructor();
16171617

16181618
bool didUpdateCursor = false;
1619-
1619+
presContext->PresShell()->AssertNoAnchorPosAnchorChanges();
16201620
for (size_t i = 0; i < aChangeList.Length(); ++i) {
16211621
// Collect and coalesce adjacent siblings for lazy frame construction.
16221622
// Eventually it would be even better to make RecreateFramesForContent
@@ -1928,6 +1928,7 @@ void RestyleManager::ProcessRestyledFrames(nsStyleChangeList& aChangeList) {
19281928
frame->UpdateVisibleDescendantsState();
19291929
}
19301930
}
1931+
presContext->PresShell()->MergeAnchorPosAnchorChanges();
19311932

19321933
aChangeList.Clear();
19331934
FlushOverflowChangedTracker();
@@ -3276,6 +3277,7 @@ void RestyleManager::DoProcessPendingRestyles(ServoTraversalFlags aFlags) {
32763277
// those into a secondary queue and iterate until there's nothing left.
32773278
ReentrantChangeList newChanges;
32783279
mReentrantChanges = &newChanges;
3280+
presShell->AssertNoAnchorPosAnchorChanges();
32793281

32803282
{
32813283
DocumentStyleRootIterator iter(doc->GetServoRestyleRoot());
@@ -3299,6 +3301,8 @@ void RestyleManager::DoProcessPendingRestyles(ServoTraversalFlags aFlags) {
32993301
}
33003302
}
33013303

3304+
presShell->MergeAnchorPosAnchorChanges();
3305+
33023306
doc->ClearServoRestyleRoot();
33033307
ClearSnapshots();
33043308

layout/generic/nsIFrame.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -887,7 +887,7 @@ void nsIFrame::Destroy(DestroyContext& aContext) {
887887

888888
if (HasAnchorPosName()) {
889889
for (auto& name : disp->mAnchorName.AsSpan()) {
890-
PresShell()->RemoveAnchorPosAnchor(name.AsAtom(), this);
890+
PresShell()->RemoveAnchorPosAnchorNow(name.AsAtom(), this);
891891
}
892892
}
893893

testing/web-platform/meta/css/css-anchor-position/anchor-getComputedStyle-001.html.ini

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
[anchor-getComputedStyle-001.html]
2-
expected: CRASH
32
[Basic case]
43
expected: FAIL
54

testing/web-platform/meta/css/css-anchor-position/anchor-getComputedStyle-002.html.ini

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
[anchor-getComputedStyle-002.html]
2-
expected: CRASH
32
[getComputedStyle() with fragmented containing block in multicolumn layout]
43
expected: FAIL
54

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
[anchor-scope-shadow-names.html]
2-
expected: CRASH
32
[anchor-scope in ::part() affects slotted-in element]
43
expected: FAIL

testing/web-platform/meta/css/css-anchor-position/anchor-scroll-004.html.ini

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
[anchor-scroll-004.html]
2-
expected: CRASH
32
[Initial position of the targets]
43
expected: FAIL
54

testing/web-platform/meta/css/css-anchor-position/anchor-transition-eval.html.ini

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
[anchor-transition-eval.html]
2-
expected: CRASH
32
[Transition when the result of anchor() changes]
43
expected: FAIL
54

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[position-anchor-target-with-children.html]
2+
expected: [PASS, FAIL]

0 commit comments

Comments
 (0)