summaryrefslogtreecommitdiffstats
path: root/layout/base/RestyleTracker.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'layout/base/RestyleTracker.cpp')
-rw-r--r--layout/base/RestyleTracker.cpp493
1 files changed, 493 insertions, 0 deletions
diff --git a/layout/base/RestyleTracker.cpp b/layout/base/RestyleTracker.cpp
new file mode 100644
index 000000000..7d68058d1
--- /dev/null
+++ b/layout/base/RestyleTracker.cpp
@@ -0,0 +1,493 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * A class which manages pending restyles. This handles keeping track
+ * of what nodes restyles need to happen on and so forth.
+ */
+
+#include "RestyleTracker.h"
+
+#include "GeckoProfiler.h"
+#include "nsDocShell.h"
+#include "nsFrameManager.h"
+#include "nsIDocument.h"
+#include "nsStyleChangeList.h"
+#include "mozilla/RestyleManager.h"
+#include "RestyleTrackerInlines.h"
+#include "nsTransitionManager.h"
+#include "mozilla/RestyleTimelineMarker.h"
+
+namespace mozilla {
+
+#ifdef RESTYLE_LOGGING
+static nsCString
+GetDocumentURI(nsIDocument* aDocument)
+{
+ nsCString result;
+ nsAutoString url;
+ nsresult rv = aDocument->GetDocumentURI(url);
+ if (NS_SUCCEEDED(rv)) {
+ result.Append(NS_ConvertUTF16toUTF8(url).get());
+ }
+
+ return result;
+}
+
+static nsCString
+FrameTagToString(dom::Element* aElement)
+{
+ nsCString result;
+ nsIFrame* frame = aElement->GetPrimaryFrame();
+ if (frame) {
+ nsFrame::ListTag(result, frame);
+ } else {
+ nsAutoString buf;
+ aElement->NodeInfo()->NameAtom()->ToString(buf);
+ result.AppendPrintf("(%s@%p)", NS_ConvertUTF16toUTF8(buf).get(), aElement);
+ }
+ return result;
+}
+#endif
+
+inline nsIDocument*
+RestyleTracker::Document() const {
+ return mRestyleManager->PresContext()->Document();
+}
+
+#define RESTYLE_ARRAY_STACKSIZE 128
+
+struct RestyleEnumerateData : RestyleTracker::Hints {
+ RefPtr<dom::Element> mElement;
+#if defined(MOZ_ENABLE_PROFILER_SPS)
+ UniquePtr<ProfilerBacktrace> mBacktrace;
+#endif
+};
+
+inline void
+RestyleTracker::ProcessOneRestyle(Element* aElement,
+ nsRestyleHint aRestyleHint,
+ nsChangeHint aChangeHint,
+ const RestyleHintData& aRestyleHintData)
+{
+ NS_PRECONDITION((aRestyleHint & eRestyle_LaterSiblings) == 0,
+ "Someone should have handled this before calling us");
+ NS_PRECONDITION(Document(), "Must have a document");
+ NS_PRECONDITION(aElement->GetComposedDoc() == Document(),
+ "Element has unexpected document");
+
+ LOG_RESTYLE("aRestyleHint = %s, aChangeHint = %s",
+ RestyleManager::RestyleHintToString(aRestyleHint).get(),
+ RestyleManager::ChangeHintToString(aChangeHint).get());
+
+ nsIFrame* primaryFrame = aElement->GetPrimaryFrame();
+
+ if (aRestyleHint & ~eRestyle_LaterSiblings) {
+#ifdef RESTYLE_LOGGING
+ if (ShouldLogRestyle() && primaryFrame &&
+ RestyleManager::StructsToLog() != 0) {
+ LOG_RESTYLE("style context tree before restyle:");
+ LOG_RESTYLE_INDENT();
+ primaryFrame->StyleContext()->LogStyleContextTree(
+ LoggingDepth(), RestyleManager::StructsToLog());
+ }
+#endif
+ mRestyleManager->RestyleElement(aElement, primaryFrame, aChangeHint,
+ *this, aRestyleHint, aRestyleHintData);
+ } else if (aChangeHint &&
+ (primaryFrame ||
+ (aChangeHint & nsChangeHint_ReconstructFrame))) {
+ // Don't need to recompute style; just apply the hint
+ nsStyleChangeList changeList;
+ changeList.AppendChange(primaryFrame, aElement, aChangeHint);
+ mRestyleManager->ProcessRestyledFrames(changeList);
+ }
+}
+
+void
+RestyleTracker::DoProcessRestyles()
+{
+ nsAutoCString docURL("N/A");
+ if (profiler_is_active()) {
+ nsIURI *uri = Document()->GetDocumentURI();
+ if (uri) {
+ docURL = uri->GetSpecOrDefault();
+ }
+ }
+ PROFILER_LABEL_PRINTF("RestyleTracker", "ProcessRestyles",
+ js::ProfileEntry::Category::CSS, "(%s)", docURL.get());
+
+ nsDocShell* docShell = static_cast<nsDocShell*>(mRestyleManager->PresContext()->GetDocShell());
+ RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+ bool isTimelineRecording = timelines && timelines->HasConsumer(docShell);
+
+ // Create a AnimationsWithDestroyedFrame during restyling process to
+ // stop animations and transitions on elements that have no frame at the end
+ // of the restyling process.
+ RestyleManager::AnimationsWithDestroyedFrame
+ animationsWithDestroyedFrame(mRestyleManager);
+
+ // Create a ReframingStyleContexts struct on the stack and put it in our
+ // mReframingStyleContexts for almost all of the remaining scope of
+ // this function.
+ //
+ // It needs to be *in* scope during BeginProcessingRestyles, which
+ // might (if mDoRebuildAllStyleData is true) do substantial amounts of
+ // restyle processing.
+ //
+ // However, it needs to be *out* of scope during
+ // EndProcessingRestyles, since we should release the style contexts
+ // it holds prior to any EndReconstruct call that
+ // EndProcessingRestyles makes. This is because in EndReconstruct we
+ // try to destroy the old rule tree using the GC mechanism, which
+ // means it only gets destroyed if it's unreferenced (and if it's
+ // referenced, we assert). So we want the ReframingStyleContexts
+ // (which holds old style contexts) to be destroyed before the
+ // EndReconstruct so those style contexts go away before
+ // EndReconstruct.
+ {
+ RestyleManager::ReframingStyleContexts
+ reframingStyleContexts(mRestyleManager);
+
+ mRestyleManager->BeginProcessingRestyles(*this);
+
+ LOG_RESTYLE("Processing %d pending %srestyles with %d restyle roots for %s",
+ mPendingRestyles.Count(),
+ mRestyleManager->PresContext()->TransitionManager()->
+ InAnimationOnlyStyleUpdate()
+ ? (const char*) "animation " : (const char*) "",
+ static_cast<int>(mRestyleRoots.Length()),
+ GetDocumentURI(Document()).get());
+ LOG_RESTYLE_INDENT();
+
+ // loop so that we process any restyle events generated by processing
+ while (mPendingRestyles.Count()) {
+ if (mHaveLaterSiblingRestyles) {
+ // Convert them to individual restyles on all the later siblings
+ AutoTArray<RefPtr<Element>, RESTYLE_ARRAY_STACKSIZE> laterSiblingArr;
+ for (auto iter = mPendingRestyles.Iter(); !iter.Done(); iter.Next()) {
+ auto element = static_cast<dom::Element*>(iter.Key());
+ MOZ_ASSERT(!element->IsStyledByServo(),
+ "Should not have Servo-styled elements here");
+ // Only collect the entries that actually need restyling by us (and
+ // haven't, for example, already been restyled).
+ // It's important to not mess with the flags on entries not in our
+ // document.
+ if (element->GetComposedDoc() == Document() &&
+ element->HasFlag(RestyleBit()) &&
+ (iter.Data()->mRestyleHint & eRestyle_LaterSiblings)) {
+ laterSiblingArr.AppendElement(element);
+ }
+ }
+ for (uint32_t i = 0; i < laterSiblingArr.Length(); ++i) {
+ Element* element = laterSiblingArr[i];
+ MOZ_ASSERT(!element->IsStyledByServo());
+ for (nsIContent* sibling = element->GetNextSibling();
+ sibling;
+ sibling = sibling->GetNextSibling()) {
+ if (sibling->IsElement()) {
+ LOG_RESTYLE("adding pending restyle for %s due to "
+ "eRestyle_LaterSiblings hint on %s",
+ FrameTagToString(sibling->AsElement()).get(),
+ FrameTagToString(element->AsElement()).get());
+ if (AddPendingRestyle(sibling->AsElement(), eRestyle_Subtree,
+ nsChangeHint(0))) {
+ // Nothing else to do here; we'll handle the following
+ // siblings when we get to |sibling| in laterSiblingArr.
+ break;
+ }
+ }
+ }
+ }
+
+ // Now remove all those eRestyle_LaterSiblings bits
+ for (uint32_t i = 0; i < laterSiblingArr.Length(); ++i) {
+ Element* element = laterSiblingArr[i];
+ NS_ASSERTION(element->HasFlag(RestyleBit()), "How did that happen?");
+ RestyleData* data;
+#ifdef DEBUG
+ bool found =
+#endif
+ mPendingRestyles.Get(element, &data);
+ NS_ASSERTION(found, "Where did our entry go?");
+ data->mRestyleHint =
+ nsRestyleHint(data->mRestyleHint & ~eRestyle_LaterSiblings);
+ }
+
+ LOG_RESTYLE("%d pending restyles after expanding out "
+ "eRestyle_LaterSiblings", mPendingRestyles.Count());
+
+ mHaveLaterSiblingRestyles = false;
+ }
+
+ uint32_t rootCount;
+ while ((rootCount = mRestyleRoots.Length())) {
+ // Make sure to pop the element off our restyle root array, so
+ // that we can freely append to the array as we process this
+ // element.
+ RefPtr<Element> element;
+ element.swap(mRestyleRoots[rootCount - 1]);
+ mRestyleRoots.RemoveElementAt(rootCount - 1);
+
+ LOG_RESTYLE("processing style root %s at index %d",
+ FrameTagToString(element).get(), rootCount - 1);
+ LOG_RESTYLE_INDENT();
+
+ // Do the document check before calling GetRestyleData, since we
+ // don't want to do the sibling-processing GetRestyleData does if
+ // the node is no longer relevant.
+ if (element->GetComposedDoc() != Document()) {
+ // Content node has been removed from our document; nothing else
+ // to do here
+ LOG_RESTYLE("skipping, no longer in the document");
+ continue;
+ }
+
+ nsAutoPtr<RestyleData> data;
+ if (!GetRestyleData(element, data)) {
+ LOG_RESTYLE("skipping, already restyled");
+ continue;
+ }
+
+ if (isTimelineRecording) {
+ timelines->AddMarkerForDocShell(docShell, Move(
+ MakeUnique<RestyleTimelineMarker>(
+ data->mRestyleHint, MarkerTracingType::START)));
+ }
+
+#if defined(MOZ_ENABLE_PROFILER_SPS)
+ Maybe<GeckoProfilerTracingRAII> profilerRAII;
+ if (profiler_feature_active("restyle")) {
+ profilerRAII.emplace("Paint", "Styles", Move(data->mBacktrace));
+ }
+#endif
+ ProcessOneRestyle(element, data->mRestyleHint, data->mChangeHint,
+ data->mRestyleHintData);
+ AddRestyleRootsIfAwaitingRestyle(data->mDescendants);
+
+ if (isTimelineRecording) {
+ timelines->AddMarkerForDocShell(docShell, Move(
+ MakeUnique<RestyleTimelineMarker>(
+ data->mRestyleHint, MarkerTracingType::END)));
+ }
+ }
+
+ if (mHaveLaterSiblingRestyles) {
+ // Keep processing restyles for now
+ continue;
+ }
+
+ // Now we only have entries with change hints left. To be safe in
+ // case of reentry from the handing of the change hint, use a
+ // scratch array instead of calling out to ProcessOneRestyle while
+ // enumerating the hashtable. Use the stack if we can, otherwise
+ // fall back on heap-allocation.
+ AutoTArray<RestyleEnumerateData, RESTYLE_ARRAY_STACKSIZE> restyleArr;
+ RestyleEnumerateData* restylesToProcess =
+ restyleArr.AppendElements(mPendingRestyles.Count());
+ if (restylesToProcess) {
+ RestyleEnumerateData* restyle = restylesToProcess;
+#ifdef RESTYLE_LOGGING
+ uint32_t count = 0;
+#endif
+ for (auto iter = mPendingRestyles.Iter(); !iter.Done(); iter.Next()) {
+ auto element = static_cast<dom::Element*>(iter.Key());
+ RestyleTracker::RestyleData* data = iter.Data();
+
+ // Only collect the entries that actually need restyling by us (and
+ // haven't, for example, already been restyled).
+ // It's important to not mess with the flags on entries not in our
+ // document.
+ if (element->GetComposedDoc() != Document() ||
+ !element->HasFlag(RestyleBit())) {
+ LOG_RESTYLE("skipping pending restyle %s, already restyled or no "
+ "longer in the document",
+ FrameTagToString(element).get());
+ continue;
+ }
+
+ NS_ASSERTION(
+ !element->HasFlag(RootBit()) ||
+ // Maybe we're just not reachable via the frame tree?
+ (element->GetFlattenedTreeParent() &&
+ (!element->GetFlattenedTreeParent()->GetPrimaryFrame() ||
+ element->GetFlattenedTreeParent()->GetPrimaryFrame()->IsLeaf() ||
+ element->GetComposedDoc()->GetShell()->FrameManager()
+ ->GetDisplayContentsStyleFor(element))) ||
+ // Or not reachable due to an async reinsert we have
+ // pending? If so, we'll have a reframe hint around.
+ // That incidentally makes it safe that we still have
+ // the bit, since any descendants that didn't get added
+ // to the roots list because we had the bits will be
+ // completely restyled in a moment.
+ (data->mChangeHint & nsChangeHint_ReconstructFrame),
+ "Why did this not get handled while processing mRestyleRoots?");
+
+ // Unset the restyle bits now, so if they get readded later as we
+ // process we won't clobber that adding of the bit.
+ element->UnsetFlags(RestyleBit() |
+ RootBit() |
+ ConditionalDescendantsBit());
+
+ restyle->mElement = element;
+ restyle->mRestyleHint = data->mRestyleHint;
+ restyle->mChangeHint = data->mChangeHint;
+ // We can move data since we'll be clearing mPendingRestyles after
+ // we finish enumerating it.
+ restyle->mRestyleHintData = Move(data->mRestyleHintData);
+#if defined(MOZ_ENABLE_PROFILER_SPS)
+ restyle->mBacktrace = Move(data->mBacktrace);
+#endif
+
+#ifdef RESTYLE_LOGGING
+ count++;
+#endif
+
+ // Increment to the next slot in the array
+ restyle++;
+ }
+
+ RestyleEnumerateData* lastRestyle = restyle;
+
+ // Clear the hashtable now that we don't need it anymore
+ mPendingRestyles.Clear();
+
+#ifdef RESTYLE_LOGGING
+ uint32_t index = 0;
+#endif
+ for (RestyleEnumerateData* currentRestyle = restylesToProcess;
+ currentRestyle != lastRestyle;
+ ++currentRestyle) {
+ LOG_RESTYLE("processing pending restyle %s at index %d/%d",
+ FrameTagToString(currentRestyle->mElement).get(),
+ index++, count);
+ LOG_RESTYLE_INDENT();
+
+#if defined(MOZ_ENABLE_PROFILER_SPS)
+ Maybe<GeckoProfilerTracingRAII> profilerRAII;
+ if (profiler_feature_active("restyle")) {
+ profilerRAII.emplace("Paint", "Styles", Move(currentRestyle->mBacktrace));
+ }
+#endif
+ if (isTimelineRecording) {
+ timelines->AddMarkerForDocShell(docShell, Move(
+ MakeUnique<RestyleTimelineMarker>(
+ currentRestyle->mRestyleHint, MarkerTracingType::START)));
+ }
+
+ ProcessOneRestyle(currentRestyle->mElement,
+ currentRestyle->mRestyleHint,
+ currentRestyle->mChangeHint,
+ currentRestyle->mRestyleHintData);
+
+ if (isTimelineRecording) {
+ timelines->AddMarkerForDocShell(docShell, Move(
+ MakeUnique<RestyleTimelineMarker>(
+ currentRestyle->mRestyleHint, MarkerTracingType::END)));
+ }
+ }
+ }
+ }
+ }
+
+ // mPendingRestyles is now empty.
+ mHaveSelectors = false;
+
+ mRestyleManager->EndProcessingRestyles();
+}
+
+bool
+RestyleTracker::GetRestyleData(Element* aElement, nsAutoPtr<RestyleData>& aData)
+{
+ NS_PRECONDITION(aElement->GetComposedDoc() == Document(),
+ "Unexpected document; this will lead to incorrect behavior!");
+
+ if (!aElement->HasFlag(RestyleBit())) {
+ NS_ASSERTION(!aElement->HasFlag(RootBit()), "Bogus root bit?");
+ return false;
+ }
+
+ mPendingRestyles.RemoveAndForget(aElement, aData);
+ NS_ASSERTION(aData.get(), "Must have data if restyle bit is set");
+
+ if (aData->mRestyleHint & eRestyle_LaterSiblings) {
+ // Someone readded the eRestyle_LaterSiblings hint for this
+ // element. Leave it around for now, but remove the other restyle
+ // hints and the change hint for it. Also unset its root bit,
+ // since it's no longer a root with the new restyle data.
+
+ // During a normal restyle, we should have already processed the
+ // mDescendants array the last time we processed the restyle
+ // for this element. But in RebuildAllStyleData, we don't initially
+ // expand out eRestyle_LaterSiblings, so we can get in here the
+ // first time we need to process a restyle for this element. In that
+ // case, it's fine for us to have a non-empty mDescendants, since
+ // we know that RebuildAllStyleData adds eRestyle_ForceDescendants
+ // and we're guaranteed we'll restyle the entire tree.
+ NS_ASSERTION(mRestyleManager->InRebuildAllStyleData() ||
+ aData->mDescendants.IsEmpty(),
+ "expected descendants to be handled by now");
+
+ RestyleData* newData = new RestyleData;
+ newData->mChangeHint = nsChangeHint(0);
+ newData->mRestyleHint = eRestyle_LaterSiblings;
+ mPendingRestyles.Put(aElement, newData);
+ aElement->UnsetFlags(RootBit());
+ aData->mRestyleHint =
+ nsRestyleHint(aData->mRestyleHint & ~eRestyle_LaterSiblings);
+ } else {
+ aElement->UnsetFlags(mRestyleBits);
+ }
+
+ return true;
+}
+
+void
+RestyleTracker::AddRestyleRootsIfAwaitingRestyle(
+ const nsTArray<RefPtr<Element>>& aElements)
+{
+ // The RestyleData for a given element has stored in mDescendants
+ // the list of descendants we need to end up restyling. Since we
+ // won't necessarily end up restyling them, due to the restyle
+ // process finishing early (see how RestyleResult::eStop is handled
+ // in ElementRestyler::Restyle), we add them to the list of restyle
+ // roots to handle the next time around the
+ // RestyleTracker::DoProcessRestyles loop.
+ //
+ // Note that aElements must maintain the same invariant
+ // that mRestyleRoots does, i.e. that ancestors appear after descendants.
+ // Since we call AddRestyleRootsIfAwaitingRestyle only after we have
+ // removed the restyle root we are currently processing from the end of
+ // mRestyleRoots, and the only elements we get here in aElements are
+ // descendants of that restyle root, we are safe to simply append to the
+ // end of mRestyleRoots to maintain its invariant.
+ for (size_t i = 0; i < aElements.Length(); i++) {
+ Element* element = aElements[i];
+ if (element->HasFlag(RestyleBit())) {
+ mRestyleRoots.AppendElement(element);
+ }
+ }
+}
+
+void
+RestyleTracker::ClearSelectors()
+{
+ if (!mHaveSelectors) {
+ return;
+ }
+ for (auto it = mPendingRestyles.Iter(); !it.Done(); it.Next()) {
+ RestyleData* data = it.Data();
+ if (data->mRestyleHint & eRestyle_SomeDescendants) {
+ data->mRestyleHint =
+ (data->mRestyleHint & ~eRestyle_SomeDescendants) | eRestyle_Subtree;
+ data->mRestyleHintData.mSelectorsForDescendants.Clear();
+ } else {
+ MOZ_ASSERT(data->mRestyleHintData.mSelectorsForDescendants.IsEmpty());
+ }
+ }
+ mHaveSelectors = false;
+}
+
+} // namespace mozilla