nsCounterManager.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2. /* This Source Code Form is subject to the terms of the Mozilla Public
  3. * License, v. 2.0. If a copy of the MPL was not distributed with this
  4. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  5. /* implementation of CSS counters (for numbering things) */
  6. #include "nsCounterManager.h"
  7. #include "mozilla/Likely.h"
  8. #include "mozilla/WritingModes.h"
  9. #include "nsBulletFrame.h" // legacy location for list style type to text code
  10. #include "nsContentUtils.h"
  11. #include "nsIContent.h"
  12. #include "nsTArray.h"
  13. using namespace mozilla;
  14. bool
  15. nsCounterUseNode::InitTextFrame(nsGenConList* aList,
  16. nsIFrame* aPseudoFrame, nsIFrame* aTextFrame)
  17. {
  18. nsCounterNode::InitTextFrame(aList, aPseudoFrame, aTextFrame);
  19. nsCounterList *counterList = static_cast<nsCounterList*>(aList);
  20. counterList->Insert(this);
  21. bool dirty = counterList->IsDirty();
  22. if (!dirty) {
  23. if (counterList->IsLast(this)) {
  24. Calc(counterList);
  25. nsAutoString contentString;
  26. GetText(contentString);
  27. aTextFrame->GetContent()->SetText(contentString, false);
  28. } else {
  29. // In all other cases (list already dirty or node not at the end),
  30. // just start with an empty string for now and when we recalculate
  31. // the list we'll change the value to the right one.
  32. counterList->SetDirty();
  33. return true;
  34. }
  35. }
  36. return false;
  37. }
  38. CounterStyle*
  39. nsCounterUseNode::GetCounterStyle()
  40. {
  41. if (!mCounterStyle) {
  42. const nsCSSValue& style = mCounterFunction->Item(mAllCounters ? 2 : 1);
  43. CounterStyleManager* manager = mPresContext->CounterStyleManager();
  44. if (style.GetUnit() == eCSSUnit_Ident) {
  45. nsString ident;
  46. style.GetStringValue(ident);
  47. mCounterStyle = manager->BuildCounterStyle(ident);
  48. } else if (style.GetUnit() == eCSSUnit_Symbols) {
  49. mCounterStyle = new AnonymousCounterStyle(style.GetArrayValue());
  50. } else {
  51. NS_NOTREACHED("Unknown counter style");
  52. mCounterStyle = CounterStyleManager::GetDecimalStyle();
  53. }
  54. }
  55. return mCounterStyle;
  56. }
  57. // assign the correct |mValueAfter| value to a node that has been inserted
  58. // Should be called immediately after calling |Insert|.
  59. void nsCounterUseNode::Calc(nsCounterList *aList)
  60. {
  61. NS_ASSERTION(!aList->IsDirty(),
  62. "Why are we calculating with a dirty list?");
  63. mValueAfter = aList->ValueBefore(this);
  64. }
  65. // assign the correct |mValueAfter| value to a node that has been inserted
  66. // Should be called immediately after calling |Insert|.
  67. void nsCounterChangeNode::Calc(nsCounterList *aList)
  68. {
  69. NS_ASSERTION(!aList->IsDirty(),
  70. "Why are we calculating with a dirty list?");
  71. if (mType == RESET) {
  72. mValueAfter = mChangeValue;
  73. } else {
  74. NS_ASSERTION(mType == INCREMENT, "invalid type");
  75. mValueAfter = nsCounterManager::IncrementCounter(aList->ValueBefore(this),
  76. mChangeValue);
  77. }
  78. }
  79. // The text that should be displayed for this counter.
  80. void
  81. nsCounterUseNode::GetText(nsString& aResult)
  82. {
  83. aResult.Truncate();
  84. AutoTArray<nsCounterNode*, 8> stack;
  85. stack.AppendElement(static_cast<nsCounterNode*>(this));
  86. if (mAllCounters && mScopeStart)
  87. for (nsCounterNode *n = mScopeStart; n->mScopePrev; n = n->mScopeStart)
  88. stack.AppendElement(n->mScopePrev);
  89. const char16_t* separator;
  90. if (mAllCounters)
  91. separator = mCounterFunction->Item(1).GetStringBufferValue();
  92. CounterStyle* style = GetCounterStyle();
  93. WritingMode wm = mPseudoFrame ?
  94. mPseudoFrame->GetWritingMode() : WritingMode();
  95. for (uint32_t i = stack.Length() - 1;; --i) {
  96. nsCounterNode *n = stack[i];
  97. nsAutoString text;
  98. bool isTextRTL;
  99. style->GetCounterText(n->mValueAfter, wm, text, isTextRTL);
  100. aResult.Append(text);
  101. if (i == 0)
  102. break;
  103. NS_ASSERTION(mAllCounters, "yikes, separator is uninitialized");
  104. aResult.Append(separator);
  105. }
  106. }
  107. void
  108. nsCounterList::SetScope(nsCounterNode *aNode)
  109. {
  110. // This function is responsible for setting |mScopeStart| and
  111. // |mScopePrev| (whose purpose is described in nsCounterManager.h).
  112. // We do this by starting from the node immediately preceding
  113. // |aNode| in content tree order, which is reasonably likely to be
  114. // the previous element in our scope (or, for a reset, the previous
  115. // element in the containing scope, which is what we want). If
  116. // we're not in the same scope that it is, then it's too deep in the
  117. // frame tree, so we walk up parent scopes until we find something
  118. // appropriate.
  119. if (aNode == First()) {
  120. aNode->mScopeStart = nullptr;
  121. aNode->mScopePrev = nullptr;
  122. return;
  123. }
  124. // Get the content node for aNode's rendering object's *parent*,
  125. // since scope includes siblings, so we want a descendant check on
  126. // parents.
  127. nsIContent *nodeContent = aNode->mPseudoFrame->GetContent()->GetParent();
  128. for (nsCounterNode *prev = Prev(aNode), *start;
  129. prev; prev = start->mScopePrev) {
  130. // If |prev| starts a scope (because it's a real or implied
  131. // reset), we want it as the scope start rather than the start
  132. // of its enclosing scope. Otherwise, there's no enclosing
  133. // scope, so the next thing in prev's scope shares its scope
  134. // start.
  135. start = (prev->mType == nsCounterNode::RESET || !prev->mScopeStart)
  136. ? prev : prev->mScopeStart;
  137. // |startContent| is analogous to |nodeContent| (see above).
  138. nsIContent *startContent = start->mPseudoFrame->GetContent()->GetParent();
  139. NS_ASSERTION(nodeContent || !startContent,
  140. "null check on startContent should be sufficient to "
  141. "null check nodeContent as well, since if nodeContent "
  142. "is for the root, startContent (which is before it) "
  143. "must be too");
  144. // A reset's outer scope can't be a scope created by a sibling.
  145. if (!(aNode->mType == nsCounterNode::RESET &&
  146. nodeContent == startContent) &&
  147. // everything is inside the root (except the case above,
  148. // a second reset on the root)
  149. (!startContent ||
  150. nsContentUtils::ContentIsDescendantOf(nodeContent,
  151. startContent))) {
  152. aNode->mScopeStart = start;
  153. aNode->mScopePrev = prev;
  154. return;
  155. }
  156. }
  157. aNode->mScopeStart = nullptr;
  158. aNode->mScopePrev = nullptr;
  159. }
  160. void
  161. nsCounterList::RecalcAll()
  162. {
  163. mDirty = false;
  164. for (nsCounterNode* node = First(); node; node = Next(node)) {
  165. SetScope(node);
  166. node->Calc(this);
  167. if (node->mType == nsCounterNode::USE) {
  168. nsCounterUseNode *useNode = node->UseNode();
  169. // Null-check mText, since if the frame constructor isn't
  170. // batching, we could end up here while the node is being
  171. // constructed.
  172. if (useNode->mText) {
  173. nsAutoString text;
  174. useNode->GetText(text);
  175. useNode->mText->SetData(text);
  176. }
  177. }
  178. }
  179. }
  180. nsCounterManager::nsCounterManager()
  181. : mNames()
  182. {
  183. }
  184. bool
  185. nsCounterManager::AddCounterResetsAndIncrements(nsIFrame *aFrame)
  186. {
  187. const nsStyleContent *styleContent = aFrame->StyleContent();
  188. if (!styleContent->CounterIncrementCount() &&
  189. !styleContent->CounterResetCount())
  190. return false;
  191. // Add in order, resets first, so all the comparisons will be optimized
  192. // for addition at the end of the list.
  193. int32_t i, i_end;
  194. bool dirty = false;
  195. for (i = 0, i_end = styleContent->CounterResetCount(); i != i_end; ++i)
  196. dirty |= AddResetOrIncrement(aFrame, i, styleContent->CounterResetAt(i),
  197. nsCounterChangeNode::RESET);
  198. for (i = 0, i_end = styleContent->CounterIncrementCount(); i != i_end; ++i)
  199. dirty |=
  200. AddResetOrIncrement(aFrame, i, styleContent->CounterIncrementAt(i),
  201. nsCounterChangeNode::INCREMENT);
  202. return dirty;
  203. }
  204. bool
  205. nsCounterManager::AddResetOrIncrement(nsIFrame* aFrame, int32_t aIndex,
  206. const nsStyleCounterData& aCounterData,
  207. nsCounterNode::Type aType)
  208. {
  209. nsCounterChangeNode* node =
  210. new nsCounterChangeNode(aFrame, aType, aCounterData.mValue, aIndex);
  211. nsCounterList* counterList = CounterListFor(aCounterData.mCounter);
  212. counterList->Insert(node);
  213. if (!counterList->IsLast(node)) {
  214. // Tell the caller it's responsible for recalculating the entire
  215. // list.
  216. counterList->SetDirty();
  217. return true;
  218. }
  219. // Don't call Calc() if the list is already dirty -- it'll be recalculated
  220. // anyway, and trying to calculate with a dirty list doesn't work.
  221. if (MOZ_LIKELY(!counterList->IsDirty())) {
  222. node->Calc(counterList);
  223. }
  224. return false;
  225. }
  226. nsCounterList*
  227. nsCounterManager::CounterListFor(const nsSubstring& aCounterName)
  228. {
  229. // XXX Why doesn't nsTHashtable provide an API that allows us to use
  230. // get/put in one hashtable lookup?
  231. nsCounterList *counterList;
  232. if (!mNames.Get(aCounterName, &counterList)) {
  233. counterList = new nsCounterList();
  234. mNames.Put(aCounterName, counterList);
  235. }
  236. return counterList;
  237. }
  238. void
  239. nsCounterManager::RecalcAll()
  240. {
  241. for (auto iter = mNames.Iter(); !iter.Done(); iter.Next()) {
  242. nsCounterList* list = iter.UserData();
  243. if (list->IsDirty()) {
  244. list->RecalcAll();
  245. }
  246. }
  247. }
  248. void
  249. nsCounterManager::SetAllCounterStylesDirty()
  250. {
  251. for (auto iter = mNames.Iter(); !iter.Done(); iter.Next()) {
  252. nsCounterList* list = iter.UserData();
  253. bool changed = false;
  254. for (nsCounterNode* node = list->First(); node; node = list->Next(node)) {
  255. if (node->mType == nsCounterNode::USE) {
  256. node->UseNode()->SetCounterStyleDirty();
  257. changed = true;
  258. }
  259. }
  260. if (changed) {
  261. list->SetDirty();
  262. }
  263. }
  264. }
  265. bool
  266. nsCounterManager::DestroyNodesFor(nsIFrame *aFrame)
  267. {
  268. bool destroyedAny = false;
  269. for (auto iter = mNames.Iter(); !iter.Done(); iter.Next()) {
  270. nsCounterList* list = iter.UserData();
  271. if (list->DestroyNodesFor(aFrame)) {
  272. destroyedAny = true;
  273. list->SetDirty();
  274. }
  275. }
  276. return destroyedAny;
  277. }
  278. #ifdef DEBUG
  279. void
  280. nsCounterManager::Dump()
  281. {
  282. printf("\n\nCounter Manager Lists:\n");
  283. for (auto iter = mNames.Iter(); !iter.Done(); iter.Next()) {
  284. printf("Counter named \"%s\":\n",
  285. NS_ConvertUTF16toUTF8(iter.Key()).get());
  286. nsCounterList* list = iter.UserData();
  287. int32_t i = 0;
  288. for (nsCounterNode* node = list->First(); node; node = list->Next(node)) {
  289. const char* types[] = { "RESET", "INCREMENT", "USE" };
  290. printf(" Node #%d @%p frame=%p index=%d type=%s valAfter=%d\n"
  291. " scope-start=%p scope-prev=%p",
  292. i++, (void*)node, (void*)node->mPseudoFrame,
  293. node->mContentIndex, types[node->mType],
  294. node->mValueAfter, (void*)node->mScopeStart,
  295. (void*)node->mScopePrev);
  296. if (node->mType == nsCounterNode::USE) {
  297. nsAutoString text;
  298. node->UseNode()->GetText(text);
  299. printf(" text=%s", NS_ConvertUTF16toUTF8(text).get());
  300. }
  301. printf("\n");
  302. }
  303. }
  304. printf("\n\n");
  305. }
  306. #endif