layout/base/nsCounterManager.cpp

changeset 0
6474c204b198
     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

mercurial