diff options
Diffstat (limited to '')
-rw-r--r-- | layout/base/nsCounterManager.cpp | 371 |
1 files changed, 371 insertions, 0 deletions
diff --git a/layout/base/nsCounterManager.cpp b/layout/base/nsCounterManager.cpp new file mode 100644 index 0000000000..eab6a3ba91 --- /dev/null +++ b/layout/base/nsCounterManager.cpp @@ -0,0 +1,371 @@ +/* -*- 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/. */ + +/* implementation of CSS counters (for numbering things) */ + +#include "nsCounterManager.h" + +#include "mozilla/Likely.h" +#include "mozilla/IntegerRange.h" +#include "mozilla/PresShell.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/WritingModes.h" +#include "nsBulletFrame.h" // legacy location for list style type to text code +#include "nsContentUtils.h" +#include "nsIContent.h" +#include "nsTArray.h" +#include "mozilla/dom/Text.h" + +using namespace mozilla; + +bool nsCounterUseNode::InitTextFrame(nsGenConList* aList, + nsIFrame* aPseudoFrame, + nsIFrame* aTextFrame) { + nsCounterNode::InitTextFrame(aList, aPseudoFrame, aTextFrame); + + auto* counterList = static_cast<nsCounterList*>(aList); + counterList->Insert(this); + aPseudoFrame->AddStateBits(NS_FRAME_HAS_CSS_COUNTER_STYLE); + // If the list is already dirty, or the node is not at the end, just start + // with an empty string for now and when we recalculate the list we'll change + // the value to the right one. + if (counterList->IsDirty()) { + return false; + } + if (!counterList->IsLast(this)) { + counterList->SetDirty(); + return true; + } + Calc(counterList, /* aNotify = */ false); + return false; +} + +bool nsCounterUseNode::InitBullet(nsGenConList* aList, nsIFrame* aBullet) { + MOZ_ASSERT(aBullet->IsBulletFrame()); + MOZ_ASSERT(aBullet->Style()->GetPseudoType() == PseudoStyleType::marker); + MOZ_ASSERT(mForLegacyBullet); + return InitTextFrame(aList, aBullet, nullptr); +} + +// assign the correct |mValueAfter| value to a node that has been inserted +// Should be called immediately after calling |Insert|. +void nsCounterUseNode::Calc(nsCounterList* aList, bool aNotify) { + NS_ASSERTION(!aList->IsDirty(), "Why are we calculating with a dirty list?"); + mValueAfter = nsCounterList::ValueBefore(this); + if (mText) { + nsAutoString contentString; + GetText(contentString); + mText->SetText(contentString, aNotify); + } else if (mForLegacyBullet) { + MOZ_ASSERT_IF(mPseudoFrame, mPseudoFrame->IsBulletFrame()); + if (nsBulletFrame* f = do_QueryFrame(mPseudoFrame)) { + f->SetOrdinal(mValueAfter, aNotify); + } + } +} + +// assign the correct |mValueAfter| value to a node that has been inserted +// Should be called immediately after calling |Insert|. +void nsCounterChangeNode::Calc(nsCounterList* aList) { + NS_ASSERTION(!aList->IsDirty(), "Why are we calculating with a dirty list?"); + if (IsContentBasedReset()) { + // RecalcAll takes care of this case. + } else if (mType == RESET || mType == SET) { + mValueAfter = mChangeValue; + } else { + NS_ASSERTION(mType == INCREMENT, "invalid type"); + mValueAfter = nsCounterManager::IncrementCounter( + nsCounterList::ValueBefore(this), mChangeValue); + } +} + +// The text that should be displayed for this counter. +void nsCounterUseNode::GetText(nsString& aResult) { + aResult.Truncate(); + + AutoTArray<nsCounterNode*, 8> stack; + stack.AppendElement(static_cast<nsCounterNode*>(this)); + + if (mAllCounters && mScopeStart) { + for (nsCounterNode* n = mScopeStart; n->mScopePrev; n = n->mScopeStart) { + stack.AppendElement(n->mScopePrev); + } + } + + WritingMode wm = mPseudoFrame->GetWritingMode(); + CounterStyle* style = + mPseudoFrame->PresContext()->CounterStyleManager()->ResolveCounterStyle( + mCounterStyle); + for (uint32_t i = stack.Length() - 1;; --i) { + nsCounterNode* n = stack[i]; + nsAutoString text; + bool isTextRTL; + style->GetCounterText(n->mValueAfter, wm, text, isTextRTL); + aResult.Append(text); + if (i == 0) { + break; + } + aResult.Append(mSeparator); + } +} + +void nsCounterList::SetScope(nsCounterNode* aNode) { + // This function is responsible for setting |mScopeStart| and + // |mScopePrev| (whose purpose is described in nsCounterManager.h). + // We do this by starting from the node immediately preceding + // |aNode| in content tree order, which is reasonably likely to be + // the previous element in our scope (or, for a reset, the previous + // element in the containing scope, which is what we want). If + // we're not in the same scope that it is, then it's too deep in the + // frame tree, so we walk up parent scopes until we find something + // appropriate. + + if (aNode == First()) { + aNode->mScopeStart = nullptr; + aNode->mScopePrev = nullptr; + return; + } + + // 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 + // their descendants) in reverse document order. + if (aNode->mType != nsCounterNode::USE && + StaticPrefs::layout_css_counter_ancestor_scope_enabled()) { + nsIContent* const counterNode = aNode->mPseudoFrame->GetContent(); + nsCounterNode* lastPrev = nullptr; + for (nsCounterNode* prev = Prev(aNode); prev; prev = prev->mScopePrev) { + if (prev->mType == nsCounterNode::RESET) { + if (aNode->mPseudoFrame == prev->mPseudoFrame) { + break; + } + // FIXME(bug 1477524): should use flattened tree here: + nsIContent* resetNode = prev->mPseudoFrame->GetContent(); + if (counterNode->IsInclusiveDescendantOf(resetNode)) { + aNode->mScopeStart = prev; + aNode->mScopePrev = lastPrev ? lastPrev : prev; + return; + } + lastPrev = prev->mScopePrev; + } else if (!lastPrev) { + lastPrev = prev; + } + } + } + + // Get the content node for aNode's rendering object's *parent*, + // since scope includes siblings, so we want a descendant check on + // parents. + nsIContent* nodeContent = aNode->mPseudoFrame->GetContent()->GetParent(); + + for (nsCounterNode *prev = Prev(aNode), *start; prev; + prev = start->mScopePrev) { + // If |prev| starts a scope (because it's a real or implied + // reset), we want it as the scope start rather than the start + // of its enclosing scope. Otherwise, there's no enclosing + // scope, so the next thing in prev's scope shares its scope + // start. + start = (prev->mType == nsCounterNode::RESET || !prev->mScopeStart) + ? prev + : prev->mScopeStart; + + // |startContent| is analogous to |nodeContent| (see above). + nsIContent* startContent = start->mPseudoFrame->GetContent()->GetParent(); + NS_ASSERTION(nodeContent || !startContent, + "null check on startContent should be sufficient to " + "null check nodeContent as well, since if nodeContent " + "is for the root, startContent (which is before it) " + "must be too"); + + // A reset's outer scope can't be a scope created by a sibling. + if (!(aNode->mType == nsCounterNode::RESET && + nodeContent == startContent) && + // everything is inside the root (except the case above, + // a second reset on the root) + // FIXME(bug 1477524): should use flattened tree here: + (!startContent || nodeContent->IsInclusiveDescendantOf(startContent))) { + aNode->mScopeStart = start; + aNode->mScopePrev = prev; + return; + } + } + + aNode->mScopeStart = nullptr; + aNode->mScopePrev = nullptr; +} + +void nsCounterList::RecalcAll() { + mDirty = false; + + // Setup the scope and calculate the default start value for <ol reversed>. + 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; + } + } + + for (nsCounterNode* node = First(); node; node = Next(node)) { + node->Calc(this, /* aNotify = */ true); + } +} + +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); + 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. + counterList->SetDirty(); + return true; + } + + // Don't call Calc() if the list is already dirty -- it'll be recalculated + // anyway, and trying to calculate with a dirty list doesn't work. + if (MOZ_LIKELY(!counterList->IsDirty())) { + node->Calc(counterList); + } + return false; +} + +static bool HasCounters(const nsStyleContent& aStyle) { + return !aStyle.mCounterIncrement.IsEmpty() || + !aStyle.mCounterReset.IsEmpty() || !aStyle.mCounterSet.IsEmpty(); +} + +bool nsCounterManager::AddCounterChanges(nsIFrame* aFrame) { + // For elements with 'display:list-item' we add a default + // 'counter-increment:list-item' unless 'counter-increment' already has a + // value for 'list-item'. + // + // https://drafts.csswg.org/css-lists-3/#declaring-a-list-item + // + // We inherit `display` for some anonymous boxes, but we don't want them to + // increment the list-item counter. + const bool requiresListItemIncrement = + aFrame->StyleDisplay()->IsListItem() && !aFrame->Style()->IsAnonBox(); + + const nsStyleContent* styleContent = aFrame->StyleContent(); + + if (!requiresListItemIncrement && !HasCounters(*styleContent)) { + MOZ_ASSERT(!aFrame->HasAnyStateBits(NS_FRAME_HAS_CSS_COUNTER_STYLE)); + return false; + } + + aFrame->AddStateBits(NS_FRAME_HAS_CSS_COUNTER_STYLE); + + bool dirty = false; + // Add in order, resets first, so all the comparisons will be optimized + // for addition at the end of the list. + { + int32_t i = 0; + for (const auto& pair : styleContent->mCounterReset.AsSpan()) { + dirty |= AddCounterChangeNode(*this, aFrame, i++, pair, + nsCounterChangeNode::RESET); + } + } + bool hasListItemIncrement = false; + { + int32_t i = 0; + for (const auto& pair : styleContent->mCounterIncrement.AsSpan()) { + hasListItemIncrement |= pair.name.AsAtom() == nsGkAtoms::list_item; + dirty |= AddCounterChangeNode(*this, aFrame, i++, pair, + nsCounterChangeNode::INCREMENT); + } + } + + if (requiresListItemIncrement && !hasListItemIncrement) { + bool reversed = + aFrame->StyleList()->mMozListReversed == StyleMozListReversed::True; + RefPtr<nsAtom> atom = nsGkAtoms::list_item; + auto listItemIncrement = nsStyleContent::CounterPair{ + {StyleAtom(atom.forget())}, reversed ? -1 : 1}; + dirty |= AddCounterChangeNode( + *this, aFrame, styleContent->mCounterIncrement.Length(), + listItemIncrement, nsCounterChangeNode::INCREMENT); + } + + { + int32_t i = 0; + for (const auto& pair : styleContent->mCounterSet.AsSpan()) { + dirty |= AddCounterChangeNode(*this, aFrame, i++, pair, + nsCounterChangeNode::SET); + } + } + return dirty; +} + +nsCounterList* nsCounterManager::CounterListFor(nsAtom* aCounterName) { + MOZ_ASSERT(aCounterName); + return mNames.LookupForAdd(aCounterName) + .OrInsert([]() { return new nsCounterList(); }) + .get(); +} + +void nsCounterManager::RecalcAll() { + for (auto iter = mNames.Iter(); !iter.Done(); iter.Next()) { + nsCounterList* list = iter.UserData(); + if (list->IsDirty()) { + list->RecalcAll(); + } + } +} + +void nsCounterManager::SetAllDirty() { + for (auto iter = mNames.Iter(); !iter.Done(); iter.Next()) { + iter.UserData()->SetDirty(); + } +} + +bool nsCounterManager::DestroyNodesFor(nsIFrame* aFrame) { + MOZ_ASSERT(aFrame->HasAnyStateBits(NS_FRAME_HAS_CSS_COUNTER_STYLE), + "why call me?"); + bool destroyedAny = false; + for (auto iter = mNames.Iter(); !iter.Done(); iter.Next()) { + nsCounterList* list = iter.UserData(); + if (list->DestroyNodesFor(aFrame)) { + destroyedAny = true; + list->SetDirty(); + } + } + return destroyedAny; +} + +#ifdef DEBUG +void nsCounterManager::Dump() { + printf("\n\nCounter Manager Lists:\n"); + for (auto iter = mNames.Iter(); !iter.Done(); iter.Next()) { + printf("Counter named \"%s\":\n", nsAtomCString(iter.Key()).get()); + + nsCounterList* list = iter.UserData(); + int32_t i = 0; + for (nsCounterNode* node = list->First(); node; node = list->Next(node)) { + const char* types[] = {"RESET", "SET", "INCREMENT", "USE"}; + printf( + " Node #%d @%p frame=%p index=%d type=%s valAfter=%d\n" + " scope-start=%p scope-prev=%p", + i++, (void*)node, (void*)node->mPseudoFrame, node->mContentIndex, + types[node->mType], node->mValueAfter, (void*)node->mScopeStart, + (void*)node->mScopePrev); + if (node->mType == nsCounterNode::USE) { + nsAutoString text; + node->UseNode()->GetText(text); + printf(" text=%s", NS_ConvertUTF16toUTF8(text).get()); + } + printf("\n"); + } + } + printf("\n\n"); +} +#endif |