Skip to content

Commit

Permalink
Bug 1706346 part 2 - [css-lists] Implement the counter value calculat…
Browse files Browse the repository at this point in the history
…ions for 'reversed(<counter-name>)'. r=emilio

Differential Revision: https://phabricator.services.mozilla.com/D129956
  • Loading branch information
Mats Palmgren committed Nov 14, 2021
1 parent 2e0a36d commit b103892
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 35 deletions.
13 changes: 0 additions & 13 deletions layout/base/nsCSSFrameConstructor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9566,25 +9566,12 @@ inline void nsCSSFrameConstructor::ConstructFramesFromItemList(
CreateNeededPseudoInternalRubyBoxes(aState, aItems, aParentFrame);
CreateNeededPseudoSiblings(aState, aItems, aParentFrame);

bool listItemListIsDirty = false;
for (FCItemIterator iter(aItems); !iter.IsDone(); iter.Next()) {
MOZ_ASSERT(!iter.item().mIsRenderedLegend,
"Only one item can be the rendered legend, "
"and it should've been handled above");
NS_ASSERTION(iter.item().DesiredParentType() == GetParentType(aParentFrame),
"Needed pseudos didn't get created; expect bad things");
// display:list-item boxes affects the start value of the "list-item"
// counter when an <ol reversed> element doesn't have an explicit start
// value.
if (!listItemListIsDirty &&
iter.item().mComputedStyle->StyleList()->mMozListReversed ==
StyleMozListReversed::True &&
iter.item().mComputedStyle->StyleDisplay()->IsListItem()) {
auto* list = mCounterManager.CounterListFor(nsGkAtoms::list_item);
list->SetDirty();
CountersDirty();
listItemListIsDirty = true;
}
ConstructFramesFromItem(aState, iter, aParentFrame, aFrameList);
}

Expand Down
78 changes: 61 additions & 17 deletions layout/base/nsCounterManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,25 @@ void nsCounterList::SetScope(nsCounterNode* aNode) {
if (aNode == First()) {
aNode->mScopeStart = nullptr;
aNode->mScopePrev = nullptr;
if (aNode->IsUnitializedIncrementNode()) {
aNode->ChangeNode()->mChangeValue = 1;
}
return;
}

auto didSetScopeFor = [this](nsCounterNode* aNode) {
if (aNode->mType == nsCounterNode::USE) {
return;
}
if (aNode->mScopeStart->IsContentBasedReset()) {
mDirty = true;
}
if (aNode->IsUnitializedIncrementNode()) {
aNode->ChangeNode()->mChangeValue =
aNode->mScopeStart->IsReversed() ? -1 : 1;
}
};

// If there exist an explicit RESET scope created by an ancestor or
// the element itself, then we use that scope.
// Otherwise, fall through to consider scopes created by siblings (and
Expand All @@ -166,8 +182,7 @@ void nsCounterList::SetScope(nsCounterNode* aNode) {
}
aNode->mScopeStart = counter;
aNode->mScopePrev = counter;
for (nsCounterNode* prev = Prev(aNode); prev;
prev = prev->mScopePrev) {
for (nsCounterNode* prev = Prev(aNode); prev; prev = prev->mScopePrev) {
if (prev->mScopeStart == counter) {
aNode->mScopePrev =
prev->mType == nsCounterNode::RESET ? prev->mScopePrev : prev;
Expand All @@ -180,6 +195,7 @@ void nsCounterList::SetScope(nsCounterNode* aNode) {
}
}
}
didSetScopeFor(aNode);
return;
}
}
Expand Down Expand Up @@ -217,6 +233,7 @@ void nsCounterList::SetScope(nsCounterNode* aNode) {
(!startContent || nodeContent->IsInclusiveDescendantOf(startContent))) {
aNode->mScopeStart = start;
aNode->mScopePrev = prev;
didSetScopeFor(aNode);
return;
}
}
Expand All @@ -226,20 +243,46 @@ void nsCounterList::SetScope(nsCounterNode* aNode) {
}

void nsCounterList::RecalcAll() {
mDirty = false;

// Setup the scope and calculate the default start value for <ol reversed>.
// Setup the scope and calculate the default start value for content-based
// reversed() counters. We need to track the last increment for each of
// those scopes so that we can add it in an extra time at the end.
// https://drafts.csswg.org/css-lists/#instantiating-counters
nsTHashMap<nsPtrHashKey<nsCounterChangeNode>, int32_t> scopes;
for (nsCounterNode* node = First(); node; node = Next(node)) {
SetScope(node);
if (node->IsContentBasedReset()) {
node->mValueAfter = 1;
} else if (node->mType == nsCounterChangeNode::INCREMENT &&
node->mScopeStart && node->mScopeStart->IsContentBasedReset() &&
node->mPseudoFrame->StyleDisplay()->IsListItem()) {
++node->mScopeStart->mValueAfter;
node->ChangeNode()->mSeenSetNode = false;
node->mValueAfter = 0;
scopes.InsertOrUpdate(node->ChangeNode(), 0);
} else if (node->mScopeStart && node->mScopeStart->IsContentBasedReset() &&
!node->mScopeStart->ChangeNode()->mSeenSetNode) {
if (node->mType == nsCounterChangeNode::INCREMENT) {
auto incrementNegated = -node->ChangeNode()->mChangeValue;
if (auto entry = scopes.Lookup(node->mScopeStart->ChangeNode())) {
entry.Data() = incrementNegated;
}
auto* next = Next(node);
if (next && next->mPseudoFrame == node->mPseudoFrame &&
next->mType == nsCounterChangeNode::SET) {
continue;
}
node->mScopeStart->mValueAfter += incrementNegated;
} else if (node->mType == nsCounterChangeNode::SET) {
node->mScopeStart->mValueAfter += node->ChangeNode()->mChangeValue;
// We have a 'counter-set' for this scope so we're done.
// The counter is incremented from that value for the remaining nodes.
node->mScopeStart->ChangeNode()->mSeenSetNode = true;
}
}
}

// For all the content-based reversed() counters we found, add in the
// incrementNegated from its last counter-increment.
for (auto iter = scopes.ConstIter(); !iter.Done(); iter.Next()) {
iter.Key()->mValueAfter += iter.Data();
}

mDirty = false;
for (nsCounterNode* node = First(); node; node = Next(node)) {
node->Calc(this, /* aNotify = */ true);
}
Expand All @@ -249,12 +292,12 @@ static bool AddCounterChangeNode(nsCounterManager& aManager, nsIFrame* aFrame,
int32_t aIndex,
const nsStyleContent::CounterPair& aPair,
nsCounterNode::Type aType) {
auto* node = new nsCounterChangeNode(aFrame, aType, aPair.value, aIndex);
auto* node = new nsCounterChangeNode(aFrame, aType, aPair.value, aIndex,
aPair.is_reversed);
nsCounterList* counterList = aManager.CounterListFor(aPair.name.AsAtom());
counterList->Insert(node);
if (!counterList->IsLast(node)) {
// Tell the caller it's responsible for recalculating the entire
// list.
// Tell the caller it's responsible for recalculating the entire list.
counterList->SetDirty();
return true;
}
Expand All @@ -264,7 +307,7 @@ static bool AddCounterChangeNode(nsCounterManager& aManager, nsIFrame* aFrame,
if (MOZ_LIKELY(!counterList->IsDirty())) {
node->Calc(counterList);
}
return false;
return counterList->IsDirty();
}

static bool HasCounters(const nsStyleContent& aStyle) {
Expand Down Expand Up @@ -316,11 +359,12 @@ bool nsCounterManager::AddCounterChanges(nsIFrame* aFrame) {
}

if (requiresListItemIncrement && !hasListItemIncrement) {
bool reversed =
aFrame->StyleList()->mMozListReversed == StyleMozListReversed::True;
RefPtr<nsAtom> atom = nsGkAtoms::list_item;
// We use a magic value here to signal to SetScope() that it should
// set the value to -1 or 1 depending on if the scope is reversed()
// or not.
auto listItemIncrement = nsStyleContent::CounterPair{
{StyleAtom(atom.forget())}, reversed ? -1 : 1};
{StyleAtom(atom.forget())}, std::numeric_limits<int32_t>::min()};
dirty |= AddCounterChangeNode(
*this, aFrame, styleContent->mCounterIncrement.Length(),
listItemIncrement, nsCounterChangeNode::INCREMENT);
Expand Down
40 changes: 35 additions & 5 deletions layout/base/nsCounterManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,16 @@ struct nsCounterNode : public nsGenConNode {
// to avoid virtual function calls in the common case
inline void Calc(nsCounterList* aList, bool aNotify);

// Is this a <ol reversed> RESET node?
// Is this a RESET node for a content-based (i.e. without a start value)
// reversed() counter?
inline bool IsContentBasedReset();

// Is this a RESET node for a reversed() counter?
inline bool IsReversed();

// Is this an INCREMENT node that needs to be initialized to -1 or 1
// depending on if our scope is reversed() or not?
inline bool IsUnitializedIncrementNode();
};

struct nsCounterUseNode : public nsCounterNode {
Expand Down Expand Up @@ -114,15 +122,14 @@ struct nsCounterUseNode : public nsCounterNode {
};

struct nsCounterChangeNode : public nsCounterNode {
int32_t mChangeValue; // the numeric value of the increment, set or reset

// |aPseudoFrame| is not necessarily a pseudo-element's frame, but
// since it is for every other subclass of nsGenConNode, we follow
// the naming convention here.
// |aPropIndex| is the index of the value within the list in the
// 'counter-increment', 'counter-reset' or 'counter-set' property.
nsCounterChangeNode(nsIFrame* aPseudoFrame, nsCounterNode::Type aChangeType,
int32_t aChangeValue, int32_t aPropIndex)
int32_t aChangeValue, int32_t aPropIndex,
bool aIsReversed)
: nsCounterNode( // Fake a content index for resets, increments and sets
// that comes before all the real content, with
// the resets first, in order, and then the increments
Expand All @@ -132,7 +139,9 @@ struct nsCounterChangeNode : public nsCounterNode {
? ((INT32_MIN / 3) * 2)
: INT32_MIN / 3)),
aChangeType),
mChangeValue(aChangeValue) {
mChangeValue(aChangeValue),
mIsReversed(aIsReversed),
mSeenSetNode(false) {
NS_ASSERTION(aPropIndex >= 0, "out of range");
NS_ASSERTION(
aChangeType == INCREMENT || aChangeType == SET || aChangeType == RESET,
Expand All @@ -144,6 +153,18 @@ struct nsCounterChangeNode : public nsCounterNode {
// assign the correct |mValueAfter| value to a node that has been inserted
// Should be called immediately after calling |Insert|.
void Calc(nsCounterList* aList);

// The numeric value of the INCREMENT, SET or RESET.
// Note: numeric_limits<int32_t>::min() is used for content-based reversed()
// RESET nodes, and temporarily on INCREMENT nodes to signal that it should be
// initialized to -1 or 1 depending on if the scope is reversed() or not.
int32_t mChangeValue;

// True if the counter is reversed(). Only used on RESET nodes.
bool mIsReversed : 1;
// True if we've seen a SET node during the initialization of
// an IsContentBasedReset() node; always false on other nodes.
bool mSeenSetNode : 1;
};

inline nsCounterUseNode* nsCounterNode::UseNode() {
Expand All @@ -168,6 +189,15 @@ inline bool nsCounterNode::IsContentBasedReset() {
ChangeNode()->mChangeValue == std::numeric_limits<int32_t>::min();
}

inline bool nsCounterNode::IsReversed() {
return mType == RESET && ChangeNode()->mIsReversed;
}

inline bool nsCounterNode::IsUnitializedIncrementNode() {
return mType == INCREMENT &&
ChangeNode()->mChangeValue == std::numeric_limits<int32_t>::min();
}

class nsCounterList : public nsGenConList {
public:
nsCounterList() : nsGenConList(), mDirty(false) {}
Expand Down

0 comments on commit b103892

Please sign in to comment.