michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 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: /** michael@0: * A class which manages pending restyles. This handles keeping track michael@0: * of what nodes restyles need to happen on and so forth. michael@0: */ michael@0: michael@0: #include "RestyleTracker.h" michael@0: #include "nsStyleChangeList.h" michael@0: #include "RestyleManager.h" michael@0: #include "GeckoProfiler.h" michael@0: michael@0: namespace mozilla { michael@0: michael@0: inline nsIDocument* michael@0: RestyleTracker::Document() const { michael@0: return mRestyleManager->PresContext()->Document(); michael@0: } michael@0: michael@0: #define RESTYLE_ARRAY_STACKSIZE 128 michael@0: michael@0: struct LaterSiblingCollector { michael@0: RestyleTracker* tracker; michael@0: nsTArray< nsRefPtr >* elements; michael@0: }; michael@0: michael@0: static PLDHashOperator michael@0: CollectLaterSiblings(nsISupports* aElement, michael@0: RestyleTracker::RestyleData& aData, michael@0: void* aSiblingCollector) michael@0: { michael@0: dom::Element* element = michael@0: static_cast(aElement); michael@0: LaterSiblingCollector* collector = michael@0: static_cast(aSiblingCollector); michael@0: // Only collect the entries that actually need restyling by us (and michael@0: // haven't, for example, already been restyled). michael@0: // It's important to not mess with the flags on entries not in our michael@0: // document. michael@0: if (element->GetCurrentDoc() == collector->tracker->Document() && michael@0: element->HasFlag(collector->tracker->RestyleBit()) && michael@0: (aData.mRestyleHint & eRestyle_LaterSiblings)) { michael@0: collector->elements->AppendElement(element); michael@0: } michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: struct RestyleCollector { michael@0: RestyleTracker* tracker; michael@0: RestyleTracker::RestyleEnumerateData** restyleArrayPtr; michael@0: }; michael@0: michael@0: static PLDHashOperator michael@0: CollectRestyles(nsISupports* aElement, michael@0: RestyleTracker::RestyleData& aData, michael@0: void* aRestyleCollector) michael@0: { michael@0: dom::Element* element = michael@0: static_cast(aElement); michael@0: RestyleCollector* collector = michael@0: static_cast(aRestyleCollector); michael@0: // Only collect the entries that actually need restyling by us (and michael@0: // haven't, for example, already been restyled). michael@0: // It's important to not mess with the flags on entries not in our michael@0: // document. michael@0: if (element->GetCurrentDoc() != collector->tracker->Document() || michael@0: !element->HasFlag(collector->tracker->RestyleBit())) { michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: NS_ASSERTION(!element->HasFlag(collector->tracker->RootBit()) || michael@0: // Maybe we're just not reachable via the frame tree? michael@0: (element->GetFlattenedTreeParent() && michael@0: (!element->GetFlattenedTreeParent()->GetPrimaryFrame()|| michael@0: element->GetFlattenedTreeParent()->GetPrimaryFrame()->IsLeaf())) || michael@0: // Or not reachable due to an async reinsert we have michael@0: // pending? If so, we'll have a reframe hint around. michael@0: // That incidentally makes it safe that we still have michael@0: // the bit, since any descendants that didn't get added michael@0: // to the roots list because we had the bits will be michael@0: // completely restyled in a moment. michael@0: (aData.mChangeHint & nsChangeHint_ReconstructFrame), michael@0: "Why did this not get handled while processing mRestyleRoots?"); michael@0: michael@0: // Unset the restyle bits now, so if they get readded later as we michael@0: // process we won't clobber that adding of the bit. michael@0: element->UnsetFlags(collector->tracker->RestyleBit() | michael@0: collector->tracker->RootBit()); michael@0: michael@0: RestyleTracker::RestyleEnumerateData** restyleArrayPtr = michael@0: collector->restyleArrayPtr; michael@0: RestyleTracker::RestyleEnumerateData* currentRestyle = michael@0: *restyleArrayPtr; michael@0: currentRestyle->mElement = element; michael@0: currentRestyle->mRestyleHint = aData.mRestyleHint; michael@0: currentRestyle->mChangeHint = aData.mChangeHint; michael@0: michael@0: // Increment to the next slot in the array michael@0: *restyleArrayPtr = currentRestyle + 1; michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: inline void michael@0: RestyleTracker::ProcessOneRestyle(Element* aElement, michael@0: nsRestyleHint aRestyleHint, michael@0: nsChangeHint aChangeHint) michael@0: { michael@0: NS_PRECONDITION((aRestyleHint & eRestyle_LaterSiblings) == 0, michael@0: "Someone should have handled this before calling us"); michael@0: NS_PRECONDITION(Document(), "Must have a document"); michael@0: NS_PRECONDITION(aElement->GetCurrentDoc() == Document(), michael@0: "Element has unexpected document"); michael@0: michael@0: nsIFrame* primaryFrame = aElement->GetPrimaryFrame(); michael@0: if (aRestyleHint & (eRestyle_Self | eRestyle_Subtree)) { michael@0: mRestyleManager->RestyleElement(aElement, primaryFrame, aChangeHint, michael@0: *this, michael@0: (aRestyleHint & eRestyle_Subtree) != 0); michael@0: } else if (aChangeHint && michael@0: (primaryFrame || michael@0: (aChangeHint & nsChangeHint_ReconstructFrame))) { michael@0: // Don't need to recompute style; just apply the hint michael@0: nsStyleChangeList changeList; michael@0: changeList.AppendChange(primaryFrame, aElement, aChangeHint); michael@0: mRestyleManager->ProcessRestyledFrames(changeList); michael@0: } michael@0: } michael@0: michael@0: void michael@0: RestyleTracker::DoProcessRestyles() michael@0: { michael@0: PROFILER_LABEL("CSS", "ProcessRestyles"); michael@0: michael@0: mRestyleManager->BeginProcessingRestyles(); michael@0: michael@0: // loop so that we process any restyle events generated by processing michael@0: while (mPendingRestyles.Count()) { michael@0: if (mHaveLaterSiblingRestyles) { michael@0: // Convert them to individual restyles on all the later siblings michael@0: nsAutoTArray, RESTYLE_ARRAY_STACKSIZE> laterSiblingArr; michael@0: LaterSiblingCollector siblingCollector = { this, &laterSiblingArr }; michael@0: mPendingRestyles.Enumerate(CollectLaterSiblings, &siblingCollector); michael@0: for (uint32_t i = 0; i < laterSiblingArr.Length(); ++i) { michael@0: Element* element = laterSiblingArr[i]; michael@0: for (nsIContent* sibling = element->GetNextSibling(); michael@0: sibling; michael@0: sibling = sibling->GetNextSibling()) { michael@0: if (sibling->IsElement() && michael@0: AddPendingRestyle(sibling->AsElement(), eRestyle_Subtree, michael@0: NS_STYLE_HINT_NONE)) { michael@0: // Nothing else to do here; we'll handle the following michael@0: // siblings when we get to |sibling| in laterSiblingArr. michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Now remove all those eRestyle_LaterSiblings bits michael@0: for (uint32_t i = 0; i < laterSiblingArr.Length(); ++i) { michael@0: Element* element = laterSiblingArr[i]; michael@0: NS_ASSERTION(element->HasFlag(RestyleBit()), "How did that happen?"); michael@0: RestyleData data; michael@0: #ifdef DEBUG michael@0: bool found = michael@0: #endif michael@0: mPendingRestyles.Get(element, &data); michael@0: NS_ASSERTION(found, "Where did our entry go?"); michael@0: data.mRestyleHint = michael@0: nsRestyleHint(data.mRestyleHint & ~eRestyle_LaterSiblings); michael@0: michael@0: mPendingRestyles.Put(element, data); michael@0: } michael@0: michael@0: mHaveLaterSiblingRestyles = false; michael@0: } michael@0: michael@0: uint32_t rootCount; michael@0: while ((rootCount = mRestyleRoots.Length())) { michael@0: // Make sure to pop the element off our restyle root array, so michael@0: // that we can freely append to the array as we process this michael@0: // element. michael@0: nsRefPtr element; michael@0: element.swap(mRestyleRoots[rootCount - 1]); michael@0: mRestyleRoots.RemoveElementAt(rootCount - 1); michael@0: michael@0: // Do the document check before calling GetRestyleData, since we michael@0: // don't want to do the sibling-processing GetRestyleData does if michael@0: // the node is no longer relevant. michael@0: if (element->GetCurrentDoc() != Document()) { michael@0: // Content node has been removed from our document; nothing else michael@0: // to do here michael@0: continue; michael@0: } michael@0: michael@0: RestyleData data; michael@0: if (!GetRestyleData(element, &data)) { michael@0: continue; michael@0: } michael@0: michael@0: ProcessOneRestyle(element, data.mRestyleHint, data.mChangeHint); michael@0: } michael@0: michael@0: if (mHaveLaterSiblingRestyles) { michael@0: // Keep processing restyles for now michael@0: continue; michael@0: } michael@0: michael@0: // Now we only have entries with change hints left. To be safe in michael@0: // case of reentry from the handing of the change hint, use a michael@0: // scratch array instead of calling out to ProcessOneRestyle while michael@0: // enumerating the hashtable. Use the stack if we can, otherwise michael@0: // fall back on heap-allocation. michael@0: nsAutoTArray restyleArr; michael@0: RestyleEnumerateData* restylesToProcess = michael@0: restyleArr.AppendElements(mPendingRestyles.Count()); michael@0: if (restylesToProcess) { michael@0: RestyleEnumerateData* lastRestyle = restylesToProcess; michael@0: RestyleCollector collector = { this, &lastRestyle }; michael@0: mPendingRestyles.Enumerate(CollectRestyles, &collector); michael@0: michael@0: // Clear the hashtable now that we don't need it anymore michael@0: mPendingRestyles.Clear(); michael@0: michael@0: for (RestyleEnumerateData* currentRestyle = restylesToProcess; michael@0: currentRestyle != lastRestyle; michael@0: ++currentRestyle) { michael@0: ProcessOneRestyle(currentRestyle->mElement, michael@0: currentRestyle->mRestyleHint, michael@0: currentRestyle->mChangeHint); michael@0: } michael@0: } michael@0: } michael@0: michael@0: mRestyleManager->EndProcessingRestyles(); michael@0: } michael@0: michael@0: bool michael@0: RestyleTracker::GetRestyleData(Element* aElement, RestyleData* aData) michael@0: { michael@0: NS_PRECONDITION(aElement->GetCurrentDoc() == Document(), michael@0: "Unexpected document; this will lead to incorrect behavior!"); michael@0: michael@0: if (!aElement->HasFlag(RestyleBit())) { michael@0: NS_ASSERTION(!aElement->HasFlag(RootBit()), "Bogus root bit?"); michael@0: return false; michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: bool gotData = michael@0: #endif michael@0: mPendingRestyles.Get(aElement, aData); michael@0: NS_ASSERTION(gotData, "Must have data if restyle bit is set"); michael@0: michael@0: if (aData->mRestyleHint & eRestyle_LaterSiblings) { michael@0: // Someone readded the eRestyle_LaterSiblings hint for this michael@0: // element. Leave it around for now, but remove the other restyle michael@0: // hints and the change hint for it. Also unset its root bit, michael@0: // since it's no longer a root with the new restyle data. michael@0: RestyleData newData; michael@0: newData.mChangeHint = nsChangeHint(0); michael@0: newData.mRestyleHint = eRestyle_LaterSiblings; michael@0: mPendingRestyles.Put(aElement, newData); michael@0: aElement->UnsetFlags(RootBit()); michael@0: aData->mRestyleHint = michael@0: nsRestyleHint(aData->mRestyleHint & ~eRestyle_LaterSiblings); michael@0: } else { michael@0: mPendingRestyles.Remove(aElement); michael@0: aElement->UnsetFlags(mRestyleBits); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: } // namespace mozilla michael@0: