diff options
Diffstat (limited to 'layout/base/nsCounterManager.h')
-rw-r--r-- | layout/base/nsCounterManager.h | 350 |
1 files changed, 350 insertions, 0 deletions
diff --git a/layout/base/nsCounterManager.h b/layout/base/nsCounterManager.h new file mode 100644 index 0000000000..8e9d8105dc --- /dev/null +++ b/layout/base/nsCounterManager.h @@ -0,0 +1,350 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// vim:cindent:ai:sw=4:ts=4:et: +/* 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) */ + +#ifndef nsCounterManager_h_ +#define nsCounterManager_h_ + +#include "mozilla/Attributes.h" +#include "nsGenConList.h" +#include "nsClassHashtable.h" +#include "mozilla/Likely.h" +#include "CounterStyleManager.h" + +class nsCounterList; +struct nsCounterUseNode; +struct nsCounterChangeNode; + +namespace mozilla { + +class ContainStyleScope; + +} // namespace mozilla + +struct nsCounterNode : public nsGenConNode { + enum Type { + RESET, // a "counter number" pair in 'counter-reset' + INCREMENT, // a "counter number" pair in 'counter-increment' + SET, // a "counter number" pair in 'counter-set' + USE // counter() or counters() in 'content' + }; + + Type mType; + + // Counter value after this node + int32_t mValueAfter = 0; + + // mScopeStart points to the node (usually a RESET, but not in the + // case of an implied 'counter-reset') that created the scope for + // this element (for a RESET, its outer scope, i.e., the one it is + // inside rather than the one it creates). + + // May be null for all types, but only when mScopePrev is also null. + // Being null for a non-RESET means that it is an implied + // 'counter-reset'. Being null for a RESET means it has no outer + // scope. + nsCounterNode* mScopeStart = nullptr; + + // mScopePrev points to the previous node that is in the same scope, + // or for a RESET, the previous node in the scope outside of the + // reset. + + // May be null for all types, but only when mScopeStart is also + // null. Following the mScopePrev links will eventually lead to + // mScopeStart. Being null for a non-RESET means that it is an + // implied 'counter-reset'. Being null for a RESET means it has no + // outer scope. + nsCounterNode* mScopePrev = nullptr; + + // Whether or not this node's scope crosses `contain: style` boundaries. + // This can happen for USE nodes that come before any other types of + // nodes in a `contain: style` boundary's list. + bool mCrossesContainStyleBoundaries = false; + + inline nsCounterUseNode* UseNode(); + inline nsCounterChangeNode* ChangeNode(); + + // For RESET, INCREMENT and SET nodes, aPseudoFrame need not be a + // pseudo-element, and aContentIndex represents the index within the + // 'counter-reset', 'counter-increment' or 'counter-set' property + // instead of within the 'content' property but offset to ensure + // that (reset, increment, set, use) sort in that order. + // It is zero for legacy bullet USE counter nodes. + // (This slight weirdness allows sharing a lot of code with 'quotes'.) + nsCounterNode(int32_t aContentIndex, Type aType) + : nsGenConNode(aContentIndex), mType(aType) {} + + // to avoid virtual function calls in the common case + inline void Calc(nsCounterList* aList, bool aNotify); + + // 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 { + mozilla::CounterStylePtr mCounterStyle; + nsString mSeparator; + + // false for counter(), true for counters() + bool mAllCounters = false; + + bool mForLegacyBullet = false; + + enum ForLegacyBullet { ForLegacyBullet }; + nsCounterUseNode(enum ForLegacyBullet, mozilla::CounterStylePtr aCounterStyle) + : nsCounterNode(0, USE), + mCounterStyle(std::move(aCounterStyle)), + mForLegacyBullet(true) {} + + // args go directly to member variables here and of nsGenConNode + nsCounterUseNode(mozilla::CounterStylePtr aCounterStyle, nsString aSeparator, + uint32_t aContentIndex, bool aAllCounters) + : nsCounterNode(aContentIndex, USE), + mCounterStyle(std::move(aCounterStyle)), + mSeparator(std::move(aSeparator)), + mAllCounters(aAllCounters) { + NS_ASSERTION(aContentIndex <= INT32_MAX, "out of range"); + } + + bool InitTextFrame(nsGenConList* aList, nsIFrame* aPseudoFrame, + nsIFrame* aTextFrame) override; + + // assign the correct |mValueAfter| value to a node that has been inserted, + // and update the value of the text node, notifying if `aNotify` is true. + // Should be called immediately after calling |Insert|. + void Calc(nsCounterList* aList, bool aNotify); + + // The text that should be displayed for this counter. + void GetText(nsString& aResult); + void GetText(mozilla::WritingMode aWM, mozilla::CounterStyle* aStyle, + nsString& aResult); +}; + +struct nsCounterChangeNode : public nsCounterNode { + // |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, + 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 + // and then the sets. + aPropIndex + (aChangeType == RESET ? (INT32_MIN) + : (aChangeType == INCREMENT + ? ((INT32_MIN / 3) * 2) + : INT32_MIN / 3)), + aChangeType), + mChangeValue(aChangeValue), + mIsReversed(aIsReversed), + mSeenSetNode(false) { + NS_ASSERTION(aPropIndex >= 0, "out of range"); + NS_ASSERTION( + aChangeType == INCREMENT || aChangeType == SET || aChangeType == RESET, + "bad type"); + mPseudoFrame = aPseudoFrame; + CheckFrameAssertions(); + } + + // 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() { + NS_ASSERTION(mType == USE, "wrong type"); + return static_cast<nsCounterUseNode*>(this); +} + +inline nsCounterChangeNode* nsCounterNode::ChangeNode() { + MOZ_ASSERT(mType == INCREMENT || mType == SET || mType == RESET); + return static_cast<nsCounterChangeNode*>(this); +} + +inline void nsCounterNode::Calc(nsCounterList* aList, bool aNotify) { + if (mType == USE) + UseNode()->Calc(aList, aNotify); + else + ChangeNode()->Calc(aList); +} + +inline bool nsCounterNode::IsContentBasedReset() { + return mType == RESET && + 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(nsAtom* aCounterName, mozilla::ContainStyleScope* aScope) + : nsGenConList(), mCounterName(aCounterName), mScope(aScope) { + MOZ_ASSERT(aScope); + } + + // Return the first node for aFrame on this list, or nullptr. + nsCounterNode* GetFirstNodeFor(nsIFrame* aFrame) const { + return static_cast<nsCounterNode*>(nsGenConList::GetFirstNodeFor(aFrame)); + } + + void Insert(nsCounterNode* aNode) { + nsGenConList::Insert(aNode); + // Don't SetScope if we're dirty -- we'll reset all the scopes anyway, + // and we can't usefully compute scopes right now. + if (MOZ_LIKELY(!IsDirty())) { + SetScope(aNode); + } + } + + nsCounterNode* First() { + return static_cast<nsCounterNode*>(mList.getFirst()); + } + + static nsCounterNode* Next(nsCounterNode* aNode) { + return static_cast<nsCounterNode*>(nsGenConList::Next(aNode)); + } + static nsCounterNode* Prev(nsCounterNode* aNode) { + return static_cast<nsCounterNode*>(nsGenConList::Prev(aNode)); + } + + static int32_t ValueBefore(nsCounterNode* aNode) { + if (!aNode->mScopePrev) { + return 0; + } + + if (aNode->mType != nsCounterNode::USE && + aNode->mScopePrev->mCrossesContainStyleBoundaries) { + return 0; + } + + return aNode->mScopePrev->mValueAfter; + } + + // Correctly set |aNode->mScopeStart| and |aNode->mScopePrev| + void SetScope(nsCounterNode* aNode); + + // Recalculate |mScopeStart|, |mScopePrev|, and |mValueAfter| for + // all nodes and update text in text content nodes. + void RecalcAll(); + + bool IsDirty() const; + void SetDirty(); + bool IsRecalculatingAll() const { return mRecalculatingAll; } + + private: + bool SetScopeByWalkingBackwardThroughList( + nsCounterNode* aNodeToSetScopeFor, const nsIContent* aNodeContent, + nsCounterNode* aNodeToBeginLookingAt); + + RefPtr<nsAtom> mCounterName; + mozilla::ContainStyleScope* mScope; + bool mRecalculatingAll = false; +}; + +/** + * The counter manager maintains an |nsCounterList| for each named + * counter to keep track of all scopes with that name. + */ +class nsCounterManager { + public: + explicit nsCounterManager(mozilla::ContainStyleScope* scope) + : mScope(scope) {} + + // Returns true if dirty + bool AddCounterChanges(nsIFrame* aFrame); + + // Gets the appropriate counter list, creating it if necessary. + // Guaranteed to return non-null. (Uses an infallible hashtable API.) + nsCounterList* GetOrCreateCounterList(nsAtom* aCounterName); + + // Gets the appropriate counter list, returning null if it doesn't exist. + nsCounterList* GetCounterList(nsAtom* aCounterName); + + // Clean up data in any dirty counter lists. + void RecalcAll(); + + // Set all counter lists dirty + void SetAllDirty(); + + // Destroy nodes for the frame in any lists, and return whether any + // nodes were destroyed. + bool DestroyNodesFor(nsIFrame* aFrame); + + // Clear all data. + void Clear() { mNames.Clear(); } + +#ifdef ACCESSIBILITY + // Set |aOrdinal| to the first used counter value for the given frame and + // return true. If no USE node for the given frame can be found, return false + // and do not change the value of |aOrdinal|. + bool GetFirstCounterValueForFrame(nsIFrame* aFrame, + mozilla::CounterValue& aOrdinal) const; +#endif + +#if defined(DEBUG) || defined(MOZ_LAYOUT_DEBUGGER) + void Dump() const; +#endif + + static int32_t IncrementCounter(int32_t aOldValue, int32_t aIncrement) { + // Addition of unsigned values is defined to be arithmetic + // modulo 2^bits (C++ 2011, 3.9.1 [basic.fundamental], clause 4); + // addition of signed values is undefined (and clang does + // something very strange if we use it here). Likewise integral + // conversion from signed to unsigned is also defined as modulo + // 2^bits (C++ 2011, 4.7 [conv.integral], clause 2); conversion + // from unsigned to signed is however undefined (ibid., clause 3), + // but to do what we want we must nonetheless depend on that + // small piece of undefined behavior. + int32_t newValue = int32_t(uint32_t(aOldValue) + uint32_t(aIncrement)); + // The CSS Working Group resolved that a counter-increment that + // exceeds internal limits should not increment at all. + // http://lists.w3.org/Archives/Public/www-style/2013Feb/0392.html + // (This means, for example, that if aIncrement is 5, the + // counter will get stuck at the largest multiple of 5 less than + // the maximum 32-bit integer.) + if ((aIncrement > 0) != (newValue > aOldValue)) { + newValue = aOldValue; + } + return newValue; + } + + private: + mozilla::ContainStyleScope* mScope; + nsClassHashtable<nsRefPtrHashKey<nsAtom>, nsCounterList> mNames; +}; + +#endif /* nsCounterManager_h_ */ |