1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/layout/base/nsCounterManager.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,334 @@ 1.4 +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 1.5 +// vim:cindent:ai:sw=4:ts=4:et: 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +/* implementation of CSS counters (for numbering things) */ 1.11 + 1.12 +#include "nsCounterManager.h" 1.13 +#include "nsBulletFrame.h" // legacy location for list style type to text code 1.14 +#include "nsContentUtils.h" 1.15 +#include "nsTArray.h" 1.16 +#include "mozilla/Likely.h" 1.17 +#include "nsIContent.h" 1.18 + 1.19 +bool 1.20 +nsCounterUseNode::InitTextFrame(nsGenConList* aList, 1.21 + nsIFrame* aPseudoFrame, nsIFrame* aTextFrame) 1.22 +{ 1.23 + nsCounterNode::InitTextFrame(aList, aPseudoFrame, aTextFrame); 1.24 + 1.25 + nsCounterList *counterList = static_cast<nsCounterList*>(aList); 1.26 + counterList->Insert(this); 1.27 + bool dirty = counterList->IsDirty(); 1.28 + if (!dirty) { 1.29 + if (counterList->IsLast(this)) { 1.30 + Calc(counterList); 1.31 + nsAutoString contentString; 1.32 + GetText(contentString); 1.33 + aTextFrame->GetContent()->SetText(contentString, false); 1.34 + } else { 1.35 + // In all other cases (list already dirty or node not at the end), 1.36 + // just start with an empty string for now and when we recalculate 1.37 + // the list we'll change the value to the right one. 1.38 + counterList->SetDirty(); 1.39 + return true; 1.40 + } 1.41 + } 1.42 + 1.43 + return false; 1.44 +} 1.45 + 1.46 +// assign the correct |mValueAfter| value to a node that has been inserted 1.47 +// Should be called immediately after calling |Insert|. 1.48 +void nsCounterUseNode::Calc(nsCounterList *aList) 1.49 +{ 1.50 + NS_ASSERTION(!aList->IsDirty(), 1.51 + "Why are we calculating with a dirty list?"); 1.52 + mValueAfter = aList->ValueBefore(this); 1.53 +} 1.54 + 1.55 +// assign the correct |mValueAfter| value to a node that has been inserted 1.56 +// Should be called immediately after calling |Insert|. 1.57 +void nsCounterChangeNode::Calc(nsCounterList *aList) 1.58 +{ 1.59 + NS_ASSERTION(!aList->IsDirty(), 1.60 + "Why are we calculating with a dirty list?"); 1.61 + if (mType == RESET) { 1.62 + mValueAfter = mChangeValue; 1.63 + } else { 1.64 + NS_ASSERTION(mType == INCREMENT, "invalid type"); 1.65 + mValueAfter = nsCounterManager::IncrementCounter(aList->ValueBefore(this), 1.66 + mChangeValue); 1.67 + } 1.68 +} 1.69 + 1.70 +// The text that should be displayed for this counter. 1.71 +void 1.72 +nsCounterUseNode::GetText(nsString& aResult) 1.73 +{ 1.74 + aResult.Truncate(); 1.75 + 1.76 + nsAutoTArray<nsCounterNode*, 8> stack; 1.77 + stack.AppendElement(static_cast<nsCounterNode*>(this)); 1.78 + 1.79 + if (mAllCounters && mScopeStart) 1.80 + for (nsCounterNode *n = mScopeStart; n->mScopePrev; n = n->mScopeStart) 1.81 + stack.AppendElement(n->mScopePrev); 1.82 + 1.83 + const nsCSSValue& styleItem = mCounterStyle->Item(mAllCounters ? 2 : 1); 1.84 + int32_t style = styleItem.GetIntValue(); 1.85 + const char16_t* separator; 1.86 + if (mAllCounters) 1.87 + separator = mCounterStyle->Item(1).GetStringBufferValue(); 1.88 + 1.89 + for (uint32_t i = stack.Length() - 1;; --i) { 1.90 + nsCounterNode *n = stack[i]; 1.91 + bool isTextRTL; 1.92 + nsBulletFrame::AppendCounterText( 1.93 + style, n->mValueAfter, aResult, isTextRTL); 1.94 + if (i == 0) 1.95 + break; 1.96 + NS_ASSERTION(mAllCounters, "yikes, separator is uninitialized"); 1.97 + aResult.Append(separator); 1.98 + } 1.99 +} 1.100 + 1.101 +void 1.102 +nsCounterList::SetScope(nsCounterNode *aNode) 1.103 +{ 1.104 + // This function is responsible for setting |mScopeStart| and 1.105 + // |mScopePrev| (whose purpose is described in nsCounterManager.h). 1.106 + // We do this by starting from the node immediately preceding 1.107 + // |aNode| in content tree order, which is reasonably likely to be 1.108 + // the previous element in our scope (or, for a reset, the previous 1.109 + // element in the containing scope, which is what we want). If 1.110 + // we're not in the same scope that it is, then it's too deep in the 1.111 + // frame tree, so we walk up parent scopes until we find something 1.112 + // appropriate. 1.113 + 1.114 + if (aNode == First()) { 1.115 + aNode->mScopeStart = nullptr; 1.116 + aNode->mScopePrev = nullptr; 1.117 + return; 1.118 + } 1.119 + 1.120 + // Get the content node for aNode's rendering object's *parent*, 1.121 + // since scope includes siblings, so we want a descendant check on 1.122 + // parents. 1.123 + nsIContent *nodeContent = aNode->mPseudoFrame->GetContent()->GetParent(); 1.124 + 1.125 + for (nsCounterNode *prev = Prev(aNode), *start; 1.126 + prev; prev = start->mScopePrev) { 1.127 + // If |prev| starts a scope (because it's a real or implied 1.128 + // reset), we want it as the scope start rather than the start 1.129 + // of its enclosing scope. Otherwise, there's no enclosing 1.130 + // scope, so the next thing in prev's scope shares its scope 1.131 + // start. 1.132 + start = (prev->mType == nsCounterNode::RESET || !prev->mScopeStart) 1.133 + ? prev : prev->mScopeStart; 1.134 + 1.135 + // |startContent| is analogous to |nodeContent| (see above). 1.136 + nsIContent *startContent = start->mPseudoFrame->GetContent()->GetParent(); 1.137 + NS_ASSERTION(nodeContent || !startContent, 1.138 + "null check on startContent should be sufficient to " 1.139 + "null check nodeContent as well, since if nodeContent " 1.140 + "is for the root, startContent (which is before it) " 1.141 + "must be too"); 1.142 + 1.143 + // A reset's outer scope can't be a scope created by a sibling. 1.144 + if (!(aNode->mType == nsCounterNode::RESET && 1.145 + nodeContent == startContent) && 1.146 + // everything is inside the root (except the case above, 1.147 + // a second reset on the root) 1.148 + (!startContent || 1.149 + nsContentUtils::ContentIsDescendantOf(nodeContent, 1.150 + startContent))) { 1.151 + aNode->mScopeStart = start; 1.152 + aNode->mScopePrev = prev; 1.153 + return; 1.154 + } 1.155 + } 1.156 + 1.157 + aNode->mScopeStart = nullptr; 1.158 + aNode->mScopePrev = nullptr; 1.159 +} 1.160 + 1.161 +void 1.162 +nsCounterList::RecalcAll() 1.163 +{ 1.164 + mDirty = false; 1.165 + 1.166 + nsCounterNode *node = First(); 1.167 + if (!node) 1.168 + return; 1.169 + 1.170 + do { 1.171 + SetScope(node); 1.172 + node->Calc(this); 1.173 + 1.174 + if (node->mType == nsCounterNode::USE) { 1.175 + nsCounterUseNode *useNode = node->UseNode(); 1.176 + // Null-check mText, since if the frame constructor isn't 1.177 + // batching, we could end up here while the node is being 1.178 + // constructed. 1.179 + if (useNode->mText) { 1.180 + nsAutoString text; 1.181 + useNode->GetText(text); 1.182 + useNode->mText->SetData(text); 1.183 + } 1.184 + } 1.185 + } while ((node = Next(node)) != First()); 1.186 +} 1.187 + 1.188 +nsCounterManager::nsCounterManager() 1.189 + : mNames(16) 1.190 +{ 1.191 +} 1.192 + 1.193 +bool 1.194 +nsCounterManager::AddCounterResetsAndIncrements(nsIFrame *aFrame) 1.195 +{ 1.196 + const nsStyleContent *styleContent = aFrame->StyleContent(); 1.197 + if (!styleContent->CounterIncrementCount() && 1.198 + !styleContent->CounterResetCount()) 1.199 + return false; 1.200 + 1.201 + // Add in order, resets first, so all the comparisons will be optimized 1.202 + // for addition at the end of the list. 1.203 + int32_t i, i_end; 1.204 + bool dirty = false; 1.205 + for (i = 0, i_end = styleContent->CounterResetCount(); i != i_end; ++i) 1.206 + dirty |= AddResetOrIncrement(aFrame, i, 1.207 + styleContent->GetCounterResetAt(i), 1.208 + nsCounterChangeNode::RESET); 1.209 + for (i = 0, i_end = styleContent->CounterIncrementCount(); i != i_end; ++i) 1.210 + dirty |= AddResetOrIncrement(aFrame, i, 1.211 + styleContent->GetCounterIncrementAt(i), 1.212 + nsCounterChangeNode::INCREMENT); 1.213 + return dirty; 1.214 +} 1.215 + 1.216 +bool 1.217 +nsCounterManager::AddResetOrIncrement(nsIFrame *aFrame, int32_t aIndex, 1.218 + const nsStyleCounterData *aCounterData, 1.219 + nsCounterNode::Type aType) 1.220 +{ 1.221 + nsCounterChangeNode *node = 1.222 + new nsCounterChangeNode(aFrame, aType, aCounterData->mValue, aIndex); 1.223 + 1.224 + nsCounterList *counterList = CounterListFor(aCounterData->mCounter); 1.225 + if (!counterList) { 1.226 + NS_NOTREACHED("CounterListFor failed (should only happen on OOM)"); 1.227 + return false; 1.228 + } 1.229 + 1.230 + counterList->Insert(node); 1.231 + if (!counterList->IsLast(node)) { 1.232 + // Tell the caller it's responsible for recalculating the entire 1.233 + // list. 1.234 + counterList->SetDirty(); 1.235 + return true; 1.236 + } 1.237 + 1.238 + // Don't call Calc() if the list is already dirty -- it'll be recalculated 1.239 + // anyway, and trying to calculate with a dirty list doesn't work. 1.240 + if (MOZ_LIKELY(!counterList->IsDirty())) { 1.241 + node->Calc(counterList); 1.242 + } 1.243 + return false; 1.244 +} 1.245 + 1.246 +nsCounterList* 1.247 +nsCounterManager::CounterListFor(const nsSubstring& aCounterName) 1.248 +{ 1.249 + // XXX Why doesn't nsTHashtable provide an API that allows us to use 1.250 + // get/put in one hashtable lookup? 1.251 + nsCounterList *counterList; 1.252 + if (!mNames.Get(aCounterName, &counterList)) { 1.253 + counterList = new nsCounterList(); 1.254 + mNames.Put(aCounterName, counterList); 1.255 + } 1.256 + return counterList; 1.257 +} 1.258 + 1.259 +static PLDHashOperator 1.260 +RecalcDirtyLists(const nsAString& aKey, nsCounterList* aList, void* aClosure) 1.261 +{ 1.262 + if (aList->IsDirty()) 1.263 + aList->RecalcAll(); 1.264 + return PL_DHASH_NEXT; 1.265 +} 1.266 + 1.267 +void 1.268 +nsCounterManager::RecalcAll() 1.269 +{ 1.270 + mNames.EnumerateRead(RecalcDirtyLists, nullptr); 1.271 +} 1.272 + 1.273 +struct DestroyNodesData { 1.274 + DestroyNodesData(nsIFrame *aFrame) 1.275 + : mFrame(aFrame) 1.276 + , mDestroyedAny(false) 1.277 + { 1.278 + } 1.279 + 1.280 + nsIFrame *mFrame; 1.281 + bool mDestroyedAny; 1.282 +}; 1.283 + 1.284 +static PLDHashOperator 1.285 +DestroyNodesInList(const nsAString& aKey, nsCounterList* aList, void* aClosure) 1.286 +{ 1.287 + DestroyNodesData *data = static_cast<DestroyNodesData*>(aClosure); 1.288 + if (aList->DestroyNodesFor(data->mFrame)) { 1.289 + data->mDestroyedAny = true; 1.290 + aList->SetDirty(); 1.291 + } 1.292 + return PL_DHASH_NEXT; 1.293 +} 1.294 + 1.295 +bool 1.296 +nsCounterManager::DestroyNodesFor(nsIFrame *aFrame) 1.297 +{ 1.298 + DestroyNodesData data(aFrame); 1.299 + mNames.EnumerateRead(DestroyNodesInList, &data); 1.300 + return data.mDestroyedAny; 1.301 +} 1.302 + 1.303 +#ifdef DEBUG 1.304 +static PLDHashOperator 1.305 +DumpList(const nsAString& aKey, nsCounterList* aList, void* aClosure) 1.306 +{ 1.307 + printf("Counter named \"%s\":\n", NS_ConvertUTF16toUTF8(aKey).get()); 1.308 + nsCounterNode *node = aList->First(); 1.309 + 1.310 + if (node) { 1.311 + int32_t i = 0; 1.312 + do { 1.313 + const char *types[] = { "RESET", "INCREMENT", "USE" }; 1.314 + printf(" Node #%d @%p frame=%p index=%d type=%s valAfter=%d\n" 1.315 + " scope-start=%p scope-prev=%p", 1.316 + i++, (void*)node, (void*)node->mPseudoFrame, 1.317 + node->mContentIndex, types[node->mType], node->mValueAfter, 1.318 + (void*)node->mScopeStart, (void*)node->mScopePrev); 1.319 + if (node->mType == nsCounterNode::USE) { 1.320 + nsAutoString text; 1.321 + node->UseNode()->GetText(text); 1.322 + printf(" text=%s", NS_ConvertUTF16toUTF8(text).get()); 1.323 + } 1.324 + printf("\n"); 1.325 + } while ((node = aList->Next(node)) != aList->First()); 1.326 + } 1.327 + return PL_DHASH_NEXT; 1.328 +} 1.329 + 1.330 +void 1.331 +nsCounterManager::Dump() 1.332 +{ 1.333 + printf("\n\nCounter Manager Lists:\n"); 1.334 + mNames.EnumerateRead(DumpList, nullptr); 1.335 + printf("\n\n"); 1.336 +} 1.337 +#endif