michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: // vim:cindent:ai:sw=4:ts=4:et: michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* implementation of CSS counters (for numbering things) */ michael@0: michael@0: #ifndef nsCounterManager_h_ michael@0: #define nsCounterManager_h_ michael@0: michael@0: #include "mozilla/Attributes.h" michael@0: #include "nsGenConList.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsClassHashtable.h" michael@0: #include "mozilla/Likely.h" michael@0: michael@0: class nsCounterList; michael@0: struct nsCounterUseNode; michael@0: struct nsCounterChangeNode; michael@0: michael@0: struct nsCounterNode : public nsGenConNode { michael@0: enum Type { michael@0: RESET, // a "counter number" pair in 'counter-reset' michael@0: INCREMENT, // a "counter number" pair in 'counter-increment' michael@0: USE // counter() or counters() in 'content' michael@0: }; michael@0: michael@0: Type mType; michael@0: michael@0: // Counter value after this node michael@0: int32_t mValueAfter; michael@0: michael@0: // mScopeStart points to the node (usually a RESET, but not in the michael@0: // case of an implied 'counter-reset') that created the scope for michael@0: // this element (for a RESET, its outer scope, i.e., the one it is michael@0: // inside rather than the one it creates). michael@0: michael@0: // May be null for all types, but only when mScopePrev is also null. michael@0: // Being null for a non-RESET means that it is an implied michael@0: // 'counter-reset'. Being null for a RESET means it has no outer michael@0: // scope. michael@0: nsCounterNode *mScopeStart; michael@0: michael@0: // mScopePrev points to the previous node that is in the same scope, michael@0: // or for a RESET, the previous node in the scope outside of the michael@0: // reset. michael@0: michael@0: // May be null for all types, but only when mScopeStart is also michael@0: // null. Following the mScopePrev links will eventually lead to michael@0: // mScopeStart. Being null for a non-RESET means that it is an michael@0: // implied 'counter-reset'. Being null for a RESET means it has no michael@0: // outer scope. michael@0: nsCounterNode *mScopePrev; michael@0: michael@0: inline nsCounterUseNode* UseNode(); michael@0: inline nsCounterChangeNode* ChangeNode(); michael@0: michael@0: // For RESET and INCREMENT nodes, aPseudoFrame need not be a michael@0: // pseudo-element, and aContentIndex represents the index within the michael@0: // 'counter-reset' or 'counter-increment' property instead of within michael@0: // the 'content' property but offset to ensure that (reset, michael@0: // increment, use) sort in that order. (This slight weirdness michael@0: // allows sharing a lot of code with 'quotes'.) michael@0: nsCounterNode(int32_t aContentIndex, Type aType) michael@0: : nsGenConNode(aContentIndex) michael@0: , mType(aType) michael@0: , mValueAfter(0) michael@0: , mScopeStart(nullptr) michael@0: , mScopePrev(nullptr) michael@0: { michael@0: } michael@0: michael@0: // to avoid virtual function calls in the common case michael@0: inline void Calc(nsCounterList* aList); michael@0: }; michael@0: michael@0: struct nsCounterUseNode : public nsCounterNode { michael@0: // The same structure passed through the style system: an array michael@0: // containing the values in the counter() or counters() in the order michael@0: // given in the CSS spec. michael@0: nsRefPtr mCounterStyle; michael@0: michael@0: // false for counter(), true for counters() michael@0: bool mAllCounters; michael@0: michael@0: // args go directly to member variables here and of nsGenConNode michael@0: nsCounterUseNode(nsCSSValue::Array* aCounterStyle, michael@0: uint32_t aContentIndex, bool aAllCounters) michael@0: : nsCounterNode(aContentIndex, USE) michael@0: , mCounterStyle(aCounterStyle) michael@0: , mAllCounters(aAllCounters) michael@0: { michael@0: NS_ASSERTION(aContentIndex <= INT32_MAX, "out of range"); michael@0: } michael@0: michael@0: virtual bool InitTextFrame(nsGenConList* aList, michael@0: nsIFrame* aPseudoFrame, nsIFrame* aTextFrame) MOZ_OVERRIDE; michael@0: michael@0: // assign the correct |mValueAfter| value to a node that has been inserted michael@0: // Should be called immediately after calling |Insert|. michael@0: void Calc(nsCounterList* aList); michael@0: michael@0: // The text that should be displayed for this counter. michael@0: void GetText(nsString& aResult); michael@0: }; michael@0: michael@0: struct nsCounterChangeNode : public nsCounterNode { michael@0: int32_t mChangeValue; // the numeric value of the increment or reset michael@0: michael@0: // |aPseudoFrame| is not necessarily a pseudo-element's frame, but michael@0: // since it is for every other subclass of nsGenConNode, we follow michael@0: // the naming convention here. michael@0: // |aPropIndex| is the index of the value within the list in the michael@0: // 'counter-increment' or 'counter-reset' property. michael@0: nsCounterChangeNode(nsIFrame* aPseudoFrame, michael@0: nsCounterNode::Type aChangeType, michael@0: int32_t aChangeValue, michael@0: int32_t aPropIndex) michael@0: : nsCounterNode(// Fake a content index for resets and increments michael@0: // that comes before all the real content, with michael@0: // the resets first, in order, and then the increments. michael@0: aPropIndex + (aChangeType == RESET michael@0: ? (INT32_MIN) michael@0: : (INT32_MIN / 2)), michael@0: aChangeType) michael@0: , mChangeValue(aChangeValue) michael@0: { michael@0: NS_ASSERTION(aPropIndex >= 0, "out of range"); michael@0: NS_ASSERTION(aChangeType == INCREMENT || aChangeType == RESET, michael@0: "bad type"); michael@0: mPseudoFrame = aPseudoFrame; michael@0: CheckFrameAssertions(); michael@0: } michael@0: michael@0: // assign the correct |mValueAfter| value to a node that has been inserted michael@0: // Should be called immediately after calling |Insert|. michael@0: void Calc(nsCounterList* aList); michael@0: }; michael@0: michael@0: inline nsCounterUseNode* nsCounterNode::UseNode() michael@0: { michael@0: NS_ASSERTION(mType == USE, "wrong type"); michael@0: return static_cast(this); michael@0: } michael@0: michael@0: inline nsCounterChangeNode* nsCounterNode::ChangeNode() michael@0: { michael@0: NS_ASSERTION(mType == INCREMENT || mType == RESET, "wrong type"); michael@0: return static_cast(this); michael@0: } michael@0: michael@0: inline void nsCounterNode::Calc(nsCounterList* aList) michael@0: { michael@0: if (mType == USE) michael@0: UseNode()->Calc(aList); michael@0: else michael@0: ChangeNode()->Calc(aList); michael@0: } michael@0: michael@0: class nsCounterList : public nsGenConList { michael@0: public: michael@0: nsCounterList() : nsGenConList(), michael@0: mDirty(false) michael@0: {} michael@0: michael@0: void Insert(nsCounterNode* aNode) { michael@0: nsGenConList::Insert(aNode); michael@0: // Don't SetScope if we're dirty -- we'll reset all the scopes anyway, michael@0: // and we can't usefully compute scopes right now. michael@0: if (MOZ_LIKELY(!IsDirty())) { michael@0: SetScope(aNode); michael@0: } michael@0: } michael@0: michael@0: nsCounterNode* First() { michael@0: return static_cast(mFirstNode); michael@0: } michael@0: michael@0: static nsCounterNode* Next(nsCounterNode* aNode) { michael@0: return static_cast(nsGenConList::Next(aNode)); michael@0: } michael@0: static nsCounterNode* Prev(nsCounterNode* aNode) { michael@0: return static_cast(nsGenConList::Prev(aNode)); michael@0: } michael@0: michael@0: static int32_t ValueBefore(nsCounterNode* aNode) { michael@0: return aNode->mScopePrev ? aNode->mScopePrev->mValueAfter : 0; michael@0: } michael@0: michael@0: // Correctly set |aNode->mScopeStart| and |aNode->mScopePrev| michael@0: void SetScope(nsCounterNode *aNode); michael@0: michael@0: // Recalculate |mScopeStart|, |mScopePrev|, and |mValueAfter| for michael@0: // all nodes and update text in text content nodes. michael@0: void RecalcAll(); michael@0: michael@0: bool IsDirty() { return mDirty; } michael@0: void SetDirty() { mDirty = true; } michael@0: michael@0: private: michael@0: bool mDirty; michael@0: }; michael@0: michael@0: /** michael@0: * The counter manager maintains an |nsCounterList| for each named michael@0: * counter to keep track of all scopes with that name. michael@0: */ michael@0: class nsCounterManager { michael@0: public: michael@0: nsCounterManager(); michael@0: // Returns true if dirty michael@0: bool AddCounterResetsAndIncrements(nsIFrame *aFrame); michael@0: michael@0: // Gets the appropriate counter list, creating it if necessary. michael@0: // Returns null only on out-of-memory. michael@0: nsCounterList* CounterListFor(const nsSubstring& aCounterName); michael@0: michael@0: // Clean up data in any dirty counter lists. michael@0: void RecalcAll(); michael@0: michael@0: // Destroy nodes for the frame in any lists, and return whether any michael@0: // nodes were destroyed. michael@0: bool DestroyNodesFor(nsIFrame *aFrame); michael@0: michael@0: // Clear all data. michael@0: void Clear() { mNames.Clear(); } michael@0: michael@0: #ifdef DEBUG michael@0: void Dump(); michael@0: #endif michael@0: michael@0: static int32_t IncrementCounter(int32_t aOldValue, int32_t aIncrement) michael@0: { michael@0: // Addition of unsigned values is defined to be arithmetic michael@0: // modulo 2^bits (C++ 2011, 3.9.1 [basic.fundamental], clause 4); michael@0: // addition of signed values is undefined (and clang does michael@0: // something very strange if we use it here). Likewise integral michael@0: // conversion from signed to unsigned is also defined as modulo michael@0: // 2^bits (C++ 2011, 4.7 [conv.integral], clause 2); conversion michael@0: // from unsigned to signed is however undefined (ibid., clause 3), michael@0: // but to do what we want we must nonetheless depend on that michael@0: // small piece of undefined behavior. michael@0: int32_t newValue = int32_t(uint32_t(aOldValue) + uint32_t(aIncrement)); michael@0: // The CSS Working Group resolved that a counter-increment that michael@0: // exceeds internal limits should not increment at all. michael@0: // http://lists.w3.org/Archives/Public/www-style/2013Feb/0392.html michael@0: // (This means, for example, that if aIncrement is 5, the michael@0: // counter will get stuck at the largest multiple of 5 less than michael@0: // the maximum 32-bit integer.) michael@0: if ((aIncrement > 0) != (newValue > aOldValue)) { michael@0: newValue = aOldValue; michael@0: } michael@0: return newValue; michael@0: } michael@0: michael@0: private: michael@0: // for |AddCounterResetsAndIncrements| only michael@0: bool AddResetOrIncrement(nsIFrame *aFrame, int32_t aIndex, michael@0: const nsStyleCounterData *aCounterData, michael@0: nsCounterNode::Type aType); michael@0: michael@0: nsClassHashtable mNames; michael@0: }; michael@0: michael@0: #endif /* nsCounterManager_h_ */