gecko-dev/layout/base/ContainStyleScopeManager.cpp
Emilio Cobos Álvarez e7d0f3e978 Bug 1947516 - Avoid unstable ordering in OwningElementRef comparisons. r=boris,smaug
Use shadow-dom aware ordering, and pick an order for disconnected elements
based on subtree root, because there's not too much useful things to do.

The spec should probably define what happens in those cases I guess...

That's not a useful order, mind you, but it prevents breaking the invariants
the sort algorithms rely on.

Change the comparison to return an int just because it's easier to debug.

Differential Revision: https://phabricator.services.mozilla.com/D237878
2025-03-25 12:14:03 +00:00

258 lines
7.5 KiB
C++

/* -*- 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 "ContainStyleScopeManager.h"
#include "mozilla/ServoStyleSet.h"
#include "nsIContentInlines.h"
#include "CounterStyleManager.h"
#include "nsCounterManager.h"
#include "nsIContent.h"
#include "nsIFrame.h"
#include "nsContentUtils.h"
#include "nsQuoteList.h"
namespace mozilla {
nsGenConNode* ContainStyleScope::GetPrecedingElementInGenConList(
nsGenConList* aList) {
nsContentUtils::NodeIndexCache cache;
auto IsAfter = [this, &cache](nsGenConNode* aNode) {
return nsContentUtils::CompareTreePosition<TreeKind::Flat>(
mContent, aNode->mPseudoFrame->GetContent(),
/* aCommonAncestor = */ nullptr, &cache) >= 0;
};
auto* last = aList->GetLast();
if (!last || IsAfter(last)) {
return last;
}
return aList->BinarySearch(IsAfter)->getPrevious();
}
void ContainStyleScope::RecalcAllCounters() {
GetCounterManager().RecalcAll();
for (auto* child : mChildren) {
child->RecalcAllCounters();
}
}
void ContainStyleScope::RecalcAllQuotes() {
GetQuoteList().RecalcAll();
for (auto* child : mChildren) {
child->RecalcAllQuotes();
}
}
ContainStyleScope& ContainStyleScopeManager::GetOrCreateScopeForContent(
nsIContent* aContent) {
for (; aContent; aContent = aContent->GetFlattenedTreeParent()) {
auto* element = dom::Element::FromNode(*aContent);
if (!element) {
continue;
}
// Do not allow elements which have `display: contents` to create style
// boundaries. See https://github.com/w3c/csswg-drafts/issues/7392.
if (element->IsDisplayContents()) {
continue;
}
const auto* style = Servo_Element_GetMaybeOutOfDateStyle(element);
if (!style) {
continue;
}
if (!style->SelfOrAncestorHasContainStyle()) {
return GetRootScope();
}
if (!style->StyleDisplay()->IsContainStyle()) {
continue;
}
if (auto* scope = mScopes.Get(aContent)) {
return *scope;
}
auto& parentScope =
GetOrCreateScopeForContent(aContent->GetFlattenedTreeParent());
return *mScopes.InsertOrUpdate(
aContent, MakeUnique<ContainStyleScope>(this, &parentScope, aContent));
}
return GetRootScope();
}
ContainStyleScope& ContainStyleScopeManager::GetScopeForContent(
nsIContent* aContent) {
MOZ_ASSERT(aContent);
if (auto* element = dom::Element::FromNode(*aContent)) {
if (const auto* style = Servo_Element_GetMaybeOutOfDateStyle(element)) {
if (!style->SelfOrAncestorHasContainStyle()) {
return GetRootScope();
}
}
}
for (; aContent; aContent = aContent->GetFlattenedTreeParent()) {
if (auto* scope = mScopes.Get(aContent)) {
return *scope;
}
}
return GetRootScope();
}
void ContainStyleScopeManager::Clear() {
GetRootScope().GetQuoteList().Clear();
GetRootScope().GetCounterManager().Clear();
DestroyScope(&GetRootScope());
MOZ_DIAGNOSTIC_ASSERT(mScopes.IsEmpty(),
"Destroying the root scope should destroy all scopes.");
}
void ContainStyleScopeManager::DestroyScopesFor(nsIFrame* aFrame) {
if (auto* scope = mScopes.Get(aFrame->GetContent())) {
DestroyScope(scope);
}
}
void ContainStyleScopeManager::DestroyScope(ContainStyleScope* aScope) {
// Deleting a scope modifies the array of children in its parent, so we don't
// use an iterator here.
while (!aScope->GetChildren().IsEmpty()) {
DestroyScope(aScope->GetChildren().ElementAt(0));
}
mScopes.Remove(aScope->GetContent());
}
bool ContainStyleScopeManager::DestroyCounterNodesFor(nsIFrame* aFrame) {
bool result = false;
for (auto* scope = &GetScopeForContent(aFrame->GetContent()); scope;
scope = scope->GetParent()) {
result |= scope->GetCounterManager().DestroyNodesFor(aFrame);
}
return result;
}
bool ContainStyleScopeManager::AddCounterChanges(nsIFrame* aNewFrame) {
return GetOrCreateScopeForContent(
aNewFrame->GetContent()->GetFlattenedTreeParent())
.GetCounterManager()
.AddCounterChanges(aNewFrame);
}
nsCounterList* ContainStyleScopeManager::GetOrCreateCounterList(
dom::Element& aElement, nsAtom* aCounterName) {
return GetOrCreateScopeForContent(&aElement)
.GetCounterManager()
.GetOrCreateCounterList(aCounterName);
}
bool ContainStyleScopeManager::CounterDirty(nsAtom* aCounterName) {
return mDirtyCounters.Contains(aCounterName);
}
void ContainStyleScopeManager::SetCounterDirty(nsAtom* aCounterName) {
mDirtyCounters.Insert(aCounterName);
}
void ContainStyleScopeManager::RecalcAllCounters() {
GetRootScope().RecalcAllCounters();
mDirtyCounters.Clear();
}
#if defined(DEBUG) || defined(MOZ_LAYOUT_DEBUGGER)
void ContainStyleScopeManager::DumpCounters() {
GetRootScope().GetCounterManager().Dump();
for (auto& entry : mScopes) {
entry.GetWeak()->GetCounterManager().Dump();
}
}
#endif
#ifdef ACCESSIBILITY
static bool GetFirstCounterValueForScopeAndFrame(ContainStyleScope* aScope,
nsIFrame* aFrame,
CounterValue& aOrdinal) {
if (aScope->GetCounterManager().GetFirstCounterValueForFrame(aFrame,
aOrdinal)) {
return true;
}
for (auto* child : aScope->GetChildren()) {
if (GetFirstCounterValueForScopeAndFrame(child, aFrame, aOrdinal)) {
return true;
}
}
return false;
}
void ContainStyleScopeManager::GetSpokenCounterText(nsIFrame* aFrame,
nsAString& aText) {
using Tag = StyleCounterStyle::Tag;
const auto& listStyleType = aFrame->StyleList()->mListStyleType;
switch (listStyleType.tag) {
case Tag::None:
return;
case Tag::String:
listStyleType.AsString().AsAtom()->ToString(aText);
return;
case Tag::Symbols:
case Tag::Name:
break;
}
CounterValue ordinal = 1;
GetFirstCounterValueForScopeAndFrame(&GetRootScope(), aFrame, ordinal);
aFrame->PresContext()->CounterStyleManager()->WithCounterStyleNameOrSymbols(
listStyleType, [&](CounterStyle* aStyle) {
nsAutoString text;
bool isBullet;
aStyle->GetSpokenCounterText(ordinal, aFrame->GetWritingMode(), text,
isBullet);
if (isBullet) {
aText = text;
aText.Append(' ');
} else {
aStyle->GetPrefix(aText);
aText += text;
nsAutoString suffix;
aStyle->GetSuffix(suffix);
aText += suffix;
}
});
}
#endif
void ContainStyleScopeManager::SetAllCountersDirty() {
GetRootScope().GetCounterManager().SetAllDirty();
for (auto& entry : mScopes) {
entry.GetWeak()->GetCounterManager().SetAllDirty();
}
}
bool ContainStyleScopeManager::DestroyQuoteNodesFor(nsIFrame* aFrame) {
bool result = false;
for (auto* scope = &GetScopeForContent(aFrame->GetContent()); scope;
scope = scope->GetParent()) {
result |= scope->GetQuoteList().DestroyNodesFor(aFrame);
}
return result;
}
nsQuoteList* ContainStyleScopeManager::QuoteListFor(dom::Element& aElement) {
return &GetOrCreateScopeForContent(&aElement).GetQuoteList();
}
void ContainStyleScopeManager::RecalcAllQuotes() {
GetRootScope().RecalcAllQuotes();
}
} // namespace mozilla