summaryrefslogtreecommitdiffstats
path: root/layout/base/RestyleManager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'layout/base/RestyleManager.cpp')
-rw-r--r--layout/base/RestyleManager.cpp3974
1 files changed, 3974 insertions, 0 deletions
diff --git a/layout/base/RestyleManager.cpp b/layout/base/RestyleManager.cpp
new file mode 100644
index 000000000..de8f10224
--- /dev/null
+++ b/layout/base/RestyleManager.cpp
@@ -0,0 +1,3974 @@
+/* -*- 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/. */
+
+/**
+ * Code responsible for managing style changes: tracking what style
+ * changes need to happen, scheduling them, and doing them.
+ */
+
+#include "mozilla/RestyleManager.h"
+
+#include <algorithm> // For std::max
+#include "mozilla/EffectSet.h"
+#include "mozilla/EventStates.h"
+#include "nsLayoutUtils.h"
+#include "AnimationCommon.h" // For GetLayerAnimationInfo
+#include "FrameLayerBuilder.h"
+#include "GeckoProfiler.h"
+#include "LayerAnimationInfo.h" // For LayerAnimationInfo::sRecords
+#include "nsAutoPtr.h"
+#include "nsStyleChangeList.h"
+#include "nsRuleProcessorData.h"
+#include "nsStyleSet.h"
+#include "nsStyleUtil.h"
+#include "nsCSSFrameConstructor.h"
+#include "nsSVGEffects.h"
+#include "nsCSSPseudoElements.h"
+#include "nsCSSRendering.h"
+#include "nsAnimationManager.h"
+#include "nsTransitionManager.h"
+#include "nsViewManager.h"
+#include "nsRenderingContext.h"
+#include "nsSVGIntegrationUtils.h"
+#include "nsCSSAnonBoxes.h"
+#include "nsContainerFrame.h"
+#include "nsPlaceholderFrame.h"
+#include "nsBlockFrame.h"
+#include "nsViewportFrame.h"
+#include "SVGTextFrame.h"
+#include "StickyScrollContainer.h"
+#include "nsIRootBox.h"
+#include "nsIDOMMutationEvent.h"
+#include "nsContentUtils.h"
+#include "nsIFrameInlines.h"
+#include "ActiveLayerTracker.h"
+#include "nsDisplayList.h"
+#include "RestyleTrackerInlines.h"
+#include "nsSMILAnimationController.h"
+#include "nsCSSRuleProcessor.h"
+#include "ChildIterator.h"
+#include "Layers.h"
+
+#ifdef ACCESSIBILITY
+#include "nsAccessibilityService.h"
+#endif
+
+namespace mozilla {
+
+using namespace layers;
+using namespace dom;
+
+#define LOG_RESTYLE_CONTINUE(reason_, ...) \
+ LOG_RESTYLE("continuing restyle since " reason_, ##__VA_ARGS__)
+
+#ifdef RESTYLE_LOGGING
+static nsCString
+FrameTagToString(const nsIFrame* aFrame)
+{
+ nsCString result;
+ aFrame->ListTag(result);
+ return result;
+}
+
+static nsCString
+ElementTagToString(dom::Element* aElement)
+{
+ nsCString result;
+ nsDependentAtomString buf(aElement->NodeInfo()->NameAtom());
+ result.AppendPrintf("(%s@%p)", NS_ConvertUTF16toUTF8(buf).get(), aElement);
+ return result;
+}
+#endif
+
+RestyleManager::RestyleManager(nsPresContext* aPresContext)
+ : RestyleManagerBase(aPresContext)
+ , mDoRebuildAllStyleData(false)
+ , mInRebuildAllStyleData(false)
+ , mSkipAnimationRules(false)
+ , mHavePendingNonAnimationRestyles(false)
+ , mRebuildAllExtraHint(nsChangeHint(0))
+ , mRebuildAllRestyleHint(nsRestyleHint(0))
+ , mAnimationGeneration(0)
+ , mReframingStyleContexts(nullptr)
+ , mAnimationsWithDestroyedFrame(nullptr)
+ , mPendingRestyles(ELEMENT_HAS_PENDING_RESTYLE |
+ ELEMENT_IS_POTENTIAL_RESTYLE_ROOT |
+ ELEMENT_IS_CONDITIONAL_RESTYLE_ANCESTOR)
+ , mIsProcessingRestyles(false)
+#ifdef RESTYLE_LOGGING
+ , mLoggingDepth(0)
+#endif
+{
+ mPendingRestyles.Init(this);
+}
+
+void
+RestyleManager::RestyleElement(Element* aElement,
+ nsIFrame* aPrimaryFrame,
+ nsChangeHint aMinHint,
+ RestyleTracker& aRestyleTracker,
+ nsRestyleHint aRestyleHint,
+ const RestyleHintData& aRestyleHintData)
+{
+ MOZ_ASSERT(mReframingStyleContexts, "should have rsc");
+ NS_ASSERTION(aPrimaryFrame == aElement->GetPrimaryFrame(),
+ "frame/content mismatch");
+ if (aPrimaryFrame && aPrimaryFrame->GetContent() != aElement) {
+ // XXXbz this is due to image maps messing with the primary frame pointer
+ // of <area>s. See bug 135040. We can remove this block once that's fixed.
+ aPrimaryFrame = nullptr;
+ }
+ NS_ASSERTION(!aPrimaryFrame || aPrimaryFrame->GetContent() == aElement,
+ "frame/content mismatch");
+
+ // If we're restyling the root element and there are 'rem' units in
+ // use, handle dynamic changes to the definition of a 'rem' here.
+ if (PresContext()->UsesRootEMUnits() && aPrimaryFrame &&
+ !mInRebuildAllStyleData) {
+ nsStyleContext* oldContext = aPrimaryFrame->StyleContext();
+ if (!oldContext->GetParent()) { // check that we're the root element
+ RefPtr<nsStyleContext> newContext = StyleSet()->
+ ResolveStyleFor(aElement, nullptr /* == oldContext->GetParent() */);
+ if (oldContext->StyleFont()->mFont.size !=
+ newContext->StyleFont()->mFont.size) {
+ // The basis for 'rem' units has changed.
+ mRebuildAllRestyleHint |= aRestyleHint;
+ if (aRestyleHint & eRestyle_SomeDescendants) {
+ mRebuildAllRestyleHint |= eRestyle_Subtree;
+ }
+ mRebuildAllExtraHint |= aMinHint;
+ StartRebuildAllStyleData(aRestyleTracker);
+ return;
+ }
+ }
+ }
+
+ if (aMinHint & nsChangeHint_ReconstructFrame) {
+ FrameConstructor()->RecreateFramesForContent(aElement, false,
+ nsCSSFrameConstructor::REMOVE_FOR_RECONSTRUCTION, nullptr);
+ } else if (aPrimaryFrame) {
+ ComputeAndProcessStyleChange(aPrimaryFrame, aMinHint, aRestyleTracker,
+ aRestyleHint, aRestyleHintData);
+ } else if (aRestyleHint & ~eRestyle_LaterSiblings) {
+ // We're restyling an element with no frame, so we should try to
+ // make one if its new style says it should have one. But in order
+ // to try to honor the restyle hint (which we'd like to do so that,
+ // for example, an animation-only style flush doesn't flush other
+ // buffered style changes), we only do this if the restyle hint says
+ // we have *some* restyling for this frame. This means we'll
+ // potentially get ahead of ourselves in that case, but not as much
+ // as we would if we didn't check the restyle hint.
+ nsStyleContext* newContext =
+ FrameConstructor()->MaybeRecreateFramesForElement(aElement);
+ if (newContext &&
+ newContext->StyleDisplay()->mDisplay == StyleDisplay::Contents) {
+ // Style change for a display:contents node that did not recreate frames.
+ ComputeAndProcessStyleChange(newContext, aElement, aMinHint,
+ aRestyleTracker, aRestyleHint,
+ aRestyleHintData);
+ }
+ }
+}
+
+RestyleManager::ReframingStyleContexts::ReframingStyleContexts(
+ RestyleManager* aRestyleManager)
+ : mRestyleManager(aRestyleManager)
+ , mRestorePointer(mRestyleManager->mReframingStyleContexts)
+{
+ MOZ_ASSERT(!mRestyleManager->mReframingStyleContexts,
+ "shouldn't construct recursively");
+ mRestyleManager->mReframingStyleContexts = this;
+}
+
+RestyleManager::ReframingStyleContexts::~ReframingStyleContexts()
+{
+ // Before we go away, we need to flush out any frame construction that
+ // was enqueued, so that we initiate transitions.
+ // Note that this is a little bit evil in that we're calling into code
+ // that calls our member functions from our destructor, but it's at
+ // the beginning of our destructor, so it shouldn't be too bad.
+ mRestyleManager->PresContext()->FrameConstructor()->CreateNeededFrames();
+}
+
+RestyleManager::AnimationsWithDestroyedFrame::AnimationsWithDestroyedFrame(
+ RestyleManager* aRestyleManager)
+ : mRestyleManager(aRestyleManager)
+ , mRestorePointer(mRestyleManager->mAnimationsWithDestroyedFrame)
+{
+ MOZ_ASSERT(!mRestyleManager->mAnimationsWithDestroyedFrame,
+ "shouldn't construct recursively");
+ mRestyleManager->mAnimationsWithDestroyedFrame = this;
+}
+
+void
+RestyleManager::AnimationsWithDestroyedFrame::StopAnimationsForElementsWithoutFrames()
+{
+ StopAnimationsWithoutFrame(mContents, CSSPseudoElementType::NotPseudo);
+ StopAnimationsWithoutFrame(mBeforeContents, CSSPseudoElementType::before);
+ StopAnimationsWithoutFrame(mAfterContents, CSSPseudoElementType::after);
+}
+
+void
+RestyleManager::AnimationsWithDestroyedFrame::StopAnimationsWithoutFrame(
+ nsTArray<RefPtr<nsIContent>>& aArray,
+ CSSPseudoElementType aPseudoType)
+{
+ nsAnimationManager* animationManager =
+ mRestyleManager->PresContext()->AnimationManager();
+ nsTransitionManager* transitionManager =
+ mRestyleManager->PresContext()->TransitionManager();
+ for (nsIContent* content : aArray) {
+ if (content->GetPrimaryFrame()) {
+ continue;
+ }
+ dom::Element* element = content->AsElement();
+
+ animationManager->StopAnimationsForElement(element, aPseudoType);
+ transitionManager->StopTransitionsForElement(element, aPseudoType);
+
+ // All other animations should keep running but not running on the
+ // *compositor* at this point.
+ EffectSet* effectSet = EffectSet::GetEffectSet(element, aPseudoType);
+ if (effectSet) {
+ for (KeyframeEffectReadOnly* effect : *effectSet) {
+ effect->ResetIsRunningOnCompositor();
+ }
+ }
+ }
+}
+
+static inline dom::Element*
+ElementForStyleContext(nsIContent* aParentContent,
+ nsIFrame* aFrame,
+ CSSPseudoElementType aPseudoType);
+
+// Forwarded nsIDocumentObserver method, to handle restyling (and
+// passing the notification to the frame).
+nsresult
+RestyleManager::ContentStateChanged(nsIContent* aContent,
+ EventStates aStateMask)
+{
+ // XXXbz it would be good if this function only took Elements, but
+ // we'd have to make ESM guarantee that usefully.
+ if (!aContent->IsElement()) {
+ return NS_OK;
+ }
+
+ Element* aElement = aContent->AsElement();
+
+ nsChangeHint changeHint;
+ nsRestyleHint restyleHint;
+ ContentStateChangedInternal(aElement, aStateMask, &changeHint, &restyleHint);
+
+ PostRestyleEvent(aElement, restyleHint, changeHint);
+ return NS_OK;
+}
+
+// Forwarded nsIMutationObserver method, to handle restyling.
+void
+RestyleManager::AttributeWillChange(Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aNewValue)
+{
+ RestyleHintData rsdata;
+ nsRestyleHint rshint =
+ StyleSet()->HasAttributeDependentStyle(aElement,
+ aNameSpaceID,
+ aAttribute,
+ aModType,
+ false,
+ aNewValue,
+ rsdata);
+ PostRestyleEvent(aElement, rshint, nsChangeHint(0), &rsdata);
+}
+
+// Forwarded nsIMutationObserver method, to handle restyling (and
+// passing the notification to the frame).
+void
+RestyleManager::AttributeChanged(Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aOldValue)
+{
+ // Hold onto the PresShell to prevent ourselves from being destroyed.
+ // XXXbz how, exactly, would this attribute change cause us to be
+ // destroyed from inside this function?
+ nsCOMPtr<nsIPresShell> shell = PresContext()->GetPresShell();
+ mozilla::Unused << shell; // Unused within this function
+
+ // Get the frame associated with the content which is the highest in the frame tree
+ nsIFrame* primaryFrame = aElement->GetPrimaryFrame();
+
+#if 0
+ NS_FRAME_LOG(NS_FRAME_TRACE_CALLS,
+ ("RestyleManager::AttributeChanged: content=%p[%s] frame=%p",
+ aContent, ContentTag(aElement, 0), frame));
+#endif
+
+ // the style tag has its own interpretation based on aHint
+ nsChangeHint hint = aElement->GetAttributeChangeHint(aAttribute, aModType);
+
+ bool reframe = (hint & nsChangeHint_ReconstructFrame) != 0;
+
+#ifdef MOZ_XUL
+ // The following listbox widget trap prevents offscreen listbox widget
+ // content from being removed and re-inserted (which is what would
+ // happen otherwise).
+ if (!primaryFrame && !reframe) {
+ int32_t namespaceID;
+ nsIAtom* tag = PresContext()->Document()->BindingManager()->
+ ResolveTag(aElement, &namespaceID);
+
+ if (namespaceID == kNameSpaceID_XUL &&
+ (tag == nsGkAtoms::listitem ||
+ tag == nsGkAtoms::listcell))
+ return;
+ }
+
+ if (aAttribute == nsGkAtoms::tooltiptext ||
+ aAttribute == nsGkAtoms::tooltip)
+ {
+ nsIRootBox* rootBox = nsIRootBox::GetRootBox(PresContext()->GetPresShell());
+ if (rootBox) {
+ if (aModType == nsIDOMMutationEvent::REMOVAL)
+ rootBox->RemoveTooltipSupport(aElement);
+ if (aModType == nsIDOMMutationEvent::ADDITION)
+ rootBox->AddTooltipSupport(aElement);
+ }
+ }
+
+#endif // MOZ_XUL
+
+ if (primaryFrame) {
+ // See if we have appearance information for a theme.
+ const nsStyleDisplay* disp = primaryFrame->StyleDisplay();
+ if (disp->mAppearance) {
+ nsITheme* theme = PresContext()->GetTheme();
+ if (theme && theme->ThemeSupportsWidget(PresContext(), primaryFrame, disp->mAppearance)) {
+ bool repaint = false;
+ theme->WidgetStateChanged(primaryFrame, disp->mAppearance, aAttribute,
+ &repaint, aOldValue);
+ if (repaint)
+ hint |= nsChangeHint_RepaintFrame;
+ }
+ }
+
+ // let the frame deal with it now, so we don't have to deal later
+ primaryFrame->AttributeChanged(aNameSpaceID, aAttribute, aModType);
+ // XXXwaterson should probably check for IB split siblings
+ // here, and propagate the AttributeChanged notification to
+ // them, as well. Currently, inline frames don't do anything on
+ // this notification, so it's not that big a deal.
+ }
+
+ // See if we can optimize away the style re-resolution -- must be called after
+ // the frame's AttributeChanged() in case it does something that affects the style
+ RestyleHintData rsdata;
+ nsRestyleHint rshint =
+ StyleSet()->HasAttributeDependentStyle(aElement,
+ aNameSpaceID,
+ aAttribute,
+ aModType,
+ true,
+ aOldValue,
+ rsdata);
+ PostRestyleEvent(aElement, rshint, hint, &rsdata);
+}
+
+/* static */ uint64_t
+RestyleManager::GetAnimationGenerationForFrame(nsIFrame* aFrame)
+{
+ EffectSet* effectSet = EffectSet::GetEffectSet(aFrame);
+ return effectSet ? effectSet->GetAnimationGeneration() : 0;
+}
+
+void
+RestyleManager::RestyleForEmptyChange(Element* aContainer)
+{
+ // In some cases (:empty + E, :empty ~ E), a change in the content of
+ // an element requires restyling its parent's siblings.
+ nsRestyleHint hint = eRestyle_Subtree;
+ nsIContent* grandparent = aContainer->GetParent();
+ if (grandparent &&
+ (grandparent->GetFlags() & NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS)) {
+ hint = nsRestyleHint(hint | eRestyle_LaterSiblings);
+ }
+ PostRestyleEvent(aContainer, hint, nsChangeHint(0));
+}
+
+void
+RestyleManager::RestyleForAppend(nsIContent* aContainer,
+ nsIContent* aFirstNewContent)
+{
+ // The container cannot be a document, but might be a ShadowRoot.
+ if (!aContainer->IsElement()) {
+ return;
+ }
+ Element* container = aContainer->AsElement();
+
+#ifdef DEBUG
+ {
+ for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) {
+ NS_ASSERTION(!cur->IsRootOfAnonymousSubtree(),
+ "anonymous nodes should not be in child lists");
+ }
+ }
+#endif
+ uint32_t selectorFlags =
+ container->GetFlags() & (NODE_ALL_SELECTOR_FLAGS &
+ ~NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS);
+ if (selectorFlags == 0)
+ return;
+
+ if (selectorFlags & NODE_HAS_EMPTY_SELECTOR) {
+ // see whether we need to restyle the container
+ bool wasEmpty = true; // :empty or :-moz-only-whitespace
+ for (nsIContent* cur = container->GetFirstChild();
+ cur != aFirstNewContent;
+ cur = cur->GetNextSibling()) {
+ // We don't know whether we're testing :empty or :-moz-only-whitespace,
+ // so be conservative and assume :-moz-only-whitespace (i.e., make
+ // IsSignificantChild less likely to be true, and thus make us more
+ // likely to restyle).
+ if (nsStyleUtil::IsSignificantChild(cur, true, false)) {
+ wasEmpty = false;
+ break;
+ }
+ }
+ if (wasEmpty) {
+ RestyleForEmptyChange(container);
+ return;
+ }
+ }
+
+ if (selectorFlags & NODE_HAS_SLOW_SELECTOR) {
+ PostRestyleEvent(container, eRestyle_Subtree, nsChangeHint(0));
+ // Restyling the container is the most we can do here, so we're done.
+ return;
+ }
+
+ if (selectorFlags & NODE_HAS_EDGE_CHILD_SELECTOR) {
+ // restyle the last element child before this node
+ for (nsIContent* cur = aFirstNewContent->GetPreviousSibling();
+ cur;
+ cur = cur->GetPreviousSibling()) {
+ if (cur->IsElement()) {
+ PostRestyleEvent(cur->AsElement(), eRestyle_Subtree, nsChangeHint(0));
+ break;
+ }
+ }
+ }
+}
+
+// Needed since we can't use PostRestyleEvent on non-elements (with
+// eRestyle_LaterSiblings or nsRestyleHint(eRestyle_Subtree |
+// eRestyle_LaterSiblings) as appropriate).
+static void
+RestyleSiblingsStartingWith(RestyleManager* aRestyleManager,
+ nsIContent* aStartingSibling /* may be null */)
+{
+ for (nsIContent* sibling = aStartingSibling; sibling;
+ sibling = sibling->GetNextSibling()) {
+ if (sibling->IsElement()) {
+ aRestyleManager->
+ PostRestyleEvent(sibling->AsElement(),
+ nsRestyleHint(eRestyle_Subtree | eRestyle_LaterSiblings),
+ nsChangeHint(0));
+ break;
+ }
+ }
+}
+
+// Restyling for a ContentInserted or CharacterDataChanged notification.
+// This could be used for ContentRemoved as well if we got the
+// notification before the removal happened (and sometimes
+// CharacterDataChanged is more like a removal than an addition).
+// The comments are written and variables are named in terms of it being
+// a ContentInserted notification.
+void
+RestyleManager::RestyleForInsertOrChange(nsINode* aContainer,
+ nsIContent* aChild)
+{
+ // The container might be a document or a ShadowRoot.
+ if (!aContainer->IsElement()) {
+ return;
+ }
+ Element* container = aContainer->AsElement();
+
+ NS_ASSERTION(!aChild->IsRootOfAnonymousSubtree(),
+ "anonymous nodes should not be in child lists");
+ uint32_t selectorFlags =
+ container ? (container->GetFlags() & NODE_ALL_SELECTOR_FLAGS) : 0;
+ if (selectorFlags == 0)
+ return;
+
+ if (selectorFlags & NODE_HAS_EMPTY_SELECTOR) {
+ // see whether we need to restyle the container
+ bool wasEmpty = true; // :empty or :-moz-only-whitespace
+ for (nsIContent* child = container->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ if (child == aChild)
+ continue;
+ // We don't know whether we're testing :empty or :-moz-only-whitespace,
+ // so be conservative and assume :-moz-only-whitespace (i.e., make
+ // IsSignificantChild less likely to be true, and thus make us more
+ // likely to restyle).
+ if (nsStyleUtil::IsSignificantChild(child, true, false)) {
+ wasEmpty = false;
+ break;
+ }
+ }
+ if (wasEmpty) {
+ RestyleForEmptyChange(container);
+ return;
+ }
+ }
+
+ if (selectorFlags & NODE_HAS_SLOW_SELECTOR) {
+ PostRestyleEvent(container, eRestyle_Subtree, nsChangeHint(0));
+ // Restyling the container is the most we can do here, so we're done.
+ return;
+ }
+
+ if (selectorFlags & NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS) {
+ // Restyle all later siblings.
+ RestyleSiblingsStartingWith(this, aChild->GetNextSibling());
+ }
+
+ if (selectorFlags & NODE_HAS_EDGE_CHILD_SELECTOR) {
+ // restyle the previously-first element child if it is after this node
+ bool passedChild = false;
+ for (nsIContent* content = container->GetFirstChild();
+ content;
+ content = content->GetNextSibling()) {
+ if (content == aChild) {
+ passedChild = true;
+ continue;
+ }
+ if (content->IsElement()) {
+ if (passedChild) {
+ PostRestyleEvent(content->AsElement(), eRestyle_Subtree,
+ nsChangeHint(0));
+ }
+ break;
+ }
+ }
+ // restyle the previously-last element child if it is before this node
+ passedChild = false;
+ for (nsIContent* content = container->GetLastChild();
+ content;
+ content = content->GetPreviousSibling()) {
+ if (content == aChild) {
+ passedChild = true;
+ continue;
+ }
+ if (content->IsElement()) {
+ if (passedChild) {
+ PostRestyleEvent(content->AsElement(), eRestyle_Subtree,
+ nsChangeHint(0));
+ }
+ break;
+ }
+ }
+ }
+}
+
+void
+RestyleManager::ContentRemoved(nsINode* aContainer,
+ nsIContent* aOldChild,
+ nsIContent* aFollowingSibling)
+{
+ // The container might be a document or a ShadowRoot.
+ if (!aContainer->IsElement()) {
+ return;
+ }
+ Element* container = aContainer->AsElement();
+
+ if (aOldChild->IsRootOfAnonymousSubtree()) {
+ // This should be an assert, but this is called incorrectly in
+ // HTMLEditor::DeleteRefToAnonymousNode and the assertions were clogging
+ // up the logs. Make it an assert again when that's fixed.
+ MOZ_ASSERT(aOldChild->GetProperty(nsGkAtoms::restylableAnonymousNode),
+ "anonymous nodes should not be in child lists (bug 439258)");
+ }
+ uint32_t selectorFlags =
+ container ? (container->GetFlags() & NODE_ALL_SELECTOR_FLAGS) : 0;
+ if (selectorFlags == 0)
+ return;
+
+ if (selectorFlags & NODE_HAS_EMPTY_SELECTOR) {
+ // see whether we need to restyle the container
+ bool isEmpty = true; // :empty or :-moz-only-whitespace
+ for (nsIContent* child = container->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ // We don't know whether we're testing :empty or :-moz-only-whitespace,
+ // so be conservative and assume :-moz-only-whitespace (i.e., make
+ // IsSignificantChild less likely to be true, and thus make us more
+ // likely to restyle).
+ if (nsStyleUtil::IsSignificantChild(child, true, false)) {
+ isEmpty = false;
+ break;
+ }
+ }
+ if (isEmpty) {
+ RestyleForEmptyChange(container);
+ return;
+ }
+ }
+
+ if (selectorFlags & NODE_HAS_SLOW_SELECTOR) {
+ PostRestyleEvent(container, eRestyle_Subtree, nsChangeHint(0));
+ // Restyling the container is the most we can do here, so we're done.
+ return;
+ }
+
+ if (selectorFlags & NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS) {
+ // Restyle all later siblings.
+ RestyleSiblingsStartingWith(this, aFollowingSibling);
+ }
+
+ if (selectorFlags & NODE_HAS_EDGE_CHILD_SELECTOR) {
+ // restyle the now-first element child if it was after aOldChild
+ bool reachedFollowingSibling = false;
+ for (nsIContent* content = container->GetFirstChild();
+ content;
+ content = content->GetNextSibling()) {
+ if (content == aFollowingSibling) {
+ reachedFollowingSibling = true;
+ // do NOT continue here; we might want to restyle this node
+ }
+ if (content->IsElement()) {
+ if (reachedFollowingSibling) {
+ PostRestyleEvent(content->AsElement(), eRestyle_Subtree,
+ nsChangeHint(0));
+ }
+ break;
+ }
+ }
+ // restyle the now-last element child if it was before aOldChild
+ reachedFollowingSibling = (aFollowingSibling == nullptr);
+ for (nsIContent* content = container->GetLastChild();
+ content;
+ content = content->GetPreviousSibling()) {
+ if (content->IsElement()) {
+ if (reachedFollowingSibling) {
+ PostRestyleEvent(content->AsElement(), eRestyle_Subtree, nsChangeHint(0));
+ }
+ break;
+ }
+ if (content == aFollowingSibling) {
+ reachedFollowingSibling = true;
+ }
+ }
+ }
+}
+
+void
+RestyleManager::RebuildAllStyleData(nsChangeHint aExtraHint,
+ nsRestyleHint aRestyleHint)
+{
+ NS_ASSERTION(!(aExtraHint & nsChangeHint_ReconstructFrame),
+ "Should not reconstruct the root of the frame tree. "
+ "Use ReconstructDocElementHierarchy instead.");
+ MOZ_ASSERT(!(aRestyleHint & ~(eRestyle_Subtree | eRestyle_ForceDescendants)),
+ "the only bits allowed in aRestyleHint are eRestyle_Subtree and "
+ "eRestyle_ForceDescendants");
+
+ mRebuildAllExtraHint |= aExtraHint;
+ mRebuildAllRestyleHint |= aRestyleHint;
+
+ // Processing the style changes could cause a flush that propagates to
+ // the parent frame and thus destroys the pres shell, so we must hold
+ // a reference.
+ nsCOMPtr<nsIPresShell> presShell = PresContext()->GetPresShell();
+ if (!presShell || !presShell->GetRootFrame()) {
+ mDoRebuildAllStyleData = false;
+ return;
+ }
+
+ // Make sure that the viewmanager will outlive the presshell
+ RefPtr<nsViewManager> vm = presShell->GetViewManager();
+ mozilla::Unused << vm; // Not used within this function
+
+ // We may reconstruct frames below and hence process anything that is in the
+ // tree. We don't want to get notified to process those items again after.
+ presShell->GetDocument()->FlushPendingNotifications(Flush_ContentAndNotify);
+
+ nsAutoScriptBlocker scriptBlocker;
+
+ mDoRebuildAllStyleData = true;
+
+ ProcessPendingRestyles();
+}
+
+void
+RestyleManager::StartRebuildAllStyleData(RestyleTracker& aRestyleTracker)
+{
+ MOZ_ASSERT(mIsProcessingRestyles);
+
+ nsIFrame* rootFrame = PresContext()->PresShell()->GetRootFrame();
+ if (!rootFrame) {
+ // No need to do anything.
+ return;
+ }
+
+ mInRebuildAllStyleData = true;
+
+ // Tell the style set to get the old rule tree out of the way
+ // so we can recalculate while maintaining rule tree immutability
+ nsresult rv = StyleSet()->BeginReconstruct();
+ if (NS_FAILED(rv)) {
+ MOZ_CRASH("unable to rebuild style data");
+ }
+
+ nsRestyleHint restyleHint = mRebuildAllRestyleHint;
+ nsChangeHint changeHint = mRebuildAllExtraHint;
+ mRebuildAllExtraHint = nsChangeHint(0);
+ mRebuildAllRestyleHint = nsRestyleHint(0);
+
+ restyleHint |= eRestyle_ForceDescendants;
+
+ if (!(restyleHint & eRestyle_Subtree) &&
+ (restyleHint & ~(eRestyle_Force | eRestyle_ForceDescendants))) {
+ // We want this hint to apply to the root node's primary frame
+ // rather than the root frame, since it's the primary frame that has
+ // the styles for the root element (rather than the ancestors of the
+ // primary frame whose mContent is the root node but which have
+ // different styles). If we use up the hint for one of the
+ // ancestors that we hit first, then we'll fail to do the restyling
+ // we need to do.
+ Element* root = PresContext()->Document()->GetRootElement();
+ if (root) {
+ // If the root element is gone, dropping the hint on the floor
+ // should be fine.
+ aRestyleTracker.AddPendingRestyle(root, restyleHint, nsChangeHint(0));
+ }
+ restyleHint = nsRestyleHint(0);
+ }
+
+ // Recalculate all of the style contexts for the document, from the
+ // root frame. We can't do this with a change hint, since we can't
+ // post a change hint for the root frame.
+ // Note that we can ignore the return value of ComputeStyleChangeFor
+ // because we never need to reframe the root frame.
+ // XXX Does it matter that we're passing aExtraHint to the real root
+ // frame and not the root node's primary frame? (We could do
+ // roughly what we do for aRestyleHint above.)
+ ComputeAndProcessStyleChange(rootFrame,
+ changeHint, aRestyleTracker, restyleHint,
+ RestyleHintData());
+}
+
+void
+RestyleManager::FinishRebuildAllStyleData()
+{
+ MOZ_ASSERT(mInRebuildAllStyleData, "bad caller");
+
+ // Tell the style set it's safe to destroy the old rule tree. We
+ // must do this after the ProcessRestyledFrames call in case the
+ // change list has frame reconstructs in it (since frames to be
+ // reconstructed will still have their old style context pointers
+ // until they are destroyed).
+ StyleSet()->EndReconstruct();
+
+ mInRebuildAllStyleData = false;
+}
+
+void
+RestyleManager::ProcessPendingRestyles()
+{
+ NS_PRECONDITION(PresContext()->Document(), "No document? Pshaw!");
+ NS_PRECONDITION(!nsContentUtils::IsSafeToRunScript(),
+ "Missing a script blocker!");
+
+ // First do any queued-up frame creation. (We should really
+ // merge this into the rest of the process, though; see bug 827239.)
+ PresContext()->FrameConstructor()->CreateNeededFrames();
+
+ // Process non-animation restyles...
+ MOZ_ASSERT(!mIsProcessingRestyles,
+ "Nesting calls to ProcessPendingRestyles?");
+ mIsProcessingRestyles = true;
+
+ // Before we process any restyles, we need to ensure that style
+ // resulting from any animations is up-to-date, so that if any style
+ // changes we cause trigger transitions, we have the correct old style
+ // for starting the transition.
+ bool haveNonAnimation =
+ mHavePendingNonAnimationRestyles || mDoRebuildAllStyleData;
+ if (haveNonAnimation) {
+ ++mAnimationGeneration;
+ UpdateOnlyAnimationStyles();
+ } else {
+ // If we don't have non-animation style updates, then we have queued
+ // up animation style updates from the refresh driver tick. This
+ // doesn't necessarily include *all* animation style updates, since
+ // we might be suppressing main-thread updates for some animations,
+ // so we don't want to call UpdateOnlyAnimationStyles, which updates
+ // all animations. In other words, the work that we're about to do
+ // to process the pending restyles queue is a *subset* of the work
+ // that UpdateOnlyAnimationStyles would do, since we're *not*
+ // updating transitions that are running on the compositor thread
+ // and suppressed on the main thread.
+ //
+ // But when we update those styles, we want to suppress updates to
+ // transitions just like we do in UpdateOnlyAnimationStyles. So we
+ // want to tell the transition manager to act as though we're in
+ // UpdateOnlyAnimationStyles.
+ //
+ // FIXME: In the future, we might want to refactor the way the
+ // animation and transition manager do their refresh driver ticks so
+ // that we can use UpdateOnlyAnimationStyles, with a different
+ // boolean argument, for this update as well, instead of having them
+ // post style updates in their WillRefresh methods.
+ PresContext()->TransitionManager()->SetInAnimationOnlyStyleUpdate(true);
+ }
+
+ ProcessRestyles(mPendingRestyles);
+
+ if (!haveNonAnimation) {
+ PresContext()->TransitionManager()->SetInAnimationOnlyStyleUpdate(false);
+ }
+
+ mIsProcessingRestyles = false;
+
+ NS_ASSERTION(haveNonAnimation || !mHavePendingNonAnimationRestyles,
+ "should not have added restyles");
+ mHavePendingNonAnimationRestyles = false;
+
+ if (mDoRebuildAllStyleData) {
+ // We probably wasted a lot of work up above, but this seems safest
+ // and it should be rarely used.
+ // This might add us as a refresh observer again; that's ok.
+ ProcessPendingRestyles();
+
+ NS_ASSERTION(!mDoRebuildAllStyleData,
+ "repeatedly setting mDoRebuildAllStyleData?");
+ }
+
+ MOZ_ASSERT(!mInRebuildAllStyleData,
+ "should have called FinishRebuildAllStyleData");
+}
+
+void
+RestyleManager::BeginProcessingRestyles(RestyleTracker& aRestyleTracker)
+{
+ // Make sure to not rebuild quote or counter lists while we're
+ // processing restyles
+ PresContext()->FrameConstructor()->BeginUpdate();
+
+ mInStyleRefresh = true;
+
+ if (ShouldStartRebuildAllFor(aRestyleTracker)) {
+ mDoRebuildAllStyleData = false;
+ StartRebuildAllStyleData(aRestyleTracker);
+ }
+}
+
+void
+RestyleManager::EndProcessingRestyles()
+{
+ FlushOverflowChangedTracker();
+
+ MOZ_ASSERT(mAnimationsWithDestroyedFrame);
+ mAnimationsWithDestroyedFrame->
+ StopAnimationsForElementsWithoutFrames();
+
+ // Set mInStyleRefresh to false now, since the EndUpdate call might
+ // add more restyles.
+ mInStyleRefresh = false;
+
+ if (mInRebuildAllStyleData) {
+ FinishRebuildAllStyleData();
+ }
+
+ PresContext()->FrameConstructor()->EndUpdate();
+
+#ifdef DEBUG
+ PresContext()->PresShell()->VerifyStyleTree();
+#endif
+}
+
+void
+RestyleManager::UpdateOnlyAnimationStyles()
+{
+ bool doCSS = PresContext()->EffectCompositor()->HasPendingStyleUpdates();
+
+ nsIDocument* document = PresContext()->Document();
+ nsSMILAnimationController* animationController =
+ document->HasAnimationController() ?
+ document->GetAnimationController() :
+ nullptr;
+ bool doSMIL = animationController &&
+ animationController->MightHavePendingStyleUpdates();
+
+ if (!doCSS && !doSMIL) {
+ return;
+ }
+
+ nsTransitionManager* transitionManager = PresContext()->TransitionManager();
+
+ transitionManager->SetInAnimationOnlyStyleUpdate(true);
+
+ RestyleTracker tracker(ELEMENT_HAS_PENDING_ANIMATION_ONLY_RESTYLE |
+ ELEMENT_IS_POTENTIAL_ANIMATION_ONLY_RESTYLE_ROOT);
+ tracker.Init(this);
+
+ if (doCSS) {
+ // FIXME: We should have the transition manager and animation manager
+ // add only the elements for which animations are currently throttled
+ // (i.e., animating on the compositor with main-thread style updates
+ // suppressed).
+ PresContext()->EffectCompositor()->AddStyleUpdatesTo(tracker);
+ }
+
+ if (doSMIL) {
+ animationController->AddStyleUpdatesTo(tracker);
+ }
+
+ ProcessRestyles(tracker);
+
+ transitionManager->SetInAnimationOnlyStyleUpdate(false);
+}
+
+void
+RestyleManager::PostRestyleEvent(Element* aElement,
+ nsRestyleHint aRestyleHint,
+ nsChangeHint aMinChangeHint,
+ const RestyleHintData* aRestyleHintData)
+{
+ if (MOZ_UNLIKELY(IsDisconnected()) ||
+ MOZ_UNLIKELY(PresContext()->PresShell()->IsDestroying())) {
+ return;
+ }
+
+ if (aRestyleHint == 0 && !aMinChangeHint) {
+ // Nothing to do here
+ return;
+ }
+
+ mPendingRestyles.AddPendingRestyle(aElement, aRestyleHint, aMinChangeHint,
+ aRestyleHintData);
+
+ // Set mHavePendingNonAnimationRestyles for any restyle that could
+ // possibly contain non-animation styles (i.e., those that require us
+ // to do an animation-only style flush before processing style changes
+ // to ensure correct initialization of CSS transitions).
+ if (aRestyleHint & ~eRestyle_AllHintsWithAnimations) {
+ mHavePendingNonAnimationRestyles = true;
+ }
+
+ PostRestyleEventInternal(false);
+}
+
+void
+RestyleManager::PostRebuildAllStyleDataEvent(nsChangeHint aExtraHint,
+ nsRestyleHint aRestyleHint)
+{
+ NS_ASSERTION(!(aExtraHint & nsChangeHint_ReconstructFrame),
+ "Should not reconstruct the root of the frame tree. "
+ "Use ReconstructDocElementHierarchy instead.");
+ MOZ_ASSERT(!(aRestyleHint & eRestyle_SomeDescendants),
+ "PostRebuildAllStyleDataEvent does not handle "
+ "eRestyle_SomeDescendants");
+
+ mDoRebuildAllStyleData = true;
+ mRebuildAllExtraHint |= aExtraHint;
+ mRebuildAllRestyleHint |= aRestyleHint;
+
+ // Get a restyle event posted if necessary
+ PostRestyleEventInternal(false);
+}
+
+// aContent must be the content for the frame in question, which may be
+// :before/:after content
+/* static */ bool
+RestyleManager::TryInitiatingTransition(nsPresContext* aPresContext,
+ nsIContent* aContent,
+ nsStyleContext* aOldStyleContext,
+ RefPtr<nsStyleContext>*
+ aNewStyleContext /* inout */)
+{
+ if (!aContent || !aContent->IsElement()) {
+ return false;
+ }
+
+ // Notify the transition manager. If it starts a transition,
+ // it might modify the new style context.
+ RefPtr<nsStyleContext> sc = *aNewStyleContext;
+ aPresContext->TransitionManager()->StyleContextChanged(
+ aContent->AsElement(), aOldStyleContext, aNewStyleContext);
+ return *aNewStyleContext != sc;
+}
+
+static dom::Element*
+ElementForStyleContext(nsIContent* aParentContent,
+ nsIFrame* aFrame,
+ CSSPseudoElementType aPseudoType)
+{
+ // We don't expect XUL tree stuff here.
+ NS_PRECONDITION(aPseudoType == CSSPseudoElementType::NotPseudo ||
+ aPseudoType == CSSPseudoElementType::AnonBox ||
+ aPseudoType < CSSPseudoElementType::Count,
+ "Unexpected pseudo");
+ // XXX see the comments about the various element confusion in
+ // ElementRestyler::Restyle.
+ if (aPseudoType == CSSPseudoElementType::NotPseudo) {
+ return aFrame->GetContent()->AsElement();
+ }
+
+ if (aPseudoType == CSSPseudoElementType::AnonBox) {
+ return nullptr;
+ }
+
+ if (aPseudoType == CSSPseudoElementType::firstLetter) {
+ NS_ASSERTION(aFrame->GetType() == nsGkAtoms::letterFrame,
+ "firstLetter pseudoTag without a nsFirstLetterFrame");
+ nsBlockFrame* block = nsBlockFrame::GetNearestAncestorBlock(aFrame);
+ return block->GetContent()->AsElement();
+ }
+
+ if (aPseudoType == CSSPseudoElementType::mozColorSwatch) {
+ MOZ_ASSERT(aFrame->GetParent() &&
+ aFrame->GetParent()->GetParent(),
+ "Color swatch frame should have a parent & grandparent");
+
+ nsIFrame* grandparentFrame = aFrame->GetParent()->GetParent();
+ MOZ_ASSERT(grandparentFrame->GetType() == nsGkAtoms::colorControlFrame,
+ "Color swatch's grandparent should be nsColorControlFrame");
+
+ return grandparentFrame->GetContent()->AsElement();
+ }
+
+ if (aPseudoType == CSSPseudoElementType::mozNumberText ||
+ aPseudoType == CSSPseudoElementType::mozNumberWrapper ||
+ aPseudoType == CSSPseudoElementType::mozNumberSpinBox ||
+ aPseudoType == CSSPseudoElementType::mozNumberSpinUp ||
+ aPseudoType == CSSPseudoElementType::mozNumberSpinDown) {
+ // Get content for nearest nsNumberControlFrame:
+ nsIFrame* f = aFrame->GetParent();
+ MOZ_ASSERT(f);
+ while (f->GetType() != nsGkAtoms::numberControlFrame) {
+ f = f->GetParent();
+ MOZ_ASSERT(f);
+ }
+ return f->GetContent()->AsElement();
+ }
+
+ if (aParentContent) {
+ return aParentContent->AsElement();
+ }
+
+ MOZ_ASSERT(aFrame->GetContent()->GetParent(),
+ "should not have got here for the root element");
+ return aFrame->GetContent()->GetParent()->AsElement();
+}
+
+/**
+ * Some pseudo-elements actually have a content node created for them,
+ * whereas others have only a frame but not a content node. In some
+ * cases, we want to support style attributes or states on those
+ * elements. For those pseudo-elements, we need to pass the
+ * anonymous pseudo-element content to selector matching processes in
+ * addition to the element that the pseudo-element is for; in other
+ * cases we should pass null instead. This function returns the
+ * pseudo-element content that we should pass.
+ */
+static dom::Element*
+PseudoElementForStyleContext(nsIFrame* aFrame,
+ CSSPseudoElementType aPseudoType)
+{
+ if (aPseudoType >= CSSPseudoElementType::Count) {
+ return nullptr;
+ }
+
+ if (nsCSSPseudoElements::PseudoElementSupportsStyleAttribute(aPseudoType) ||
+ nsCSSPseudoElements::PseudoElementSupportsUserActionState(aPseudoType)) {
+ return aFrame->GetContent()->AsElement();
+ }
+
+ return nullptr;
+}
+
+/**
+ * FIXME: Temporary. Should merge with following function.
+ */
+static nsIFrame*
+GetPrevContinuationWithPossiblySameStyle(nsIFrame* aFrame)
+{
+ // Account for {ib} splits when looking for "prevContinuation". In
+ // particular, for the first-continuation of a part of an {ib} split
+ // we want to use the previous ib-split sibling of the previous
+ // ib-split sibling of aFrame, which should have the same style
+ // context as aFrame itself. In particular, if aFrame is the first
+ // continuation of an inline part of a block-in-inline split then its
+ // previous ib-split sibling is a block, and the previous ib-split
+ // sibling of _that_ is an inline, just like aFrame. Similarly, if
+ // aFrame is the first continuation of a block part of an
+ // block-in-inline split (a block-in-inline wrapper block), then its
+ // previous ib-split sibling is an inline and the previous ib-split
+ // sibling of that is either another block-in-inline wrapper block box
+ // or null.
+ nsIFrame* prevContinuation = aFrame->GetPrevContinuation();
+ if (!prevContinuation &&
+ (aFrame->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT)) {
+ // We're the first continuation, so we can just get the frame
+ // property directly
+ prevContinuation =
+ aFrame->Properties().Get(nsIFrame::IBSplitPrevSibling());
+ if (prevContinuation) {
+ prevContinuation =
+ prevContinuation->Properties().Get(nsIFrame::IBSplitPrevSibling());
+ }
+ }
+
+ NS_ASSERTION(!prevContinuation ||
+ prevContinuation->GetContent() == aFrame->GetContent(),
+ "unexpected content mismatch");
+
+ return prevContinuation;
+}
+
+/**
+ * Get the previous continuation or similar ib-split sibling (assuming
+ * block/inline alternation), conditionally on it having the same style.
+ * This assumes that we're not between resolving the two (i.e., that
+ * they're both already resolved.
+ */
+static nsIFrame*
+GetPrevContinuationWithSameStyle(nsIFrame* aFrame)
+{
+ nsIFrame* prevContinuation = GetPrevContinuationWithPossiblySameStyle(aFrame);
+ if (!prevContinuation) {
+ return nullptr;
+ }
+
+ nsStyleContext* prevStyle = prevContinuation->StyleContext();
+ nsStyleContext* selfStyle = aFrame->StyleContext();
+ if (prevStyle != selfStyle) {
+ NS_ASSERTION(prevStyle->GetPseudo() != selfStyle->GetPseudo() ||
+ prevStyle->GetParent() != selfStyle->GetParent(),
+ "continuations should have the same style context");
+ prevContinuation = nullptr;
+ }
+ return prevContinuation;
+}
+
+nsresult
+RestyleManager::ReparentStyleContext(nsIFrame* aFrame)
+{
+ nsIAtom* frameType = aFrame->GetType();
+ if (frameType == nsGkAtoms::placeholderFrame) {
+ // Also reparent the out-of-flow and all its continuations.
+ nsIFrame* outOfFlow =
+ nsPlaceholderFrame::GetRealFrameForPlaceholder(aFrame);
+ NS_ASSERTION(outOfFlow, "no out-of-flow frame");
+ do {
+ ReparentStyleContext(outOfFlow);
+ } while ((outOfFlow = outOfFlow->GetNextContinuation()));
+ } else if (frameType == nsGkAtoms::backdropFrame) {
+ // Style context of backdrop frame has no parent style context, and
+ // thus we do not need to reparent it.
+ return NS_OK;
+ }
+
+ // DO NOT verify the style tree before reparenting. The frame
+ // tree has already been changed, so this check would just fail.
+ nsStyleContext* oldContext = aFrame->StyleContext();
+
+ RefPtr<nsStyleContext> newContext;
+ nsIFrame* providerFrame;
+ nsStyleContext* newParentContext = aFrame->GetParentStyleContext(&providerFrame);
+ bool isChild = providerFrame && providerFrame->GetParent() == aFrame;
+ nsIFrame* providerChild = nullptr;
+ if (isChild) {
+ ReparentStyleContext(providerFrame);
+ // Get the style context again after ReparentStyleContext() which might have
+ // changed it.
+ newParentContext = providerFrame->StyleContext();
+ providerChild = providerFrame;
+ }
+ NS_ASSERTION(newParentContext, "Reparenting something that has no usable"
+ " parent? Shouldn't happen!");
+ // XXX need to do something here to produce the correct style context for
+ // an IB split whose first inline part is inside a first-line frame.
+ // Currently the first IB anonymous block's style context takes the first
+ // part's style context as parent, which is wrong since first-line style
+ // should not apply to the anonymous block.
+
+#ifdef DEBUG
+ {
+ // Check that our assumption that continuations of the same
+ // pseudo-type and with the same style context parent have the
+ // same style context is valid before the reresolution. (We need
+ // to check the pseudo-type and style context parent because of
+ // :first-letter and :first-line, where we create styled and
+ // unstyled letter/line frames distinguished by pseudo-type, and
+ // then need to distinguish their descendants based on having
+ // different parents.)
+ nsIFrame* nextContinuation = aFrame->GetNextContinuation();
+ if (nextContinuation) {
+ nsStyleContext* nextContinuationContext =
+ nextContinuation->StyleContext();
+ NS_ASSERTION(oldContext == nextContinuationContext ||
+ oldContext->GetPseudo() !=
+ nextContinuationContext->GetPseudo() ||
+ oldContext->GetParent() !=
+ nextContinuationContext->GetParent(),
+ "continuations should have the same style context");
+ }
+ }
+#endif
+
+ nsIFrame* prevContinuation =
+ GetPrevContinuationWithPossiblySameStyle(aFrame);
+ nsStyleContext* prevContinuationContext;
+ bool copyFromContinuation =
+ prevContinuation &&
+ (prevContinuationContext = prevContinuation->StyleContext())
+ ->GetPseudo() == oldContext->GetPseudo() &&
+ prevContinuationContext->GetParent() == newParentContext;
+ if (copyFromContinuation) {
+ // Just use the style context from the frame's previous
+ // continuation (see assertion about aFrame->GetNextContinuation()
+ // above, which we would have previously hit for aFrame's previous
+ // continuation).
+ newContext = prevContinuationContext;
+ } else {
+ nsIFrame* parentFrame = aFrame->GetParent();
+ Element* element =
+ ElementForStyleContext(parentFrame ? parentFrame->GetContent() : nullptr,
+ aFrame,
+ oldContext->GetPseudoType());
+ newContext = StyleSet()->
+ ReparentStyleContext(oldContext, newParentContext, element);
+ }
+
+ if (newContext) {
+ if (newContext != oldContext) {
+ // We probably don't want to initiate transitions from
+ // ReparentStyleContext, since we call it during frame
+ // construction rather than in response to dynamic changes.
+ // Also see the comment at the start of
+ // nsTransitionManager::ConsiderInitiatingTransition.
+#if 0
+ if (!copyFromContinuation) {
+ TryInitiatingTransition(mPresContext, aFrame->GetContent(),
+ oldContext, &newContext);
+ }
+#endif
+
+ // Make sure to call CalcStyleDifference so that the new context ends
+ // up resolving all the structs the old context resolved.
+ if (!copyFromContinuation) {
+ uint32_t equalStructs;
+ uint32_t samePointerStructs;
+ DebugOnly<nsChangeHint> styleChange =
+ oldContext->CalcStyleDifference(newContext, nsChangeHint(0),
+ &equalStructs,
+ &samePointerStructs);
+ // The style change is always 0 because we have the same rulenode and
+ // CalcStyleDifference optimizes us away. That's OK, though:
+ // reparenting should never trigger a frame reconstruct, and whenever
+ // it's happening we already plan to reflow and repaint the frames.
+ NS_ASSERTION(!(styleChange & nsChangeHint_ReconstructFrame),
+ "Our frame tree is likely to be bogus!");
+ }
+
+ aFrame->SetStyleContext(newContext);
+
+ nsIFrame::ChildListIterator lists(aFrame);
+ for (; !lists.IsDone(); lists.Next()) {
+ for (nsIFrame* child : lists.CurrentList()) {
+ // only do frames that are in flow
+ if (!(child->GetStateBits() & NS_FRAME_OUT_OF_FLOW) &&
+ child != providerChild) {
+#ifdef DEBUG
+ if (nsGkAtoms::placeholderFrame == child->GetType()) {
+ nsIFrame* outOfFlowFrame =
+ nsPlaceholderFrame::GetRealFrameForPlaceholder(child);
+ NS_ASSERTION(outOfFlowFrame, "no out-of-flow frame");
+
+ NS_ASSERTION(outOfFlowFrame != providerChild,
+ "Out of flow provider?");
+ }
+#endif
+ ReparentStyleContext(child);
+ }
+ }
+ }
+
+ // If this frame is part of an IB split, then the style context of
+ // the next part of the split might be a child of our style context.
+ // Reparent its style context just in case one of our ancestors
+ // (split or not) hasn't done so already). It's not a problem to
+ // reparent the same frame twice because the "if (newContext !=
+ // oldContext)" check will prevent us from redoing work.
+ if ((aFrame->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT) &&
+ !aFrame->GetPrevContinuation()) {
+ nsIFrame* sib =
+ aFrame->Properties().Get(nsIFrame::IBSplitSibling());
+ if (sib) {
+ ReparentStyleContext(sib);
+ }
+ }
+
+ // do additional contexts
+ int32_t contextIndex = 0;
+ for (nsStyleContext* oldExtraContext;
+ (oldExtraContext = aFrame->GetAdditionalStyleContext(contextIndex));
+ ++contextIndex) {
+ RefPtr<nsStyleContext> newExtraContext;
+ newExtraContext = StyleSet()->
+ ReparentStyleContext(oldExtraContext,
+ newContext, nullptr);
+ if (newExtraContext) {
+ if (newExtraContext != oldExtraContext) {
+ // Make sure to call CalcStyleDifference so that the new
+ // context ends up resolving all the structs the old context
+ // resolved.
+ uint32_t equalStructs;
+ uint32_t samePointerStructs;
+ DebugOnly<nsChangeHint> styleChange =
+ oldExtraContext->CalcStyleDifference(newExtraContext,
+ nsChangeHint(0),
+ &equalStructs,
+ &samePointerStructs);
+ // The style change is always 0 because we have the same
+ // rulenode and CalcStyleDifference optimizes us away. That's
+ // OK, though: reparenting should never trigger a frame
+ // reconstruct, and whenever it's happening we already plan to
+ // reflow and repaint the frames.
+ NS_ASSERTION(!(styleChange & nsChangeHint_ReconstructFrame),
+ "Our frame tree is likely to be bogus!");
+ }
+
+ aFrame->SetAdditionalStyleContext(contextIndex, newExtraContext);
+ }
+ }
+#ifdef DEBUG
+ DebugVerifyStyleTree(aFrame);
+#endif
+ }
+ }
+
+ return NS_OK;
+}
+
+ElementRestyler::ElementRestyler(nsPresContext* aPresContext,
+ nsIFrame* aFrame,
+ nsStyleChangeList* aChangeList,
+ nsChangeHint aHintsHandledByAncestors,
+ RestyleTracker& aRestyleTracker,
+ nsTArray<nsCSSSelector*>&
+ aSelectorsForDescendants,
+ TreeMatchContext& aTreeMatchContext,
+ nsTArray<nsIContent*>&
+ aVisibleKidsOfHiddenElement,
+ nsTArray<ContextToClear>& aContextsToClear,
+ nsTArray<RefPtr<nsStyleContext>>&
+ aSwappedStructOwners)
+ : mPresContext(aPresContext)
+ , mFrame(aFrame)
+ , mParentContent(nullptr)
+ // XXXldb Why does it make sense to use aParentContent? (See
+ // comment above assertion at start of ElementRestyler::Restyle.)
+ , mContent(mFrame->GetContent() ? mFrame->GetContent() : mParentContent)
+ , mChangeList(aChangeList)
+ , mHintsHandled(aHintsHandledByAncestors &
+ ~NS_HintsNotHandledForDescendantsIn(aHintsHandledByAncestors))
+ , mParentFrameHintsNotHandledForDescendants(nsChangeHint(0))
+ , mHintsNotHandledForDescendants(nsChangeHint(0))
+ , mRestyleTracker(aRestyleTracker)
+ , mSelectorsForDescendants(aSelectorsForDescendants)
+ , mTreeMatchContext(aTreeMatchContext)
+ , mResolvedChild(nullptr)
+ , mContextsToClear(aContextsToClear)
+ , mSwappedStructOwners(aSwappedStructOwners)
+ , mIsRootOfRestyle(true)
+#ifdef ACCESSIBILITY
+ , mDesiredA11yNotifications(eSendAllNotifications)
+ , mKidsDesiredA11yNotifications(mDesiredA11yNotifications)
+ , mOurA11yNotification(eDontNotify)
+ , mVisibleKidsOfHiddenElement(aVisibleKidsOfHiddenElement)
+#endif
+#ifdef RESTYLE_LOGGING
+ , mLoggingDepth(aRestyleTracker.LoggingDepth() + 1)
+#endif
+{
+ MOZ_ASSERT_IF(mContent, !mContent->IsStyledByServo());
+}
+
+ElementRestyler::ElementRestyler(const ElementRestyler& aParentRestyler,
+ nsIFrame* aFrame,
+ uint32_t aConstructorFlags)
+ : mPresContext(aParentRestyler.mPresContext)
+ , mFrame(aFrame)
+ , mParentContent(aParentRestyler.mContent)
+ // XXXldb Why does it make sense to use aParentContent? (See
+ // comment above assertion at start of ElementRestyler::Restyle.)
+ , mContent(mFrame->GetContent() ? mFrame->GetContent() : mParentContent)
+ , mChangeList(aParentRestyler.mChangeList)
+ , mHintsHandled(aParentRestyler.mHintsHandled &
+ ~NS_HintsNotHandledForDescendantsIn(aParentRestyler.mHintsHandled))
+ , mParentFrameHintsNotHandledForDescendants(
+ aParentRestyler.mHintsNotHandledForDescendants)
+ , mHintsNotHandledForDescendants(nsChangeHint(0))
+ , mRestyleTracker(aParentRestyler.mRestyleTracker)
+ , mSelectorsForDescendants(aParentRestyler.mSelectorsForDescendants)
+ , mTreeMatchContext(aParentRestyler.mTreeMatchContext)
+ , mResolvedChild(nullptr)
+ , mContextsToClear(aParentRestyler.mContextsToClear)
+ , mSwappedStructOwners(aParentRestyler.mSwappedStructOwners)
+ , mIsRootOfRestyle(false)
+#ifdef ACCESSIBILITY
+ , mDesiredA11yNotifications(aParentRestyler.mKidsDesiredA11yNotifications)
+ , mKidsDesiredA11yNotifications(mDesiredA11yNotifications)
+ , mOurA11yNotification(eDontNotify)
+ , mVisibleKidsOfHiddenElement(aParentRestyler.mVisibleKidsOfHiddenElement)
+#endif
+#ifdef RESTYLE_LOGGING
+ , mLoggingDepth(aParentRestyler.mLoggingDepth + 1)
+#endif
+{
+ MOZ_ASSERT_IF(mContent, !mContent->IsStyledByServo());
+ if (aConstructorFlags & FOR_OUT_OF_FLOW_CHILD) {
+ // Note that the out-of-flow may not be a geometric descendant of
+ // the frame where we started the reresolve. Therefore, even if
+ // mHintsHandled already includes nsChangeHint_AllReflowHints we
+ // don't want to pass that on to the out-of-flow reresolve, since
+ // that can lead to the out-of-flow not getting reflowed when it
+ // should be (eg a reresolve starting at <body> that involves
+ // reflowing the <body> would miss reflowing fixed-pos nodes that
+ // also need reflow). In the cases when the out-of-flow _is_ a
+ // geometric descendant of a frame we already have a reflow hint
+ // for, reflow coalescing should keep us from doing the work twice.
+ mHintsHandled &= ~nsChangeHint_AllReflowHints;
+ }
+}
+
+ElementRestyler::ElementRestyler(ParentContextFromChildFrame,
+ const ElementRestyler& aParentRestyler,
+ nsIFrame* aFrame)
+ : mPresContext(aParentRestyler.mPresContext)
+ , mFrame(aFrame)
+ , mParentContent(aParentRestyler.mParentContent)
+ // XXXldb Why does it make sense to use aParentContent? (See
+ // comment above assertion at start of ElementRestyler::Restyle.)
+ , mContent(mFrame->GetContent() ? mFrame->GetContent() : mParentContent)
+ , mChangeList(aParentRestyler.mChangeList)
+ , mHintsHandled(aParentRestyler.mHintsHandled &
+ ~NS_HintsNotHandledForDescendantsIn(aParentRestyler.mHintsHandled))
+ , mParentFrameHintsNotHandledForDescendants(
+ // assume the worst
+ nsChangeHint_Hints_NotHandledForDescendants)
+ , mHintsNotHandledForDescendants(nsChangeHint(0))
+ , mRestyleTracker(aParentRestyler.mRestyleTracker)
+ , mSelectorsForDescendants(aParentRestyler.mSelectorsForDescendants)
+ , mTreeMatchContext(aParentRestyler.mTreeMatchContext)
+ , mResolvedChild(nullptr)
+ , mContextsToClear(aParentRestyler.mContextsToClear)
+ , mSwappedStructOwners(aParentRestyler.mSwappedStructOwners)
+ , mIsRootOfRestyle(false)
+#ifdef ACCESSIBILITY
+ , mDesiredA11yNotifications(aParentRestyler.mDesiredA11yNotifications)
+ , mKidsDesiredA11yNotifications(mDesiredA11yNotifications)
+ , mOurA11yNotification(eDontNotify)
+ , mVisibleKidsOfHiddenElement(aParentRestyler.mVisibleKidsOfHiddenElement)
+#endif
+#ifdef RESTYLE_LOGGING
+ , mLoggingDepth(aParentRestyler.mLoggingDepth + 1)
+#endif
+{
+ MOZ_ASSERT_IF(mContent, !mContent->IsStyledByServo());
+}
+
+ElementRestyler::ElementRestyler(nsPresContext* aPresContext,
+ nsIContent* aContent,
+ nsStyleChangeList* aChangeList,
+ nsChangeHint aHintsHandledByAncestors,
+ RestyleTracker& aRestyleTracker,
+ nsTArray<nsCSSSelector*>& aSelectorsForDescendants,
+ TreeMatchContext& aTreeMatchContext,
+ nsTArray<nsIContent*>&
+ aVisibleKidsOfHiddenElement,
+ nsTArray<ContextToClear>& aContextsToClear,
+ nsTArray<RefPtr<nsStyleContext>>&
+ aSwappedStructOwners)
+ : mPresContext(aPresContext)
+ , mFrame(nullptr)
+ , mParentContent(nullptr)
+ , mContent(aContent)
+ , mChangeList(aChangeList)
+ , mHintsHandled(aHintsHandledByAncestors &
+ ~NS_HintsNotHandledForDescendantsIn(aHintsHandledByAncestors))
+ , mParentFrameHintsNotHandledForDescendants(nsChangeHint(0))
+ , mHintsNotHandledForDescendants(nsChangeHint(0))
+ , mRestyleTracker(aRestyleTracker)
+ , mSelectorsForDescendants(aSelectorsForDescendants)
+ , mTreeMatchContext(aTreeMatchContext)
+ , mResolvedChild(nullptr)
+ , mContextsToClear(aContextsToClear)
+ , mSwappedStructOwners(aSwappedStructOwners)
+ , mIsRootOfRestyle(true)
+#ifdef ACCESSIBILITY
+ , mDesiredA11yNotifications(eSendAllNotifications)
+ , mKidsDesiredA11yNotifications(mDesiredA11yNotifications)
+ , mOurA11yNotification(eDontNotify)
+ , mVisibleKidsOfHiddenElement(aVisibleKidsOfHiddenElement)
+#endif
+{
+}
+
+void
+ElementRestyler::AddLayerChangesForAnimation()
+{
+ uint64_t frameGeneration =
+ RestyleManager::GetAnimationGenerationForFrame(mFrame);
+
+ nsChangeHint hint = nsChangeHint(0);
+ for (const LayerAnimationInfo::Record& layerInfo :
+ LayerAnimationInfo::sRecords) {
+ Layer* layer =
+ FrameLayerBuilder::GetDedicatedLayer(mFrame, layerInfo.mLayerType);
+ if (layer && frameGeneration != layer->GetAnimationGeneration()) {
+ // If we have a transform layer but don't have any transform style, we
+ // probably just removed the transform but haven't destroyed the layer
+ // yet. In this case we will add the appropriate change hint
+ // (nsChangeHint_UpdateContainingBlock) when we compare style contexts
+ // so we can skip adding any change hint here. (If we *were* to add
+ // nsChangeHint_UpdateTransformLayer, ApplyRenderingChangeToTree would
+ // complain that we're updating a transform layer without a transform).
+ if (layerInfo.mLayerType == nsDisplayItem::TYPE_TRANSFORM &&
+ !mFrame->StyleDisplay()->HasTransformStyle()) {
+ continue;
+ }
+ hint |= layerInfo.mChangeHint;
+ }
+
+ // We consider it's the first paint for the frame if we have an animation
+ // for the property but have no layer.
+ // Note that in case of animations which has properties preventing running
+ // on the compositor, e.g., width or height, corresponding layer is not
+ // created at all, but even in such cases, we normally set valid change
+ // hint for such animations in each tick, i.e. restyles in each tick. As
+ // a result, we usually do restyles for such animations in every tick on
+ // the main-thread. The only animations which will be affected by this
+ // explicit change hint are animations that have opacity/transform but did
+ // not have those properies just before. e.g, setting transform by
+ // setKeyframes or changing target element from other target which prevents
+ // running on the compositor, etc.
+ if (!layer &&
+ nsLayoutUtils::HasEffectiveAnimation(mFrame, layerInfo.mProperty)) {
+ hint |= layerInfo.mChangeHint;
+ }
+ }
+ if (hint) {
+ mChangeList->AppendChange(mFrame, mContent, hint);
+ }
+}
+
+void
+ElementRestyler::CaptureChange(nsStyleContext* aOldContext,
+ nsStyleContext* aNewContext,
+ nsChangeHint aChangeToAssume,
+ uint32_t* aEqualStructs,
+ uint32_t* aSamePointerStructs)
+{
+ static_assert(nsStyleStructID_Length <= 32,
+ "aEqualStructs is not big enough");
+
+ // Check some invariants about replacing one style context with another.
+ NS_ASSERTION(aOldContext->GetPseudo() == aNewContext->GetPseudo(),
+ "old and new style contexts should have the same pseudo");
+ NS_ASSERTION(aOldContext->GetPseudoType() == aNewContext->GetPseudoType(),
+ "old and new style contexts should have the same pseudo");
+
+ nsChangeHint ourChange =
+ aOldContext->CalcStyleDifference(aNewContext,
+ mParentFrameHintsNotHandledForDescendants,
+ aEqualStructs,
+ aSamePointerStructs);
+ NS_ASSERTION(!(ourChange & nsChangeHint_AllReflowHints) ||
+ (ourChange & nsChangeHint_NeedReflow),
+ "Reflow hint bits set without actually asking for a reflow");
+
+ LOG_RESTYLE("CaptureChange, ourChange = %s, aChangeToAssume = %s",
+ RestyleManager::ChangeHintToString(ourChange).get(),
+ RestyleManager::ChangeHintToString(aChangeToAssume).get());
+ LOG_RESTYLE_INDENT();
+
+ // nsChangeHint_UpdateEffects is inherited, but it can be set due to changes
+ // in inherited properties (fill and stroke). Avoid propagating it into
+ // text nodes.
+ if ((ourChange & nsChangeHint_UpdateEffects) &&
+ mContent && !mContent->IsElement()) {
+ ourChange &= ~nsChangeHint_UpdateEffects;
+ }
+
+ ourChange |= aChangeToAssume;
+ if (!NS_IsHintSubset(ourChange, mHintsHandled)) {
+ mHintsHandled |= ourChange;
+ if (!(ourChange & nsChangeHint_ReconstructFrame) || mContent) {
+ LOG_RESTYLE("appending change %s",
+ RestyleManager::ChangeHintToString(ourChange).get());
+ mChangeList->AppendChange(mFrame, mContent, ourChange);
+ } else {
+ LOG_RESTYLE("change has already been handled");
+ }
+ }
+ mHintsNotHandledForDescendants |=
+ NS_HintsNotHandledForDescendantsIn(ourChange);
+ LOG_RESTYLE("mHintsNotHandledForDescendants = %s",
+ RestyleManager::ChangeHintToString(mHintsNotHandledForDescendants).get());
+}
+
+class MOZ_RAII AutoSelectorArrayTruncater final
+{
+public:
+ explicit AutoSelectorArrayTruncater(
+ nsTArray<nsCSSSelector*>& aSelectorsForDescendants)
+ : mSelectorsForDescendants(aSelectorsForDescendants)
+ , mOriginalLength(aSelectorsForDescendants.Length())
+ {
+ }
+
+ ~AutoSelectorArrayTruncater()
+ {
+ mSelectorsForDescendants.TruncateLength(mOriginalLength);
+ }
+
+private:
+ nsTArray<nsCSSSelector*>& mSelectorsForDescendants;
+ size_t mOriginalLength;
+};
+
+/**
+ * Called when we are stopping a restyle with eRestyle_SomeDescendants, to
+ * search for descendants that match any of the selectors in
+ * mSelectorsForDescendants. If the element does match one of the selectors,
+ * we cause it to be restyled with eRestyle_Self.
+ *
+ * We traverse down the frame tree (and through the flattened content tree
+ * when we find undisplayed content) unless we find an element that (a) already
+ * has a pending restyle, or (b) does not have a pending restyle but does match
+ * one of the selectors in mSelectorsForDescendants. For (a), we add the
+ * current mSelectorsForDescendants into the existing restyle data, and for (b)
+ * we add a new pending restyle with that array. So in both cases, when we
+ * come to restyling this element back up in ProcessPendingRestyles, we will
+ * again find the eRestyle_SomeDescendants hint and its selectors array.
+ *
+ * This ensures that we don't visit descendant elements and check them
+ * against mSelectorsForDescendants more than once.
+ */
+void
+ElementRestyler::ConditionallyRestyleChildren()
+{
+ MOZ_ASSERT(mContent == mFrame->GetContent());
+
+ if (!mContent->IsElement() || mSelectorsForDescendants.IsEmpty()) {
+ return;
+ }
+
+ Element* element = mContent->AsElement();
+
+ LOG_RESTYLE("traversing descendants of frame %s (with element %s) to "
+ "propagate eRestyle_SomeDescendants for these %d selectors:",
+ FrameTagToString(mFrame).get(),
+ ElementTagToString(element).get(),
+ int(mSelectorsForDescendants.Length()));
+ LOG_RESTYLE_INDENT();
+#ifdef RESTYLE_LOGGING
+ for (nsCSSSelector* sel : mSelectorsForDescendants) {
+ LOG_RESTYLE("%s", sel->RestrictedSelectorToString().get());
+ }
+#endif
+
+ Element* restyleRoot = mRestyleTracker.FindClosestRestyleRoot(element);
+ ConditionallyRestyleChildren(mFrame, restyleRoot);
+}
+
+void
+ElementRestyler::ConditionallyRestyleChildren(nsIFrame* aFrame,
+ Element* aRestyleRoot)
+{
+ MOZ_ASSERT(aFrame->GetContent());
+ MOZ_ASSERT(aFrame->GetContent()->IsElement());
+ MOZ_ASSERT(!aFrame->GetContent()->IsStyledByServo());
+
+ ConditionallyRestyleUndisplayedDescendants(aFrame, aRestyleRoot);
+ ConditionallyRestyleContentChildren(aFrame, aRestyleRoot);
+}
+
+// The structure of this method parallels RestyleContentChildren.
+// If you update this method, you probably want to update that one too.
+void
+ElementRestyler::ConditionallyRestyleContentChildren(nsIFrame* aFrame,
+ Element* aRestyleRoot)
+{
+ MOZ_ASSERT(aFrame->GetContent());
+ MOZ_ASSERT(aFrame->GetContent()->IsElement());
+ MOZ_ASSERT(!aFrame->GetContent()->IsStyledByServo());
+
+ if (aFrame->GetContent()->HasFlag(mRestyleTracker.RootBit())) {
+ aRestyleRoot = aFrame->GetContent()->AsElement();
+ }
+
+ for (nsIFrame* f = aFrame; f;
+ f = RestyleManager::GetNextContinuationWithSameStyle(f, f->StyleContext())) {
+ nsIFrame::ChildListIterator lists(f);
+ for (; !lists.IsDone(); lists.Next()) {
+ for (nsIFrame* child : lists.CurrentList()) {
+ // Out-of-flows are reached through their placeholders. Continuations
+ // and block-in-inline splits are reached through those chains.
+ if (!(child->GetStateBits() & NS_FRAME_OUT_OF_FLOW) &&
+ !GetPrevContinuationWithSameStyle(child)) {
+ // only do frames that are in flow
+ if (child->GetType() == nsGkAtoms::placeholderFrame) { // placeholder
+ // get out of flow frame and recur there
+ nsIFrame* outOfFlowFrame =
+ nsPlaceholderFrame::GetRealFrameForPlaceholder(child);
+
+ // |nsFrame::GetParentStyleContext| checks being out
+ // of flow so that this works correctly.
+ do {
+ if (GetPrevContinuationWithSameStyle(outOfFlowFrame)) {
+ continue;
+ }
+ if (!ConditionallyRestyle(outOfFlowFrame, aRestyleRoot)) {
+ ConditionallyRestyleChildren(outOfFlowFrame, aRestyleRoot);
+ }
+ } while ((outOfFlowFrame = outOfFlowFrame->GetNextContinuation()));
+ } else { // regular child frame
+ if (child != mResolvedChild) {
+ if (!ConditionallyRestyle(child, aRestyleRoot)) {
+ ConditionallyRestyleChildren(child, aRestyleRoot);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+// The structure of this method parallels RestyleUndisplayedDescendants.
+// If you update this method, you probably want to update that one too.
+void
+ElementRestyler::ConditionallyRestyleUndisplayedDescendants(
+ nsIFrame* aFrame,
+ Element* aRestyleRoot)
+{
+ nsIContent* undisplayedParent;
+ if (MustCheckUndisplayedContent(aFrame, undisplayedParent)) {
+ DoConditionallyRestyleUndisplayedDescendants(undisplayedParent,
+ aRestyleRoot);
+ }
+}
+
+// The structure of this method parallels DoRestyleUndisplayedDescendants.
+// If you update this method, you probably want to update that one too.
+void
+ElementRestyler::DoConditionallyRestyleUndisplayedDescendants(
+ nsIContent* aParent,
+ Element* aRestyleRoot)
+{
+ nsCSSFrameConstructor* fc = mPresContext->FrameConstructor();
+ UndisplayedNode* nodes = fc->GetAllUndisplayedContentIn(aParent);
+ ConditionallyRestyleUndisplayedNodes(nodes, aParent,
+ StyleDisplay::None, aRestyleRoot);
+ nodes = fc->GetAllDisplayContentsIn(aParent);
+ ConditionallyRestyleUndisplayedNodes(nodes, aParent,
+ StyleDisplay::Contents, aRestyleRoot);
+}
+
+// The structure of this method parallels RestyleUndisplayedNodes.
+// If you update this method, you probably want to update that one too.
+void
+ElementRestyler::ConditionallyRestyleUndisplayedNodes(
+ UndisplayedNode* aUndisplayed,
+ nsIContent* aUndisplayedParent,
+ const StyleDisplay aDisplay,
+ Element* aRestyleRoot)
+{
+ MOZ_ASSERT(aDisplay == StyleDisplay::None ||
+ aDisplay == StyleDisplay::Contents);
+ if (!aUndisplayed) {
+ return;
+ }
+
+ if (aUndisplayedParent &&
+ aUndisplayedParent->IsElement() &&
+ aUndisplayedParent->HasFlag(mRestyleTracker.RootBit())) {
+ MOZ_ASSERT(!aUndisplayedParent->IsStyledByServo());
+ aRestyleRoot = aUndisplayedParent->AsElement();
+ }
+
+ for (UndisplayedNode* undisplayed = aUndisplayed; undisplayed;
+ undisplayed = undisplayed->mNext) {
+
+ if (!undisplayed->mContent->IsElement()) {
+ continue;
+ }
+
+ Element* element = undisplayed->mContent->AsElement();
+
+ if (!ConditionallyRestyle(element, aRestyleRoot)) {
+ if (aDisplay == StyleDisplay::None) {
+ ConditionallyRestyleContentDescendants(element, aRestyleRoot);
+ } else { // StyleDisplay::Contents
+ DoConditionallyRestyleUndisplayedDescendants(element, aRestyleRoot);
+ }
+ }
+ }
+}
+
+void
+ElementRestyler::ConditionallyRestyleContentDescendants(Element* aElement,
+ Element* aRestyleRoot)
+{
+ MOZ_ASSERT(!aElement->IsStyledByServo());
+ if (aElement->HasFlag(mRestyleTracker.RootBit())) {
+ aRestyleRoot = aElement;
+ }
+
+ FlattenedChildIterator it(aElement);
+ for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
+ if (n->IsElement()) {
+ Element* e = n->AsElement();
+ if (!ConditionallyRestyle(e, aRestyleRoot)) {
+ ConditionallyRestyleContentDescendants(e, aRestyleRoot);
+ }
+ }
+ }
+}
+
+bool
+ElementRestyler::ConditionallyRestyle(nsIFrame* aFrame, Element* aRestyleRoot)
+{
+ MOZ_ASSERT(aFrame->GetContent());
+
+ if (!aFrame->GetContent()->IsElement()) {
+ return true;
+ }
+
+ return ConditionallyRestyle(aFrame->GetContent()->AsElement(), aRestyleRoot);
+}
+
+bool
+ElementRestyler::ConditionallyRestyle(Element* aElement, Element* aRestyleRoot)
+{
+ MOZ_ASSERT(!aElement->IsStyledByServo());
+ LOG_RESTYLE("considering element %s for eRestyle_SomeDescendants",
+ ElementTagToString(aElement).get());
+ LOG_RESTYLE_INDENT();
+
+ if (aElement->HasFlag(mRestyleTracker.RootBit())) {
+ aRestyleRoot = aElement;
+ }
+
+ if (mRestyleTracker.HasRestyleData(aElement)) {
+ nsRestyleHint rshint = eRestyle_SomeDescendants;
+ if (SelectorMatchesForRestyle(aElement)) {
+ LOG_RESTYLE("element has existing restyle data and matches a selector");
+ rshint |= eRestyle_Self;
+ } else {
+ LOG_RESTYLE("element has existing restyle data but doesn't match selectors");
+ }
+ RestyleHintData data;
+ data.mSelectorsForDescendants = mSelectorsForDescendants;
+ mRestyleTracker.AddPendingRestyle(aElement, rshint, nsChangeHint(0), &data,
+ Some(aRestyleRoot));
+ return true;
+ }
+
+ if (SelectorMatchesForRestyle(aElement)) {
+ LOG_RESTYLE("element has no restyle data but matches a selector");
+ RestyleHintData data;
+ data.mSelectorsForDescendants = mSelectorsForDescendants;
+ mRestyleTracker.AddPendingRestyle(aElement,
+ eRestyle_Self | eRestyle_SomeDescendants,
+ nsChangeHint(0), &data,
+ Some(aRestyleRoot));
+ return true;
+ }
+
+ return false;
+}
+
+bool
+ElementRestyler::MustCheckUndisplayedContent(nsIFrame* aFrame,
+ nsIContent*& aUndisplayedParent)
+{
+ // When the root element is display:none, we still construct *some*
+ // frames that have the root element as their mContent, down to the
+ // DocElementContainingBlock.
+ if (aFrame->StyleContext()->GetPseudo()) {
+ aUndisplayedParent = nullptr;
+ return aFrame == mPresContext->FrameConstructor()->
+ GetDocElementContainingBlock();
+ }
+
+ aUndisplayedParent = aFrame->GetContent();
+ return !!aUndisplayedParent;
+}
+
+/**
+ * Helper for MoveStyleContextsForChildren, below. Appends the style
+ * contexts to be moved to mFrame's current (new) style context to
+ * aContextsToMove.
+ */
+bool
+ElementRestyler::MoveStyleContextsForContentChildren(
+ nsIFrame* aParent,
+ nsStyleContext* aOldContext,
+ nsTArray<nsStyleContext*>& aContextsToMove)
+{
+ nsIFrame::ChildListIterator lists(aParent);
+ for (; !lists.IsDone(); lists.Next()) {
+ for (nsIFrame* child : lists.CurrentList()) {
+ // Bail out if we have out-of-flow frames.
+ // FIXME: It might be safe to just continue here instead of bailing out.
+ if (child->GetStateBits() & NS_FRAME_OUT_OF_FLOW) {
+ return false;
+ }
+ if (GetPrevContinuationWithSameStyle(child)) {
+ continue;
+ }
+ // Bail out if we have placeholder frames.
+ // FIXME: It is probably safe to just continue here instead of bailing out.
+ if (nsGkAtoms::placeholderFrame == child->GetType()) {
+ return false;
+ }
+ nsStyleContext* sc = child->StyleContext();
+ if (sc->GetParent() != aOldContext) {
+ return false;
+ }
+ nsIAtom* type = child->GetType();
+ if (type == nsGkAtoms::letterFrame ||
+ type == nsGkAtoms::lineFrame) {
+ return false;
+ }
+ if (sc->HasChildThatUsesGrandancestorStyle()) {
+ // XXX Not sure if we need this?
+ return false;
+ }
+ nsIAtom* pseudoTag = sc->GetPseudo();
+ if (pseudoTag && !nsCSSAnonBoxes::IsNonElement(pseudoTag)) {
+ return false;
+ }
+ aContextsToMove.AppendElement(sc);
+ }
+ }
+ return true;
+}
+
+/**
+ * Traverses to child elements (through the current frame's same style
+ * continuations, just like RestyleChildren does) and moves any style context
+ * for those children to be parented under mFrame's current (new) style
+ * context.
+ *
+ * False is returned if it encounters any conditions on the child elements'
+ * frames and style contexts that means it is impossible to move a
+ * style context. If false is returned, no style contexts will have been
+ * moved.
+ */
+bool
+ElementRestyler::MoveStyleContextsForChildren(nsStyleContext* aOldContext)
+{
+ // Bail out if there are undisplayed or display:contents children.
+ // FIXME: We could get this to work if we need to.
+ nsIContent* undisplayedParent;
+ if (MustCheckUndisplayedContent(mFrame, undisplayedParent)) {
+ nsCSSFrameConstructor* fc = mPresContext->FrameConstructor();
+ if (fc->GetAllUndisplayedContentIn(undisplayedParent) ||
+ fc->GetAllDisplayContentsIn(undisplayedParent)) {
+ return false;
+ }
+ }
+
+ nsTArray<nsStyleContext*> contextsToMove;
+
+ MOZ_ASSERT(!MustReframeForBeforePseudo(),
+ "shouldn't need to reframe ::before as we would have had "
+ "eRestyle_Subtree and wouldn't get in here");
+
+ DebugOnly<nsIFrame*> lastContinuation;
+ for (nsIFrame* f = mFrame; f;
+ f = RestyleManager::GetNextContinuationWithSameStyle(f, f->StyleContext())) {
+ lastContinuation = f;
+ if (!MoveStyleContextsForContentChildren(f, aOldContext, contextsToMove)) {
+ return false;
+ }
+ }
+
+ MOZ_ASSERT(!MustReframeForAfterPseudo(lastContinuation),
+ "shouldn't need to reframe ::after as we would have had "
+ "eRestyle_Subtree and wouldn't get in here");
+
+ nsStyleContext* newParent = mFrame->StyleContext();
+ for (nsStyleContext* child : contextsToMove) {
+ // We can have duplicate entries in contextsToMove, so only move
+ // each style context once.
+ if (child->GetParent() != newParent) {
+ child->MoveTo(newParent);
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Recompute style for mFrame (which should not have a prev continuation
+ * with the same style), all of its next continuations with the same
+ * style, and all ib-split siblings of the same type (either block or
+ * inline, skipping the intermediates of the other type) and accumulate
+ * changes into mChangeList given that mHintsHandled is already accumulated
+ * for an ancestor.
+ * mParentContent is the content node used to resolve the parent style
+ * context. This means that, for pseudo-elements, it is the content
+ * that should be used for selector matching (rather than the fake
+ * content node attached to the frame).
+ */
+void
+ElementRestyler::Restyle(nsRestyleHint aRestyleHint)
+{
+ // It would be nice if we could make stronger assertions here; they
+ // would let us simplify the ?: expressions below setting |content|
+ // and |pseudoContent| in sensible ways as well as making what
+ // |content| and |pseudoContent| mean, and their relationship to
+ // |mFrame->GetContent()|, make more sense. However, we can't,
+ // because of frame trees like the one in
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=472353#c14 . Once we
+ // fix bug 242277 we should be able to make this make more sense.
+ NS_ASSERTION(mFrame->GetContent() || !mParentContent ||
+ !mParentContent->GetParent(),
+ "frame must have content (unless at the top of the tree)");
+ MOZ_ASSERT(mPresContext == mFrame->PresContext(), "pres contexts match");
+
+ NS_ASSERTION(!GetPrevContinuationWithSameStyle(mFrame),
+ "should not be trying to restyle this frame separately");
+
+ MOZ_ASSERT(!(aRestyleHint & eRestyle_LaterSiblings),
+ "eRestyle_LaterSiblings must not be part of aRestyleHint");
+
+ mPresContext->RestyledElement();
+
+ AutoDisplayContentsAncestorPusher adcp(mTreeMatchContext, mPresContext,
+ mFrame->GetContent() ? mFrame->GetContent()->GetParent() : nullptr);
+
+ AutoSelectorArrayTruncater asat(mSelectorsForDescendants);
+
+ // List of descendant elements of mContent we know we will eventually need to
+ // restyle. Before we return from this function, we call
+ // RestyleTracker::AddRestyleRootsIfAwaitingRestyle to ensure they get
+ // restyled in RestyleTracker::DoProcessRestyles.
+ nsTArray<RefPtr<Element>> descendants;
+
+ nsRestyleHint hintToRestore = nsRestyleHint(0);
+ RestyleHintData hintDataToRestore;
+ if (mContent && mContent->IsElement() &&
+ // If we're resolving from the root of the frame tree (which
+ // we do when mDoRebuildAllStyleData), we need to avoid getting the
+ // root's restyle data until we get to its primary frame, since
+ // it's the primary frame that has the styles for the root element
+ // (rather than the ancestors of the primary frame whose mContent
+ // is the root node but which have different styles). If we use
+ // up the hint for one of the ancestors that we hit first, then
+ // we'll fail to do the restyling we need to do.
+ // Likewise, if we're restyling something with two nested frames,
+ // and we post a restyle from the transition manager while
+ // computing style for the outer frame (to be computed after the
+ // descendants have been resolved), we don't want to consume it
+ // for the inner frame.
+ mContent->GetPrimaryFrame() == mFrame) {
+ mContent->OwnerDoc()->FlushPendingLinkUpdates();
+ nsAutoPtr<RestyleTracker::RestyleData> restyleData;
+ if (mRestyleTracker.GetRestyleData(mContent->AsElement(), restyleData)) {
+ if (!NS_IsHintSubset(restyleData->mChangeHint, mHintsHandled)) {
+ mHintsHandled |= restyleData->mChangeHint;
+ mChangeList->AppendChange(mFrame, mContent, restyleData->mChangeHint);
+ }
+ mSelectorsForDescendants.AppendElements(
+ restyleData->mRestyleHintData.mSelectorsForDescendants);
+ hintToRestore = restyleData->mRestyleHint;
+ hintDataToRestore = Move(restyleData->mRestyleHintData);
+ aRestyleHint = nsRestyleHint(aRestyleHint | restyleData->mRestyleHint);
+ descendants.SwapElements(restyleData->mDescendants);
+ }
+ }
+
+ // If we are restyling this frame with eRestyle_Self or weaker hints,
+ // we restyle children with nsRestyleHint(0). But we pass the
+ // eRestyle_ForceDescendants flag down too.
+ nsRestyleHint childRestyleHint =
+ nsRestyleHint(aRestyleHint & (eRestyle_SomeDescendants |
+ eRestyle_Subtree |
+ eRestyle_ForceDescendants));
+
+ RefPtr<nsStyleContext> oldContext = mFrame->StyleContext();
+
+ nsTArray<SwapInstruction> swaps;
+
+ // TEMPORARY (until bug 918064): Call RestyleSelf for each
+ // continuation or block-in-inline sibling.
+
+ // We must make a single decision on how to process this frame and
+ // its descendants, yet RestyleSelf might return different RestyleResult
+ // values for the different same-style continuations. |result| is our
+ // overall decision.
+ RestyleResult result = RestyleResult::eNone;
+ uint32_t swappedStructs = 0;
+
+ nsRestyleHint thisRestyleHint = aRestyleHint;
+
+ bool haveMoreContinuations = false;
+ for (nsIFrame* f = mFrame; f; ) {
+ RestyleResult thisResult =
+ RestyleSelf(f, thisRestyleHint, &swappedStructs, swaps);
+
+ if (thisResult != RestyleResult::eStop) {
+ // Calls to RestyleSelf for later same-style continuations must not
+ // return RestyleResult::eStop, so pass eRestyle_Force in to them.
+ thisRestyleHint = nsRestyleHint(thisRestyleHint | eRestyle_Force);
+
+ if (result == RestyleResult::eStop) {
+ // We received RestyleResult::eStop for earlier same-style
+ // continuations, and RestyleResult::eStopWithStyleChange or
+ // RestyleResult::eContinue(AndForceDescendants) for this one; go
+ // back and force-restyle the earlier continuations.
+ result = thisResult;
+ f = mFrame;
+ continue;
+ }
+ }
+
+ if (thisResult > result) {
+ // We take the highest RestyleResult value when working out what to do
+ // with this frame and its descendants. Higher RestyleResult values
+ // represent a superset of the work done by lower values.
+ result = thisResult;
+ }
+
+ f = RestyleManager::GetNextContinuationWithSameStyle(f,
+ oldContext,
+ &haveMoreContinuations);
+ }
+
+ // Some changes to animations don't affect the computed style and yet still
+ // require the layer to be updated. For example, pausing an animation via
+ // the Web Animations API won't affect an element's style but still
+ // requires us to pull the animation off the layer.
+ //
+ // Although we only expect this code path to be called when computed style
+ // is not changing, we can sometimes reach this at the end of a transition
+ // when the animated style is being removed. Since
+ // AddLayerChangesForAnimation checks if mFrame has a transform style or not,
+ // we need to call it *after* calling RestyleSelf to ensure the animated
+ // transform has been removed first.
+ AddLayerChangesForAnimation();
+
+ if (haveMoreContinuations && hintToRestore) {
+ // If we have more continuations with different style (e.g., because
+ // we're inside a ::first-letter or ::first-line), put the restyle
+ // hint back.
+ mRestyleTracker.AddPendingRestyleToTable(mContent->AsElement(),
+ hintToRestore, nsChangeHint(0));
+ }
+
+ if (result == RestyleResult::eStop) {
+ MOZ_ASSERT(mFrame->StyleContext() == oldContext,
+ "frame should have been left with its old style context");
+
+ nsIFrame* unused;
+ nsStyleContext* newParent = mFrame->GetParentStyleContext(&unused);
+ if (oldContext->GetParent() != newParent) {
+ // If we received RestyleResult::eStop, then the old style context was
+ // left on mFrame. Since we ended up restyling our parent, change
+ // this old style context to point to its new parent.
+ LOG_RESTYLE("moving style context %p from old parent %p to new parent %p",
+ oldContext.get(), oldContext->GetParent(), newParent);
+ // We keep strong references to the new parent around until the end
+ // of the restyle, in case:
+ // (a) we swapped structs between the old and new parent,
+ // (b) some descendants of the old parent are not getting restyled
+ // (which is the reason for the existence of
+ // ClearCachedInheritedStyleDataOnDescendants),
+ // (c) something under ProcessPendingRestyles (which notably is called
+ // *before* ClearCachedInheritedStyleDataOnDescendants is called
+ // on the old context) causes the new parent to be destroyed, thus
+ // destroying its owned structs, and
+ // (d) something under ProcessPendingRestyles then wants to use of those
+ // now destroyed structs (through the old parent's descendants).
+ mSwappedStructOwners.AppendElement(newParent);
+ oldContext->MoveTo(newParent);
+ }
+
+ // Send the accessibility notifications that RestyleChildren otherwise
+ // would have sent.
+ if (!(mHintsHandled & nsChangeHint_ReconstructFrame)) {
+ InitializeAccessibilityNotifications(mFrame->StyleContext());
+ SendAccessibilityNotifications();
+ }
+
+ mRestyleTracker.AddRestyleRootsIfAwaitingRestyle(descendants);
+ if (aRestyleHint & eRestyle_SomeDescendants) {
+ ConditionallyRestyleChildren();
+ }
+ return;
+ }
+
+ if (result == RestyleResult::eStopWithStyleChange &&
+ !(mHintsHandled & nsChangeHint_ReconstructFrame)) {
+ MOZ_ASSERT(mFrame->StyleContext() != oldContext,
+ "RestyleResult::eStopWithStyleChange should only be returned "
+ "if we got a new style context or we will reconstruct");
+ MOZ_ASSERT(swappedStructs == 0,
+ "should have ensured we didn't swap structs when "
+ "returning RestyleResult::eStopWithStyleChange");
+
+ // We need to ensure that all of the frames that inherit their style
+ // from oldContext are able to be moved across to newContext.
+ // MoveStyleContextsForChildren will check for certain conditions
+ // to ensure it is safe to move all of the relevant child style
+ // contexts to newContext. If these conditions fail, it will
+ // return false, and we'll have to continue restyling.
+ const bool canStop = MoveStyleContextsForChildren(oldContext);
+
+ if (canStop) {
+ // Send the accessibility notifications that RestyleChildren otherwise
+ // would have sent.
+ if (!(mHintsHandled & nsChangeHint_ReconstructFrame)) {
+ InitializeAccessibilityNotifications(mFrame->StyleContext());
+ SendAccessibilityNotifications();
+ }
+
+ mRestyleTracker.AddRestyleRootsIfAwaitingRestyle(descendants);
+ if (aRestyleHint & eRestyle_SomeDescendants) {
+ ConditionallyRestyleChildren();
+ }
+ return;
+ }
+
+ // Turns out we couldn't stop restyling here. Process the struct
+ // swaps that RestyleSelf would've done had we not returned
+ // RestyleResult::eStopWithStyleChange.
+ for (SwapInstruction& swap : swaps) {
+ LOG_RESTYLE("swapping style structs between %p and %p",
+ swap.mOldContext.get(), swap.mNewContext.get());
+ swap.mOldContext->SwapStyleData(swap.mNewContext, swap.mStructsToSwap);
+ swappedStructs |= swap.mStructsToSwap;
+ }
+ swaps.Clear();
+ }
+
+ if (!swappedStructs) {
+ // If we swapped any structs from the old context, then we need to keep
+ // it alive until after the RestyleChildren call so that we can fix up
+ // its descendants' cached structs.
+ oldContext = nullptr;
+ }
+
+ if (result == RestyleResult::eContinueAndForceDescendants) {
+ childRestyleHint =
+ nsRestyleHint(childRestyleHint | eRestyle_ForceDescendants);
+ }
+
+ // No need to do this if we're planning to reframe already.
+ // It's also important to check mHintsHandled since we use
+ // mFrame->StyleContext(), which is out of date if mHintsHandled
+ // has a ReconstructFrame hint. Using an out of date style
+ // context could trigger assertions about mismatched rule trees.
+ if (!(mHintsHandled & nsChangeHint_ReconstructFrame)) {
+ RestyleChildren(childRestyleHint);
+ }
+
+ if (oldContext && !oldContext->HasSingleReference()) {
+ // If we swapped some structs out of oldContext in the RestyleSelf call
+ // and after the RestyleChildren call we still have other strong references
+ // to it, we need to make ensure its descendants don't cache any of the
+ // structs that were swapped out.
+ //
+ // Much of the time we will not get in here; we do for example when the
+ // style context is shared with a later IB split sibling (which we won't
+ // restyle until a bit later) or if other code is holding a strong reference
+ // to the style context (as is done by nsTransformedTextRun objects, which
+ // can be referenced by a text frame's mTextRun longer than the frame's
+ // mStyleContext).
+ //
+ // Also, we don't want this style context to get any more uses by being
+ // returned from nsStyleContext::FindChildWithRules, so we add the
+ // NS_STYLE_INELIGIBLE_FOR_SHARING bit to it.
+ oldContext->SetIneligibleForSharing();
+
+ ContextToClear* toClear = mContextsToClear.AppendElement();
+ toClear->mStyleContext = Move(oldContext);
+ toClear->mStructs = swappedStructs;
+ }
+
+ mRestyleTracker.AddRestyleRootsIfAwaitingRestyle(descendants);
+}
+
+/**
+ * Depending on the details of the frame we are restyling or its old style
+ * context, we may or may not be able to stop restyling after this frame if
+ * we find we had no style changes.
+ *
+ * This function returns RestyleResult::eStop if it does not find any
+ * conditions that would preclude stopping restyling, and
+ * RestyleResult::eContinue if it does.
+ */
+void
+ElementRestyler::ComputeRestyleResultFromFrame(nsIFrame* aSelf,
+ RestyleResult& aRestyleResult,
+ bool& aCanStopWithStyleChange)
+{
+ // We can't handle situations where the primary style context of a frame
+ // has not had any style data changes, but its additional style contexts
+ // have, so we don't considering stopping if this frame has any additional
+ // style contexts.
+ if (aSelf->GetAdditionalStyleContext(0)) {
+ LOG_RESTYLE_CONTINUE("there are additional style contexts");
+ aRestyleResult = RestyleResult::eContinue;
+ aCanStopWithStyleChange = false;
+ return;
+ }
+
+ // Style changes might have moved children between the two nsLetterFrames
+ // (the one matching ::first-letter and the one containing the rest of the
+ // content). Continue restyling to the children of the nsLetterFrame so
+ // that they get the correct style context parent. Similarly for
+ // nsLineFrames.
+ nsIAtom* type = aSelf->GetType();
+
+ if (type == nsGkAtoms::letterFrame) {
+ LOG_RESTYLE_CONTINUE("frame is a letter frame");
+ aRestyleResult = RestyleResult::eContinue;
+ aCanStopWithStyleChange = false;
+ return;
+ }
+
+ if (type == nsGkAtoms::lineFrame) {
+ LOG_RESTYLE_CONTINUE("frame is a line frame");
+ aRestyleResult = RestyleResult::eContinue;
+ aCanStopWithStyleChange = false;
+ return;
+ }
+
+ // Some style computations depend not on the parent's style, but a grandparent
+ // or one the grandparent's ancestors. An example is an explicit 'inherit'
+ // value for align-self, where if the parent frame's value for the property is
+ // 'auto' we end up inheriting the computed value from the grandparent. We
+ // can't stop the restyling process on this frame (the one with 'auto', in
+ // this example), as the grandparent's computed value might have changed
+ // and we need to recompute the child's 'inherit' to that new value.
+ nsStyleContext* oldContext = aSelf->StyleContext();
+ if (oldContext->HasChildThatUsesGrandancestorStyle()) {
+ LOG_RESTYLE_CONTINUE("the old context uses grandancestor style");
+ aRestyleResult = RestyleResult::eContinue;
+ aCanStopWithStyleChange = false;
+ return;
+ }
+
+ // We ignore all situations that involve :visited style.
+ if (oldContext->GetStyleIfVisited()) {
+ LOG_RESTYLE_CONTINUE("the old style context has StyleIfVisited");
+ aRestyleResult = RestyleResult::eContinue;
+ aCanStopWithStyleChange = false;
+ return;
+ }
+
+ nsStyleContext* parentContext = oldContext->GetParent();
+ if (parentContext && parentContext->GetStyleIfVisited()) {
+ LOG_RESTYLE_CONTINUE("the old style context's parent has StyleIfVisited");
+ aRestyleResult = RestyleResult::eContinue;
+ aCanStopWithStyleChange = false;
+ return;
+ }
+
+ // We also ignore frames for pseudos, as their style contexts have
+ // inheritance structures that do not match the frame inheritance
+ // structure. To avoid enumerating and checking all of the cases
+ // where we have this kind of inheritance, we keep restyling past
+ // pseudos.
+ nsIAtom* pseudoTag = oldContext->GetPseudo();
+ if (pseudoTag && !nsCSSAnonBoxes::IsNonElement(pseudoTag)) {
+ LOG_RESTYLE_CONTINUE("the old style context is for a pseudo");
+ aRestyleResult = RestyleResult::eContinue;
+ aCanStopWithStyleChange = false;
+ return;
+ }
+
+ nsIFrame* parent = mFrame->GetParent();
+
+ if (parent) {
+ // Also if the parent has a pseudo, as this frame's style context will
+ // be inheriting from a grandparent frame's style context (or a further
+ // ancestor).
+ nsIAtom* parentPseudoTag = parent->StyleContext()->GetPseudo();
+ if (parentPseudoTag &&
+ parentPseudoTag != nsCSSAnonBoxes::mozOtherNonElement) {
+ MOZ_ASSERT(parentPseudoTag != nsCSSAnonBoxes::mozText,
+ "Style of text node should not be parent of anything");
+ LOG_RESTYLE_CONTINUE("the old style context's parent is for a pseudo");
+ aRestyleResult = RestyleResult::eContinue;
+ // Parent style context pseudo-ness doesn't affect whether we can
+ // return RestyleResult::eStopWithStyleChange.
+ //
+ // If we had later conditions to check in this function, we would
+ // continue to check them, in case we set aCanStopWithStyleChange to
+ // false.
+ }
+ }
+}
+
+void
+ElementRestyler::ComputeRestyleResultFromNewContext(nsIFrame* aSelf,
+ nsStyleContext* aNewContext,
+ RestyleResult& aRestyleResult,
+ bool& aCanStopWithStyleChange)
+{
+ // If we've already determined that we must continue styling, we don't
+ // need to check anything.
+ if (aRestyleResult == RestyleResult::eContinue && !aCanStopWithStyleChange) {
+ return;
+ }
+
+ // Keep restyling if the new style context has any style-if-visted style, so
+ // that we can avoid the style context tree surgery having to deal to deal
+ // with visited styles.
+ if (aNewContext->GetStyleIfVisited()) {
+ LOG_RESTYLE_CONTINUE("the new style context has StyleIfVisited");
+ aRestyleResult = RestyleResult::eContinue;
+ aCanStopWithStyleChange = false;
+ return;
+ }
+
+ // If link-related information has changed, or the pseudo for the frame has
+ // changed, or the new style context points to a different rule node, we can't
+ // leave the old style context on the frame.
+ nsStyleContext* oldContext = aSelf->StyleContext();
+ if (oldContext->IsLinkContext() != aNewContext->IsLinkContext() ||
+ oldContext->RelevantLinkVisited() != aNewContext->RelevantLinkVisited() ||
+ oldContext->GetPseudo() != aNewContext->GetPseudo() ||
+ oldContext->GetPseudoType() != aNewContext->GetPseudoType()) {
+ LOG_RESTYLE_CONTINUE("the old and new style contexts have different link/"
+ "visited/pseudo");
+ aRestyleResult = RestyleResult::eContinue;
+ aCanStopWithStyleChange = false;
+ return;
+ }
+
+ if (oldContext->RuleNode() != aNewContext->RuleNode()) {
+ LOG_RESTYLE_CONTINUE("the old and new style contexts have different "
+ "rulenodes");
+ aRestyleResult = RestyleResult::eContinue;
+ // Continue to check other conditions if aCanStopWithStyleChange might
+ // still need to be set to false.
+ if (!aCanStopWithStyleChange) {
+ return;
+ }
+ }
+
+ // If the old and new style contexts differ in their
+ // NS_STYLE_HAS_TEXT_DECORATION_LINES or NS_STYLE_HAS_PSEUDO_ELEMENT_DATA
+ // bits, then we must keep restyling so that those new bit values are
+ // propagated.
+ if (oldContext->HasTextDecorationLines() !=
+ aNewContext->HasTextDecorationLines()) {
+ LOG_RESTYLE_CONTINUE("NS_STYLE_HAS_TEXT_DECORATION_LINES differs between old"
+ " and new style contexts");
+ aRestyleResult = RestyleResult::eContinue;
+ aCanStopWithStyleChange = false;
+ return;
+ }
+
+ if (oldContext->HasPseudoElementData() !=
+ aNewContext->HasPseudoElementData()) {
+ LOG_RESTYLE_CONTINUE("NS_STYLE_HAS_PSEUDO_ELEMENT_DATA differs between old"
+ " and new style contexts");
+ aRestyleResult = RestyleResult::eContinue;
+ aCanStopWithStyleChange = false;
+ return;
+ }
+
+ if (oldContext->ShouldSuppressLineBreak() !=
+ aNewContext->ShouldSuppressLineBreak()) {
+ LOG_RESTYLE_CONTINUE("NS_STYLE_SUPPRESS_LINEBREAK differs"
+ "between old and new style contexts");
+ aRestyleResult = RestyleResult::eContinue;
+ aCanStopWithStyleChange = false;
+ return;
+ }
+
+ if (oldContext->IsInDisplayNoneSubtree() !=
+ aNewContext->IsInDisplayNoneSubtree()) {
+ LOG_RESTYLE_CONTINUE("NS_STYLE_IN_DISPLAY_NONE_SUBTREE differs between old"
+ " and new style contexts");
+ aRestyleResult = RestyleResult::eContinue;
+ aCanStopWithStyleChange = false;
+ return;
+ }
+
+ if (oldContext->IsTextCombined() != aNewContext->IsTextCombined()) {
+ LOG_RESTYLE_CONTINUE("NS_STYLE_IS_TEXT_COMBINED differs between "
+ "old and new style contexts");
+ aRestyleResult = RestyleResult::eContinue;
+ aCanStopWithStyleChange = false;
+ return;
+ }
+}
+
+bool
+ElementRestyler::SelectorMatchesForRestyle(Element* aElement)
+{
+ if (!aElement) {
+ return false;
+ }
+ for (nsCSSSelector* selector : mSelectorsForDescendants) {
+ if (nsCSSRuleProcessor::RestrictedSelectorMatches(aElement, selector,
+ mTreeMatchContext)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool
+ElementRestyler::MustRestyleSelf(nsRestyleHint aRestyleHint,
+ Element* aElement)
+{
+ return (aRestyleHint & (eRestyle_Self | eRestyle_Subtree)) ||
+ ((aRestyleHint & eRestyle_SomeDescendants) &&
+ SelectorMatchesForRestyle(aElement));
+}
+
+bool
+ElementRestyler::CanReparentStyleContext(nsRestyleHint aRestyleHint)
+{
+ // If we had any restyle hints other than the ones listed below,
+ // which don't control whether the current frame/element needs
+ // a new style context by looking up a new rule node, or if
+ // we are reconstructing the entire rule tree, then we can't
+ // use ReparentStyleContext.
+ return !(aRestyleHint & ~(eRestyle_Force |
+ eRestyle_ForceDescendants |
+ eRestyle_SomeDescendants)) &&
+ !StyleSet()->IsInRuleTreeReconstruct();
+}
+
+// Returns true iff any rule node that is an ancestor-or-self of the
+// two specified rule nodes, but which is not an ancestor of both,
+// has any inherited style data. If false is returned, then we know
+// that a change from one rule node to the other must not result in
+// any change in inherited style data.
+static bool
+CommonInheritedStyleData(nsRuleNode* aRuleNode1, nsRuleNode* aRuleNode2)
+{
+ if (aRuleNode1 == aRuleNode2) {
+ return true;
+ }
+
+ nsRuleNode* n1 = aRuleNode1->GetParent();
+ nsRuleNode* n2 = aRuleNode2->GetParent();
+
+ if (n1 == n2) {
+ // aRuleNode1 and aRuleNode2 sharing a parent is a common case, e.g.
+ // when modifying a style="" attribute. (We must null check GetRule()'s
+ // result since although we know the two parents are the same, it might
+ // be null, as in the case of the two rule nodes being roots of two
+ // different rule trees.)
+ if (aRuleNode1->GetRule() &&
+ aRuleNode1->GetRule()->MightMapInheritedStyleData()) {
+ return false;
+ }
+ if (aRuleNode2->GetRule() &&
+ aRuleNode2->GetRule()->MightMapInheritedStyleData()) {
+ return false;
+ }
+ return true;
+ }
+
+ // Compute the depths of aRuleNode1 and aRuleNode2.
+ int d1 = 0, d2 = 0;
+ while (n1) {
+ ++d1;
+ n1 = n1->GetParent();
+ }
+ while (n2) {
+ ++d2;
+ n2 = n2->GetParent();
+ }
+
+ // Make aRuleNode1 be the deeper node.
+ if (d2 > d1) {
+ std::swap(d1, d2);
+ std::swap(aRuleNode1, aRuleNode2);
+ }
+
+ // Check all of the rule nodes in the deeper branch until we reach
+ // the same depth as the shallower branch.
+ n1 = aRuleNode1;
+ n2 = aRuleNode2;
+ while (d1 > d2) {
+ nsIStyleRule* rule = n1->GetRule();
+ MOZ_ASSERT(rule, "non-root rule node should have a rule");
+ if (rule->MightMapInheritedStyleData()) {
+ return false;
+ }
+ n1 = n1->GetParent();
+ --d1;
+ }
+
+ // Check both branches simultaneously until we reach a common ancestor.
+ while (n1 != n2) {
+ MOZ_ASSERT(n1);
+ MOZ_ASSERT(n2);
+ // As above, we must null check GetRule()'s result since we won't find
+ // a common ancestor if the two rule nodes come from different rule trees,
+ // and thus we might reach the root (which has a null rule).
+ if (n1->GetRule() && n1->GetRule()->MightMapInheritedStyleData()) {
+ return false;
+ }
+ if (n2->GetRule() && n2->GetRule()->MightMapInheritedStyleData()) {
+ return false;
+ }
+ n1 = n1->GetParent();
+ n2 = n2->GetParent();
+ }
+
+ return true;
+}
+
+ElementRestyler::RestyleResult
+ElementRestyler::RestyleSelf(nsIFrame* aSelf,
+ nsRestyleHint aRestyleHint,
+ uint32_t* aSwappedStructs,
+ nsTArray<SwapInstruction>& aSwaps)
+{
+ MOZ_ASSERT(!(aRestyleHint & eRestyle_LaterSiblings),
+ "eRestyle_LaterSiblings must not be part of aRestyleHint");
+
+ // XXXldb get new context from prev-in-flow if possible, to avoid
+ // duplication. (Or should we just let |GetContext| handle that?)
+ // Getting the hint would be nice too, but that's harder.
+
+ // XXXbryner we may be able to avoid some of the refcounting goop here.
+ // We do need a reference to oldContext for the lifetime of this function, and it's possible
+ // that the frame has the last reference to it, so AddRef it here.
+
+ LOG_RESTYLE("RestyleSelf %s, aRestyleHint = %s",
+ FrameTagToString(aSelf).get(),
+ RestyleManagerBase::RestyleHintToString(aRestyleHint).get());
+ LOG_RESTYLE_INDENT();
+
+ // Initially assume that it is safe to stop restyling.
+ //
+ // Throughout most of this function, we update the following two variables
+ // independently. |result| is set to RestyleResult::eContinue when we
+ // detect a condition that would not allow us to return RestyleResult::eStop.
+ // |canStopWithStyleChange| is set to false when we detect a condition
+ // that would not allow us to return RestyleResult::eStopWithStyleChange.
+ //
+ // Towards the end of this function, we reconcile these two variables --
+ // if |canStopWithStyleChange| is true, we convert |result| into
+ // RestyleResult::eStopWithStyleChange.
+ RestyleResult result = RestyleResult::eStop;
+ bool canStopWithStyleChange = true;
+
+ if (aRestyleHint & ~eRestyle_SomeDescendants) {
+ // If we are doing any restyling of the current element, or if we're
+ // forced to continue, we must.
+ result = RestyleResult::eContinue;
+
+ // If we have to restyle children, we can't return
+ // RestyleResult::eStopWithStyleChange.
+ if (aRestyleHint & (eRestyle_Subtree | eRestyle_Force |
+ eRestyle_ForceDescendants)) {
+ canStopWithStyleChange = false;
+ }
+ }
+
+ // We only consider returning RestyleResult::eStopWithStyleChange if this
+ // is the root of the restyle. (Otherwise, we would need to track the
+ // style changes of the ancestors we just restyled.)
+ if (!mIsRootOfRestyle) {
+ canStopWithStyleChange = false;
+ }
+
+ // Look at the frame and its current style context for conditions
+ // that would change our RestyleResult.
+ ComputeRestyleResultFromFrame(aSelf, result, canStopWithStyleChange);
+
+ nsChangeHint assumeDifferenceHint = nsChangeHint(0);
+ RefPtr<nsStyleContext> oldContext = aSelf->StyleContext();
+ nsStyleSet* styleSet = StyleSet();
+
+#ifdef ACCESSIBILITY
+ mWasFrameVisible = nsIPresShell::IsAccessibilityActive() ?
+ oldContext->StyleVisibility()->IsVisible() : false;
+#endif
+
+ nsIAtom* const pseudoTag = oldContext->GetPseudo();
+ const CSSPseudoElementType pseudoType = oldContext->GetPseudoType();
+
+ // Get the frame providing the parent style context. If it is a
+ // child, then resolve the provider first.
+ nsIFrame* providerFrame;
+ nsStyleContext* parentContext = aSelf->GetParentStyleContext(&providerFrame);
+ bool isChild = providerFrame && providerFrame->GetParent() == aSelf;
+ if (isChild) {
+ MOZ_ASSERT(providerFrame->GetContent() == aSelf->GetContent(),
+ "Postcondition for GetParentStyleContext() violated. "
+ "That means we need to add the current element to the "
+ "ancestor filter.");
+
+ // resolve the provider here (before aSelf below).
+ LOG_RESTYLE("resolving child provider frame");
+
+ // assumeDifferenceHint forces the parent's change to be also
+ // applied to this frame, no matter what
+ // nsStyleContext::CalcStyleDifference says. CalcStyleDifference
+ // can't be trusted because it assumes any changes to the parent
+ // style context provider will be automatically propagated to
+ // the frame(s) with child style contexts.
+
+ ElementRestyler providerRestyler(PARENT_CONTEXT_FROM_CHILD_FRAME,
+ *this, providerFrame);
+ providerRestyler.Restyle(aRestyleHint);
+ assumeDifferenceHint = providerRestyler.HintsHandledForFrame();
+
+ // The provider's new context becomes the parent context of
+ // aSelf's context.
+ parentContext = providerFrame->StyleContext();
+ // Set |mResolvedChild| so we don't bother resolving the
+ // provider again.
+ mResolvedChild = providerFrame;
+ LOG_RESTYLE_CONTINUE("we had a provider frame");
+ // Continue restyling past the odd style context inheritance.
+ result = RestyleResult::eContinue;
+ canStopWithStyleChange = false;
+ }
+
+ if (providerFrame != aSelf->GetParent()) {
+ // We don't actually know what the parent style context's
+ // non-inherited hints were, so assume the worst.
+ mParentFrameHintsNotHandledForDescendants =
+ nsChangeHint_Hints_NotHandledForDescendants;
+ }
+
+ LOG_RESTYLE("parentContext = %p", parentContext);
+
+ // do primary context
+ RefPtr<nsStyleContext> newContext;
+ nsIFrame* prevContinuation =
+ GetPrevContinuationWithPossiblySameStyle(aSelf);
+ nsStyleContext* prevContinuationContext;
+ bool copyFromContinuation =
+ prevContinuation &&
+ (prevContinuationContext = prevContinuation->StyleContext())
+ ->GetPseudo() == oldContext->GetPseudo() &&
+ prevContinuationContext->GetParent() == parentContext;
+ if (copyFromContinuation) {
+ // Just use the style context from the frame's previous
+ // continuation.
+ LOG_RESTYLE("using previous continuation's context");
+ newContext = prevContinuationContext;
+ } else if (pseudoTag == nsCSSAnonBoxes::mozText) {
+ MOZ_ASSERT(aSelf->GetType() == nsGkAtoms::textFrame);
+ newContext =
+ styleSet->ResolveStyleForText(aSelf->GetContent(), parentContext);
+ } else if (nsCSSAnonBoxes::IsNonElement(pseudoTag)) {
+ newContext = styleSet->ResolveStyleForOtherNonElement(parentContext);
+ }
+ else {
+ Element* element = ElementForStyleContext(mParentContent, aSelf, pseudoType);
+ if (!MustRestyleSelf(aRestyleHint, element)) {
+ if (CanReparentStyleContext(aRestyleHint)) {
+ LOG_RESTYLE("reparenting style context");
+ newContext =
+ styleSet->ReparentStyleContext(oldContext, parentContext, element);
+ } else {
+ // Use ResolveStyleWithReplacement either for actual replacements
+ // or, with no replacements, as a substitute for
+ // ReparentStyleContext that rebuilds the path in the rule tree
+ // rather than reusing the rule node, as we need to do during a
+ // rule tree reconstruct.
+ Element* pseudoElement = PseudoElementForStyleContext(aSelf, pseudoType);
+ MOZ_ASSERT(!element || element != pseudoElement,
+ "pseudo-element for selector matching should be "
+ "the anonymous content node that we create, "
+ "not the real element");
+ LOG_RESTYLE("resolving style with replacement");
+ nsRestyleHint rshint = aRestyleHint & ~eRestyle_SomeDescendants;
+ newContext =
+ styleSet->ResolveStyleWithReplacement(element, pseudoElement,
+ parentContext, oldContext,
+ rshint);
+ }
+ } else if (pseudoType == CSSPseudoElementType::AnonBox) {
+ newContext = styleSet->ResolveAnonymousBoxStyle(pseudoTag,
+ parentContext);
+ }
+ else {
+ if (pseudoTag) {
+ if (pseudoTag == nsCSSPseudoElements::before ||
+ pseudoTag == nsCSSPseudoElements::after) {
+ // XXX what other pseudos do we need to treat like this?
+ newContext = styleSet->ProbePseudoElementStyle(element,
+ pseudoType,
+ parentContext,
+ mTreeMatchContext);
+ if (!newContext) {
+ // This pseudo should no longer exist; gotta reframe
+ mHintsHandled |= nsChangeHint_ReconstructFrame;
+ mChangeList->AppendChange(aSelf, element,
+ nsChangeHint_ReconstructFrame);
+ // We're reframing anyway; just keep the same context
+ newContext = oldContext;
+#ifdef DEBUG
+ // oldContext's parent might have had its style structs swapped out
+ // with parentContext, so to avoid any assertions that might
+ // otherwise trigger in oldContext's parent's destructor, we set a
+ // flag on oldContext to skip it and its descendants in
+ // nsStyleContext::AssertStructsNotUsedElsewhere.
+ if (oldContext->GetParent() != parentContext) {
+ oldContext->AddStyleBit(NS_STYLE_IS_GOING_AWAY);
+ }
+#endif
+ }
+ } else {
+ // Don't expect XUL tree stuff here, since it needs a comparator and
+ // all.
+ NS_ASSERTION(pseudoType < CSSPseudoElementType::Count,
+ "Unexpected pseudo type");
+ Element* pseudoElement =
+ PseudoElementForStyleContext(aSelf, pseudoType);
+ MOZ_ASSERT(element != pseudoElement,
+ "pseudo-element for selector matching should be "
+ "the anonymous content node that we create, "
+ "not the real element");
+ newContext = styleSet->ResolvePseudoElementStyle(element,
+ pseudoType,
+ parentContext,
+ pseudoElement);
+ }
+ }
+ else {
+ NS_ASSERTION(aSelf->GetContent(),
+ "non pseudo-element frame without content node");
+ // Skip parent display based style fixup for anonymous subtrees:
+ TreeMatchContext::AutoParentDisplayBasedStyleFixupSkipper
+ parentDisplayBasedFixupSkipper(mTreeMatchContext,
+ element->IsRootOfNativeAnonymousSubtree());
+ newContext = styleSet->ResolveStyleFor(element, parentContext,
+ mTreeMatchContext);
+ }
+ }
+ }
+
+ MOZ_ASSERT(newContext);
+
+ if (!parentContext) {
+ if (oldContext->RuleNode() == newContext->RuleNode() &&
+ oldContext->IsLinkContext() == newContext->IsLinkContext() &&
+ oldContext->RelevantLinkVisited() ==
+ newContext->RelevantLinkVisited()) {
+ // We're the root of the style context tree and the new style
+ // context returned has the same rule node. This means that
+ // we can use FindChildWithRules to keep a lot of the old
+ // style contexts around. However, we need to start from the
+ // same root.
+ LOG_RESTYLE("restyling root and keeping old context");
+ LOG_RESTYLE_IF(this, result != RestyleResult::eContinue,
+ "continuing restyle since this is the root");
+ newContext = oldContext;
+ // Never consider stopping restyling at the root.
+ result = RestyleResult::eContinue;
+ canStopWithStyleChange = false;
+ }
+ }
+
+ LOG_RESTYLE("oldContext = %p, newContext = %p%s",
+ oldContext.get(), newContext.get(),
+ oldContext == newContext ? (const char*) " (same)" :
+ (const char*) "");
+
+ if (newContext != oldContext) {
+ if (oldContext->IsShared()) {
+ // If the old style context was shared, then we can't return
+ // RestyleResult::eStop and patch its parent to point to the
+ // new parent style context, as that change might not be valid
+ // for the other frames sharing the style context.
+ LOG_RESTYLE_CONTINUE("the old style context is shared");
+ result = RestyleResult::eContinue;
+
+ // It is not safe to return RestyleResult::eStopWithStyleChange
+ // when oldContext is shared and newContext has different
+ // inherited style data, regardless of whether the oldContext has
+ // that inherited style data cached. We can't simply rely on the
+ // samePointerStructs check later on, as the descendent style
+ // contexts just might not have had their inherited style data
+ // requested yet (which is possible for example if we flush style
+ // between resolving an initial style context for a frame and
+ // building its display list items). Therefore we must compare
+ // the rule nodes of oldContext and newContext to see if the
+ // restyle results in new inherited style data. If not, then
+ // we can continue assuming that RestyleResult::eStopWithStyleChange
+ // is safe. Without this check, we could end up with style contexts
+ // shared between elements which should have different styles.
+ if (!CommonInheritedStyleData(oldContext->RuleNode(),
+ newContext->RuleNode())) {
+ canStopWithStyleChange = false;
+ }
+ }
+
+ // Look at some details of the new style context to see if it would
+ // be safe to stop restyling, if we discover it has the same style
+ // data as the old style context.
+ ComputeRestyleResultFromNewContext(aSelf, newContext,
+ result, canStopWithStyleChange);
+
+ uint32_t equalStructs = 0;
+ uint32_t samePointerStructs = 0;
+
+ if (copyFromContinuation) {
+ // In theory we should know whether there was any style data difference,
+ // since we would have calculated that in the previous call to
+ // RestyleSelf, so until we perform only one restyling per chain-of-
+ // same-style continuations (bug 918064), we need to check again here to
+ // determine whether it is safe to stop restyling.
+ if (result == RestyleResult::eStop) {
+ oldContext->CalcStyleDifference(newContext, nsChangeHint(0),
+ &equalStructs,
+ &samePointerStructs);
+ if (equalStructs != NS_STYLE_INHERIT_MASK) {
+ // At least one struct had different data in it, so we must
+ // continue restyling children.
+ LOG_RESTYLE_CONTINUE("there is different style data: %s",
+ RestyleManager::StructNamesToString(
+ ~equalStructs & NS_STYLE_INHERIT_MASK).get());
+ result = RestyleResult::eContinue;
+ }
+ }
+ } else {
+ bool changedStyle =
+ RestyleManager::TryInitiatingTransition(mPresContext,
+ aSelf->GetContent(),
+ oldContext, &newContext);
+ if (changedStyle) {
+ LOG_RESTYLE_CONTINUE("TryInitiatingTransition changed the new style "
+ "context");
+ result = RestyleResult::eContinue;
+ canStopWithStyleChange = false;
+ }
+ CaptureChange(oldContext, newContext, assumeDifferenceHint,
+ &equalStructs, &samePointerStructs);
+ if (equalStructs != NS_STYLE_INHERIT_MASK) {
+ // At least one struct had different data in it, so we must
+ // continue restyling children.
+ LOG_RESTYLE_CONTINUE("there is different style data: %s",
+ RestyleManager::StructNamesToString(
+ ~equalStructs & NS_STYLE_INHERIT_MASK).get());
+ result = RestyleResult::eContinue;
+ }
+ }
+
+ if (canStopWithStyleChange) {
+ // If any inherited struct pointers are different, or if any
+ // reset struct pointers are different and we have descendants
+ // that rely on those reset struct pointers, we can't return
+ // RestyleResult::eStopWithStyleChange.
+ if ((samePointerStructs & NS_STYLE_INHERITED_STRUCT_MASK) !=
+ NS_STYLE_INHERITED_STRUCT_MASK) {
+ LOG_RESTYLE("can't return RestyleResult::eStopWithStyleChange since "
+ "there is different inherited data");
+ canStopWithStyleChange = false;
+ } else if ((samePointerStructs & NS_STYLE_RESET_STRUCT_MASK) !=
+ NS_STYLE_RESET_STRUCT_MASK &&
+ oldContext->HasChildThatUsesResetStyle()) {
+ LOG_RESTYLE("can't return RestyleResult::eStopWithStyleChange since "
+ "there is different reset data and descendants use it");
+ canStopWithStyleChange = false;
+ }
+ }
+
+ if (result == RestyleResult::eStop) {
+ // Since we currently have RestyleResult::eStop, we know at this
+ // point that all of our style structs are equal in terms of styles.
+ // However, some of them might be different pointers. Since our
+ // descendants might share those pointers, we have to continue to
+ // restyling our descendants.
+ //
+ // However, because of the swapping of equal structs we've done on
+ // ancestors (later in this function), we've ensured that for structs
+ // that cannot be stored in the rule tree, we keep the old equal structs
+ // around rather than replacing them with new ones. This means that we
+ // only time we hit this deoptimization is either
+ //
+ // (a) when at least one of the (old or new) equal structs could be stored
+ // in the rule tree, and those structs are then inherited (by pointer
+ // sharing) to descendant style contexts; or
+ //
+ // (b) when we were unable to swap the structs on the parent because
+ // either or both of the old parent and new parent are shared.
+ //
+ // FIXME This loop could be rewritten as bit operations on
+ // oldContext->mBits and samePointerStructs.
+ for (nsStyleStructID sid = nsStyleStructID(0);
+ sid < nsStyleStructID_Length;
+ sid = nsStyleStructID(sid + 1)) {
+ if (oldContext->HasCachedDependentStyleData(sid) &&
+ !(samePointerStructs & nsCachedStyleData::GetBitForSID(sid))) {
+ LOG_RESTYLE_CONTINUE("there are different struct pointers");
+ result = RestyleResult::eContinue;
+ break;
+ }
+ }
+ }
+
+ // From this point we no longer do any assignments of
+ // RestyleResult::eContinue to |result|. If canStopWithStyleChange is true,
+ // it means that we can convert |result| (whether it is
+ // RestyleResult::eContinue or RestyleResult::eStop) into
+ // RestyleResult::eStopWithStyleChange.
+ if (canStopWithStyleChange) {
+ LOG_RESTYLE("converting %s into RestyleResult::eStopWithStyleChange",
+ RestyleResultToString(result).get());
+ result = RestyleResult::eStopWithStyleChange;
+ }
+
+ if (aRestyleHint & eRestyle_ForceDescendants) {
+ result = RestyleResult::eContinueAndForceDescendants;
+ }
+
+ if (!(mHintsHandled & nsChangeHint_ReconstructFrame)) {
+ // If the frame gets regenerated, let it keep its old context,
+ // which is important to maintain various invariants about
+ // frame types matching their style contexts.
+ // Note that this check even makes sense if we didn't call
+ // CaptureChange because of copyFromContinuation being true,
+ // since we'll have copied the existing context from the
+ // previous continuation, so newContext == oldContext.
+
+ if (result != RestyleResult::eStop) {
+ if (copyFromContinuation) {
+ LOG_RESTYLE("not swapping style structs, since we copied from a "
+ "continuation");
+ } else if (oldContext->IsShared() && newContext->IsShared()) {
+ LOG_RESTYLE("not swapping style structs, since both old and contexts "
+ "are shared");
+ } else if (oldContext->IsShared()) {
+ LOG_RESTYLE("not swapping style structs, since the old context is "
+ "shared");
+ } else if (newContext->IsShared()) {
+ LOG_RESTYLE("not swapping style structs, since the new context is "
+ "shared");
+ } else {
+ if (result == RestyleResult::eStopWithStyleChange) {
+ LOG_RESTYLE("recording a style struct swap between %p and %p to "
+ "do if RestyleResult::eStopWithStyleChange fails",
+ oldContext.get(), newContext.get());
+ SwapInstruction* swap = aSwaps.AppendElement();
+ swap->mOldContext = oldContext;
+ swap->mNewContext = newContext;
+ swap->mStructsToSwap = equalStructs;
+ } else {
+ LOG_RESTYLE("swapping style structs between %p and %p",
+ oldContext.get(), newContext.get());
+ oldContext->SwapStyleData(newContext, equalStructs);
+ *aSwappedStructs |= equalStructs;
+ }
+#ifdef RESTYLE_LOGGING
+ uint32_t structs = RestyleManager::StructsToLog() & equalStructs;
+ if (structs) {
+ LOG_RESTYLE_INDENT();
+ LOG_RESTYLE("old style context now has: %s",
+ oldContext->GetCachedStyleDataAsString(structs).get());
+ LOG_RESTYLE("new style context now has: %s",
+ newContext->GetCachedStyleDataAsString(structs).get());
+ }
+#endif
+ }
+ LOG_RESTYLE("setting new style context");
+ aSelf->SetStyleContext(newContext);
+ }
+ } else {
+ LOG_RESTYLE("not setting new style context, since we'll reframe");
+ // We need to keep the new parent alive, in case it had structs
+ // swapped into it that our frame's style context still has cached.
+ // This is a similar scenario to the one described in the
+ // ElementRestyler::Restyle comment where we append to
+ // mSwappedStructOwners.
+ //
+ // We really only need to do this if we did swap structs on the
+ // parent, but we don't have that information here.
+ mSwappedStructOwners.AppendElement(newContext->GetParent());
+ }
+ } else {
+ if (aRestyleHint & eRestyle_ForceDescendants) {
+ result = RestyleResult::eContinueAndForceDescendants;
+ }
+ }
+ oldContext = nullptr;
+
+ // do additional contexts
+ // XXXbz might be able to avoid selector matching here in some
+ // cases; won't worry about it for now.
+ int32_t contextIndex = 0;
+ for (nsStyleContext* oldExtraContext;
+ (oldExtraContext = aSelf->GetAdditionalStyleContext(contextIndex));
+ ++contextIndex) {
+ LOG_RESTYLE("extra context %d", contextIndex);
+ LOG_RESTYLE_INDENT();
+ RefPtr<nsStyleContext> newExtraContext;
+ nsIAtom* const extraPseudoTag = oldExtraContext->GetPseudo();
+ const CSSPseudoElementType extraPseudoType =
+ oldExtraContext->GetPseudoType();
+ NS_ASSERTION(extraPseudoTag &&
+ !nsCSSAnonBoxes::IsNonElement(extraPseudoTag),
+ "extra style context is not pseudo element");
+ Element* element = extraPseudoType != CSSPseudoElementType::AnonBox
+ ? mContent->AsElement() : nullptr;
+ if (!MustRestyleSelf(aRestyleHint, element)) {
+ if (CanReparentStyleContext(aRestyleHint)) {
+ newExtraContext =
+ styleSet->ReparentStyleContext(oldExtraContext, newContext, element);
+ } else {
+ // Use ResolveStyleWithReplacement as a substitute for
+ // ReparentStyleContext that rebuilds the path in the rule tree
+ // rather than reusing the rule node, as we need to do during a
+ // rule tree reconstruct.
+ Element* pseudoElement =
+ PseudoElementForStyleContext(aSelf, extraPseudoType);
+ MOZ_ASSERT(!element || element != pseudoElement,
+ "pseudo-element for selector matching should be "
+ "the anonymous content node that we create, "
+ "not the real element");
+ newExtraContext =
+ styleSet->ResolveStyleWithReplacement(element, pseudoElement,
+ newContext, oldExtraContext,
+ nsRestyleHint(0));
+ }
+ } else if (extraPseudoType == CSSPseudoElementType::AnonBox) {
+ newExtraContext = styleSet->ResolveAnonymousBoxStyle(extraPseudoTag,
+ newContext);
+ } else {
+ // Don't expect XUL tree stuff here, since it needs a comparator and
+ // all.
+ NS_ASSERTION(extraPseudoType < CSSPseudoElementType::Count,
+ "Unexpected type");
+ newExtraContext = styleSet->ResolvePseudoElementStyle(mContent->AsElement(),
+ extraPseudoType,
+ newContext,
+ nullptr);
+ }
+
+ MOZ_ASSERT(newExtraContext);
+
+ LOG_RESTYLE("newExtraContext = %p", newExtraContext.get());
+
+ if (oldExtraContext != newExtraContext) {
+ uint32_t equalStructs;
+ uint32_t samePointerStructs;
+ CaptureChange(oldExtraContext, newExtraContext, assumeDifferenceHint,
+ &equalStructs, &samePointerStructs);
+ if (!(mHintsHandled & nsChangeHint_ReconstructFrame)) {
+ LOG_RESTYLE("setting new extra style context");
+ aSelf->SetAdditionalStyleContext(contextIndex, newExtraContext);
+ } else {
+ LOG_RESTYLE("not setting new extra style context, since we'll reframe");
+ }
+ }
+ }
+
+ LOG_RESTYLE("returning %s", RestyleResultToString(result).get());
+
+ return result;
+}
+
+void
+ElementRestyler::RestyleChildren(nsRestyleHint aChildRestyleHint)
+{
+ MOZ_ASSERT(!(mHintsHandled & nsChangeHint_ReconstructFrame),
+ "No need to do this if we're planning to reframe already.");
+
+ // We'd like style resolution to be exact in the sense that an
+ // animation-only style flush flushes only the styles it requests
+ // flushing and doesn't update any other styles. This means avoiding
+ // constructing new frames during such a flush.
+ //
+ // For a ::before or ::after, we'll do an eRestyle_Subtree due to
+ // RestyleHintForOp in nsCSSRuleProcessor.cpp (via its
+ // HasAttributeDependentStyle or HasStateDependentStyle), given that
+ // we store pseudo-elements in selectors like they were children.
+ //
+ // Also, it's faster to skip the work we do on undisplayed children
+ // and pseudo-elements when we can skip it.
+ bool mightReframePseudos = aChildRestyleHint & eRestyle_Subtree;
+
+ RestyleUndisplayedDescendants(aChildRestyleHint);
+
+ // Check whether we might need to create a new ::before frame.
+ // There's no need to do this if we're planning to reframe already
+ // or if we're not forcing restyles on kids.
+ // It's also important to check mHintsHandled since we use
+ // mFrame->StyleContext(), which is out of date if mHintsHandled has a
+ // ReconstructFrame hint. Using an out of date style context could
+ // trigger assertions about mismatched rule trees.
+ if (!(mHintsHandled & nsChangeHint_ReconstructFrame) &&
+ mightReframePseudos) {
+ MaybeReframeForBeforePseudo();
+ }
+
+ // There is no need to waste time crawling into a frame's children
+ // on a frame change. The act of reconstructing frames will force
+ // new style contexts to be resolved on all of this frame's
+ // descendants anyway, so we want to avoid wasting time processing
+ // style contexts that we're just going to throw away anyway. - dwh
+ // It's also important to check mHintsHandled since reresolving the
+ // kids would use mFrame->StyleContext(), which is out of date if
+ // mHintsHandled has a ReconstructFrame hint; doing this could trigger
+ // assertions about mismatched rule trees.
+ nsIFrame* lastContinuation;
+ if (!(mHintsHandled & nsChangeHint_ReconstructFrame)) {
+ InitializeAccessibilityNotifications(mFrame->StyleContext());
+
+ for (nsIFrame* f = mFrame; f;
+ f = RestyleManager::GetNextContinuationWithSameStyle(f, f->StyleContext())) {
+ lastContinuation = f;
+ RestyleContentChildren(f, aChildRestyleHint);
+ }
+
+ SendAccessibilityNotifications();
+ }
+
+ // Check whether we might need to create a new ::after frame.
+ // See comments above regarding :before.
+ if (!(mHintsHandled & nsChangeHint_ReconstructFrame) &&
+ mightReframePseudos) {
+ MaybeReframeForAfterPseudo(lastContinuation);
+ }
+}
+
+void
+ElementRestyler::RestyleChildrenOfDisplayContentsElement(
+ nsIFrame* aParentFrame,
+ nsStyleContext* aNewContext,
+ nsChangeHint aMinHint,
+ RestyleTracker& aRestyleTracker,
+ nsRestyleHint aRestyleHint,
+ const RestyleHintData& aRestyleHintData)
+{
+ MOZ_ASSERT(!(mHintsHandled & nsChangeHint_ReconstructFrame), "why call me?");
+
+ const bool mightReframePseudos = aRestyleHint & eRestyle_Subtree;
+ DoRestyleUndisplayedDescendants(nsRestyleHint(0), mContent, aNewContext);
+ if (!(mHintsHandled & nsChangeHint_ReconstructFrame) && mightReframePseudos) {
+ MaybeReframeForPseudo(CSSPseudoElementType::before,
+ aParentFrame, nullptr, mContent, aNewContext);
+ }
+ if (!(mHintsHandled & nsChangeHint_ReconstructFrame) && mightReframePseudos) {
+ MaybeReframeForPseudo(CSSPseudoElementType::after,
+ aParentFrame, nullptr, mContent, aNewContext);
+ }
+ if (!(mHintsHandled & nsChangeHint_ReconstructFrame)) {
+ InitializeAccessibilityNotifications(aNewContext);
+
+ // Then process child frames for content that is a descendant of mContent.
+ // XXX perhaps it's better to walk child frames (before reresolving
+ // XXX undisplayed contexts above) and mark those that has a stylecontext
+ // XXX leading up to mContent's old context? (instead of the
+ // XXX ContentIsDescendantOf check below)
+ nsIFrame::ChildListIterator lists(aParentFrame);
+ for ( ; !lists.IsDone(); lists.Next()) {
+ for (nsIFrame* f : lists.CurrentList()) {
+ if (nsContentUtils::ContentIsDescendantOf(f->GetContent(), mContent) &&
+ !f->GetPrevContinuation()) {
+ if (!(f->GetStateBits() & NS_FRAME_OUT_OF_FLOW)) {
+ ComputeStyleChangeFor(f, mChangeList, aMinHint, aRestyleTracker,
+ aRestyleHint, aRestyleHintData,
+ mContextsToClear, mSwappedStructOwners);
+ }
+ }
+ }
+ }
+ }
+ if (!(mHintsHandled & nsChangeHint_ReconstructFrame)) {
+ SendAccessibilityNotifications();
+ }
+}
+
+void
+ElementRestyler::ComputeStyleChangeFor(nsIFrame* aFrame,
+ nsStyleChangeList* aChangeList,
+ nsChangeHint aMinChange,
+ RestyleTracker& aRestyleTracker,
+ nsRestyleHint aRestyleHint,
+ const RestyleHintData& aRestyleHintData,
+ nsTArray<ContextToClear>&
+ aContextsToClear,
+ nsTArray<RefPtr<nsStyleContext>>&
+ aSwappedStructOwners)
+{
+ nsIContent* content = aFrame->GetContent();
+ nsAutoCString localDescriptor;
+ if (profiler_is_active() && content) {
+ std::string elemDesc = ToString(*content);
+ localDescriptor.Assign(elemDesc.c_str());
+ }
+
+ PROFILER_LABEL_PRINTF("ElementRestyler", "ComputeStyleChangeFor",
+ js::ProfileEntry::Category::CSS,
+ content ? "Element: %s" : "%s",
+ content ? localDescriptor.get() : "");
+ if (aMinChange) {
+ aChangeList->AppendChange(aFrame, content, aMinChange);
+ }
+
+ NS_ASSERTION(!aFrame->GetPrevContinuation(),
+ "must start with the first continuation");
+
+ // We want to start with this frame and walk all its next-in-flows,
+ // as well as all its ib-split siblings and their next-in-flows,
+ // reresolving style on all the frames we encounter in this walk that
+ // we didn't reach already. In the normal case, this will mean only
+ // restyling the first two block-in-inline splits and no
+ // continuations, and skipping everything else. However, when we have
+ // a style change targeted at an element inside a context where styles
+ // vary between continuations (e.g., a style change on an element that
+ // extends from inside a styled ::first-line to outside of that first
+ // line), we might restyle more than that.
+
+ nsPresContext* presContext = aFrame->PresContext();
+ FramePropertyTable* propTable = presContext->PropertyTable();
+
+ TreeMatchContext treeMatchContext(true,
+ nsRuleWalker::eRelevantLinkUnvisited,
+ presContext->Document());
+ Element* parent =
+ content ? content->GetParentElementCrossingShadowRoot() : nullptr;
+ treeMatchContext.InitAncestors(parent);
+ nsTArray<nsCSSSelector*> selectorsForDescendants;
+ selectorsForDescendants.AppendElements(
+ aRestyleHintData.mSelectorsForDescendants);
+ nsTArray<nsIContent*> visibleKidsOfHiddenElement;
+ nsIFrame* nextIBSibling;
+ for (nsIFrame* ibSibling = aFrame; ibSibling; ibSibling = nextIBSibling) {
+ nextIBSibling = RestyleManager::GetNextBlockInInlineSibling(propTable, ibSibling);
+
+ if (nextIBSibling) {
+ // Don't allow some ib-split siblings to be processed with
+ // RestyleResult::eStopWithStyleChange and others not.
+ aRestyleHint |= eRestyle_Force;
+ }
+
+ // Outer loop over ib-split siblings
+ for (nsIFrame* cont = ibSibling; cont; cont = cont->GetNextContinuation()) {
+ if (GetPrevContinuationWithSameStyle(cont)) {
+ // We already handled this element when dealing with its earlier
+ // continuation.
+ continue;
+ }
+
+ // Inner loop over next-in-flows of the current frame
+ ElementRestyler restyler(presContext, cont, aChangeList,
+ aMinChange, aRestyleTracker,
+ selectorsForDescendants,
+ treeMatchContext,
+ visibleKidsOfHiddenElement,
+ aContextsToClear, aSwappedStructOwners);
+
+ restyler.Restyle(aRestyleHint);
+
+ if (restyler.HintsHandledForFrame() & nsChangeHint_ReconstructFrame) {
+ // If it's going to cause a framechange, then don't bother
+ // with the continuations or ib-split siblings since they'll be
+ // clobbered by the frame reconstruct anyway.
+ NS_ASSERTION(!cont->GetPrevContinuation(),
+ "continuing frame had more severe impact than first-in-flow");
+ return;
+ }
+ }
+ }
+}
+
+// The structure of this method parallels ConditionallyRestyleUndisplayedDescendants.
+// If you update this method, you probably want to update that one too.
+void
+ElementRestyler::RestyleUndisplayedDescendants(nsRestyleHint aChildRestyleHint)
+{
+ nsIContent* undisplayedParent;
+ if (MustCheckUndisplayedContent(mFrame, undisplayedParent)) {
+ DoRestyleUndisplayedDescendants(aChildRestyleHint, undisplayedParent,
+ mFrame->StyleContext());
+ }
+}
+
+// The structure of this method parallels DoConditionallyRestyleUndisplayedDescendants.
+// If you update this method, you probably want to update that one too.
+void
+ElementRestyler::DoRestyleUndisplayedDescendants(nsRestyleHint aChildRestyleHint,
+ nsIContent* aParent,
+ nsStyleContext* aParentContext)
+{
+ nsCSSFrameConstructor* fc = mPresContext->FrameConstructor();
+ UndisplayedNode* nodes = fc->GetAllUndisplayedContentIn(aParent);
+ RestyleUndisplayedNodes(aChildRestyleHint, nodes, aParent,
+ aParentContext, StyleDisplay::None);
+ nodes = fc->GetAllDisplayContentsIn(aParent);
+ RestyleUndisplayedNodes(aChildRestyleHint, nodes, aParent,
+ aParentContext, StyleDisplay::Contents);
+}
+
+// The structure of this method parallels ConditionallyRestyleUndisplayedNodes.
+// If you update this method, you probably want to update that one too.
+void
+ElementRestyler::RestyleUndisplayedNodes(nsRestyleHint aChildRestyleHint,
+ UndisplayedNode* aUndisplayed,
+ nsIContent* aUndisplayedParent,
+ nsStyleContext* aParentContext,
+ const StyleDisplay aDisplay)
+{
+ nsIContent* undisplayedParent = aUndisplayedParent;
+ UndisplayedNode* undisplayed = aUndisplayed;
+ TreeMatchContext::AutoAncestorPusher pusher(mTreeMatchContext);
+ if (undisplayed) {
+ pusher.PushAncestorAndStyleScope(undisplayedParent);
+ }
+ for (; undisplayed; undisplayed = undisplayed->mNext) {
+ NS_ASSERTION(undisplayedParent ||
+ undisplayed->mContent ==
+ mPresContext->Document()->GetRootElement(),
+ "undisplayed node child of null must be root");
+ NS_ASSERTION(!undisplayed->mStyle->GetPseudo(),
+ "Shouldn't have random pseudo style contexts in the "
+ "undisplayed map");
+
+ LOG_RESTYLE("RestyleUndisplayedChildren: undisplayed->mContent = %p",
+ undisplayed->mContent.get());
+
+ // Get the parent of the undisplayed content and check if it is a XBL
+ // children element. Push the children element as an ancestor here because it does
+ // not have a frame and would not otherwise be pushed as an ancestor.
+ nsIContent* parent = undisplayed->mContent->GetParent();
+ TreeMatchContext::AutoAncestorPusher insertionPointPusher(mTreeMatchContext);
+ if (parent && nsContentUtils::IsContentInsertionPoint(parent)) {
+ insertionPointPusher.PushAncestorAndStyleScope(parent);
+ }
+
+ nsRestyleHint thisChildHint = aChildRestyleHint;
+ nsAutoPtr<RestyleTracker::RestyleData> undisplayedRestyleData;
+ Element* element = undisplayed->mContent->AsElement();
+ if (mRestyleTracker.GetRestyleData(element,
+ undisplayedRestyleData)) {
+ thisChildHint =
+ nsRestyleHint(thisChildHint | undisplayedRestyleData->mRestyleHint);
+ }
+ RefPtr<nsStyleContext> undisplayedContext;
+ nsStyleSet* styleSet = StyleSet();
+ if (MustRestyleSelf(thisChildHint, element)) {
+ undisplayedContext =
+ styleSet->ResolveStyleFor(element, aParentContext, mTreeMatchContext);
+ } else if (CanReparentStyleContext(thisChildHint)) {
+ undisplayedContext =
+ styleSet->ReparentStyleContext(undisplayed->mStyle,
+ aParentContext,
+ element);
+ } else {
+ // Use ResolveStyleWithReplacement either for actual
+ // replacements, or as a substitute for ReparentStyleContext
+ // that rebuilds the path in the rule tree rather than reusing
+ // the rule node, as we need to do during a rule tree
+ // reconstruct.
+ nsRestyleHint rshint = thisChildHint & ~eRestyle_SomeDescendants;
+ undisplayedContext =
+ styleSet->ResolveStyleWithReplacement(element, nullptr,
+ aParentContext,
+ undisplayed->mStyle,
+ rshint);
+ }
+ const nsStyleDisplay* display = undisplayedContext->StyleDisplay();
+ if (display->mDisplay != aDisplay) {
+ NS_ASSERTION(element, "Must have undisplayed content");
+ mChangeList->AppendChange(nullptr, element,
+ nsChangeHint_ReconstructFrame);
+ // The node should be removed from the undisplayed map when
+ // we reframe it.
+ } else {
+ // update the undisplayed node with the new context
+ undisplayed->mStyle = undisplayedContext;
+
+ if (aDisplay == StyleDisplay::Contents) {
+ DoRestyleUndisplayedDescendants(aChildRestyleHint, element,
+ undisplayed->mStyle);
+ }
+ }
+ }
+}
+
+void
+ElementRestyler::MaybeReframeForBeforePseudo()
+{
+ MaybeReframeForPseudo(CSSPseudoElementType::before,
+ mFrame, mFrame, mFrame->GetContent(),
+ mFrame->StyleContext());
+}
+
+/**
+ * aFrame is the last continuation or block-in-inline sibling that this
+ * ElementRestyler is restyling.
+ */
+void
+ElementRestyler::MaybeReframeForAfterPseudo(nsIFrame* aFrame)
+{
+ MOZ_ASSERT(aFrame);
+ MaybeReframeForPseudo(CSSPseudoElementType::after,
+ aFrame, aFrame, aFrame->GetContent(),
+ aFrame->StyleContext());
+}
+
+#ifdef DEBUG
+bool
+ElementRestyler::MustReframeForBeforePseudo()
+{
+ return MustReframeForPseudo(CSSPseudoElementType::before,
+ mFrame, mFrame, mFrame->GetContent(),
+ mFrame->StyleContext());
+}
+
+bool
+ElementRestyler::MustReframeForAfterPseudo(nsIFrame* aFrame)
+{
+ MOZ_ASSERT(aFrame);
+ return MustReframeForPseudo(CSSPseudoElementType::after,
+ aFrame, aFrame, aFrame->GetContent(),
+ aFrame->StyleContext());
+}
+#endif
+
+void
+ElementRestyler::MaybeReframeForPseudo(CSSPseudoElementType aPseudoType,
+ nsIFrame* aGenConParentFrame,
+ nsIFrame* aFrame,
+ nsIContent* aContent,
+ nsStyleContext* aStyleContext)
+{
+ if (MustReframeForPseudo(aPseudoType, aGenConParentFrame, aFrame, aContent,
+ aStyleContext)) {
+ // Have to create the new ::before/::after frame.
+ LOG_RESTYLE("MaybeReframeForPseudo, appending "
+ "nsChangeHint_ReconstructFrame");
+ mHintsHandled |= nsChangeHint_ReconstructFrame;
+ mChangeList->AppendChange(aFrame, aContent, nsChangeHint_ReconstructFrame);
+ }
+}
+
+bool
+ElementRestyler::MustReframeForPseudo(CSSPseudoElementType aPseudoType,
+ nsIFrame* aGenConParentFrame,
+ nsIFrame* aFrame,
+ nsIContent* aContent,
+ nsStyleContext* aStyleContext)
+{
+ MOZ_ASSERT(aPseudoType == CSSPseudoElementType::before ||
+ aPseudoType == CSSPseudoElementType::after);
+
+ // Make sure not to do this for pseudo-frames...
+ if (aStyleContext->GetPseudo()) {
+ return false;
+ }
+
+ // ... or frames that can't have generated content.
+ if (!(aGenConParentFrame->GetStateBits() & NS_FRAME_MAY_HAVE_GENERATED_CONTENT)) {
+ // Our content insertion frame might have gotten flagged.
+ nsContainerFrame* cif = aGenConParentFrame->GetContentInsertionFrame();
+ if (!cif || !(cif->GetStateBits() & NS_FRAME_MAY_HAVE_GENERATED_CONTENT)) {
+ return false;
+ }
+ }
+
+ if (aPseudoType == CSSPseudoElementType::before) {
+ // Check for a ::before pseudo style and the absence of a ::before content,
+ // but only if aFrame is null or is the first continuation/ib-split.
+ if ((aFrame && !nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(aFrame)) ||
+ nsLayoutUtils::GetBeforeFrameForContent(aGenConParentFrame, aContent)) {
+ return false;
+ }
+ } else {
+ // Similarly for ::after, but check for being the last continuation/
+ // ib-split.
+ if ((aFrame && nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame)) ||
+ nsLayoutUtils::GetAfterFrameForContent(aGenConParentFrame, aContent)) {
+ return false;
+ }
+ }
+
+ // Checking for a ::before frame (which we do above) is cheaper than getting
+ // the ::before style context here.
+ return nsLayoutUtils::HasPseudoStyle(aContent, aStyleContext, aPseudoType,
+ mPresContext);
+}
+
+void
+ElementRestyler::InitializeAccessibilityNotifications(nsStyleContext* aNewContext)
+{
+#ifdef ACCESSIBILITY
+ // Notify a11y for primary frame only if it's a root frame of visibility
+ // changes or its parent frame was hidden while it stays visible and
+ // it is not inside a {ib} split or is the first frame of {ib} split.
+ if (nsIPresShell::IsAccessibilityActive() &&
+ (!mFrame ||
+ (!mFrame->GetPrevContinuation() &&
+ !mFrame->FrameIsNonFirstInIBSplit()))) {
+ if (mDesiredA11yNotifications == eSendAllNotifications) {
+ bool isFrameVisible = aNewContext->StyleVisibility()->IsVisible();
+ if (isFrameVisible != mWasFrameVisible) {
+ if (isFrameVisible) {
+ // Notify a11y the element (perhaps with its children) was shown.
+ // We don't fall into this case if this element gets or stays shown
+ // while its parent becomes hidden.
+ mKidsDesiredA11yNotifications = eSkipNotifications;
+ mOurA11yNotification = eNotifyShown;
+ } else {
+ // The element is being hidden; its children may stay visible, or
+ // become visible after being hidden previously. If we'll find
+ // visible children then we should notify a11y about that as if
+ // they were inserted into tree. Notify a11y this element was
+ // hidden.
+ mKidsDesiredA11yNotifications = eNotifyIfShown;
+ mOurA11yNotification = eNotifyHidden;
+ }
+ }
+ } else if (mDesiredA11yNotifications == eNotifyIfShown &&
+ aNewContext->StyleVisibility()->IsVisible()) {
+ // Notify a11y that element stayed visible while its parent was hidden.
+ nsIContent* c = mFrame ? mFrame->GetContent() : mContent;
+ mVisibleKidsOfHiddenElement.AppendElement(c);
+ mKidsDesiredA11yNotifications = eSkipNotifications;
+ }
+ }
+#endif
+}
+
+// The structure of this method parallels ConditionallyRestyleContentChildren.
+// If you update this method, you probably want to update that one too.
+void
+ElementRestyler::RestyleContentChildren(nsIFrame* aParent,
+ nsRestyleHint aChildRestyleHint)
+{
+ LOG_RESTYLE("RestyleContentChildren");
+
+ nsIFrame::ChildListIterator lists(aParent);
+ TreeMatchContext::AutoAncestorPusher ancestorPusher(mTreeMatchContext);
+ if (!lists.IsDone()) {
+ ancestorPusher.PushAncestorAndStyleScope(mContent);
+ }
+ for (; !lists.IsDone(); lists.Next()) {
+ for (nsIFrame* child : lists.CurrentList()) {
+ // Out-of-flows are reached through their placeholders. Continuations
+ // and block-in-inline splits are reached through those chains.
+ if (!(child->GetStateBits() & NS_FRAME_OUT_OF_FLOW) &&
+ !GetPrevContinuationWithSameStyle(child)) {
+ // Get the parent of the child frame's content and check if it
+ // is a XBL children element. Push the children element as an
+ // ancestor here because it does not have a frame and would not
+ // otherwise be pushed as an ancestor.
+
+ // Check if the frame has a content because |child| may be a
+ // nsPageFrame that does not have a content.
+ nsIContent* parent = child->GetContent() ? child->GetContent()->GetParent() : nullptr;
+ TreeMatchContext::AutoAncestorPusher insertionPointPusher(mTreeMatchContext);
+ if (parent && nsContentUtils::IsContentInsertionPoint(parent)) {
+ insertionPointPusher.PushAncestorAndStyleScope(parent);
+ }
+
+ // only do frames that are in flow
+ if (nsGkAtoms::placeholderFrame == child->GetType()) { // placeholder
+ // get out of flow frame and recur there
+ nsIFrame* outOfFlowFrame =
+ nsPlaceholderFrame::GetRealFrameForPlaceholder(child);
+ NS_ASSERTION(outOfFlowFrame, "no out-of-flow frame");
+ NS_ASSERTION(outOfFlowFrame != mResolvedChild,
+ "out-of-flow frame not a true descendant");
+
+ // |nsFrame::GetParentStyleContext| checks being out
+ // of flow so that this works correctly.
+ do {
+ if (GetPrevContinuationWithSameStyle(outOfFlowFrame)) {
+ // Later continuations are likely restyled as a result of
+ // the restyling of the previous continuation.
+ // (Currently that's always true, but it's likely to
+ // change if we implement overflow:fragments or similar.)
+ continue;
+ }
+ ElementRestyler oofRestyler(*this, outOfFlowFrame,
+ FOR_OUT_OF_FLOW_CHILD);
+ oofRestyler.Restyle(aChildRestyleHint);
+ } while ((outOfFlowFrame = outOfFlowFrame->GetNextContinuation()));
+
+ // reresolve placeholder's context under the same parent
+ // as the out-of-flow frame
+ ElementRestyler phRestyler(*this, child, 0);
+ phRestyler.Restyle(aChildRestyleHint);
+ }
+ else { // regular child frame
+ if (child != mResolvedChild) {
+ ElementRestyler childRestyler(*this, child, 0);
+ childRestyler.Restyle(aChildRestyleHint);
+ }
+ }
+ }
+ }
+ }
+ // XXX need to do overflow frames???
+}
+
+void
+ElementRestyler::SendAccessibilityNotifications()
+{
+#ifdef ACCESSIBILITY
+ // Send notifications about visibility changes.
+ if (mOurA11yNotification == eNotifyShown) {
+ nsAccessibilityService* accService = nsIPresShell::AccService();
+ if (accService) {
+ nsIPresShell* presShell = mPresContext->GetPresShell();
+ nsIContent* content = mFrame ? mFrame->GetContent() : mContent;
+
+ accService->ContentRangeInserted(presShell, content->GetParent(),
+ content,
+ content->GetNextSibling());
+ }
+ } else if (mOurA11yNotification == eNotifyHidden) {
+ nsAccessibilityService* accService = nsIPresShell::AccService();
+ if (accService) {
+ nsIPresShell* presShell = mPresContext->GetPresShell();
+ nsIContent* content = mFrame ? mFrame->GetContent() : mContent;
+ accService->ContentRemoved(presShell, content);
+
+ // Process children staying shown.
+ uint32_t visibleContentCount = mVisibleKidsOfHiddenElement.Length();
+ for (uint32_t idx = 0; idx < visibleContentCount; idx++) {
+ nsIContent* childContent = mVisibleKidsOfHiddenElement[idx];
+ accService->ContentRangeInserted(presShell, childContent->GetParent(),
+ childContent,
+ childContent->GetNextSibling());
+ }
+ mVisibleKidsOfHiddenElement.Clear();
+ }
+ }
+#endif
+}
+
+static void
+ClearCachedInheritedStyleDataOnDescendants(
+ nsTArray<ElementRestyler::ContextToClear>& aContextsToClear)
+{
+ for (size_t i = 0; i < aContextsToClear.Length(); i++) {
+ auto& entry = aContextsToClear[i];
+ if (!entry.mStyleContext->HasSingleReference()) {
+ entry.mStyleContext->ClearCachedInheritedStyleDataOnDescendants(
+ entry.mStructs);
+ }
+ entry.mStyleContext = nullptr;
+ }
+}
+
+void
+RestyleManager::ComputeAndProcessStyleChange(nsIFrame* aFrame,
+ nsChangeHint aMinChange,
+ RestyleTracker& aRestyleTracker,
+ nsRestyleHint aRestyleHint,
+ const RestyleHintData& aRestyleHintData)
+{
+ MOZ_ASSERT(mReframingStyleContexts, "should have rsc");
+ nsStyleChangeList changeList;
+ nsTArray<ElementRestyler::ContextToClear> contextsToClear;
+
+ // swappedStructOwners needs to be kept alive until after
+ // ProcessRestyledFrames and ClearCachedInheritedStyleDataOnDescendants
+ // calls; see comment in ElementRestyler::Restyle.
+ nsTArray<RefPtr<nsStyleContext>> swappedStructOwners;
+ ElementRestyler::ComputeStyleChangeFor(aFrame, &changeList, aMinChange,
+ aRestyleTracker, aRestyleHint,
+ aRestyleHintData,
+ contextsToClear, swappedStructOwners);
+ ProcessRestyledFrames(changeList);
+ ClearCachedInheritedStyleDataOnDescendants(contextsToClear);
+}
+
+void
+RestyleManager::ComputeAndProcessStyleChange(nsStyleContext* aNewContext,
+ Element* aElement,
+ nsChangeHint aMinChange,
+ RestyleTracker& aRestyleTracker,
+ nsRestyleHint aRestyleHint,
+ const RestyleHintData& aRestyleHintData)
+{
+ MOZ_ASSERT(mReframingStyleContexts, "should have rsc");
+ MOZ_ASSERT(aNewContext->StyleDisplay()->mDisplay == StyleDisplay::Contents);
+ nsIFrame* frame = GetNearestAncestorFrame(aElement);
+ MOZ_ASSERT(frame, "display:contents node in map although it's a "
+ "display:none descendant?");
+ TreeMatchContext treeMatchContext(true,
+ nsRuleWalker::eRelevantLinkUnvisited,
+ frame->PresContext()->Document());
+ nsIContent* parent = aElement->GetParent();
+ Element* parentElement =
+ parent && parent->IsElement() ? parent->AsElement() : nullptr;
+ treeMatchContext.InitAncestors(parentElement);
+
+ nsTArray<nsCSSSelector*> selectorsForDescendants;
+ nsTArray<nsIContent*> visibleKidsOfHiddenElement;
+ nsTArray<ElementRestyler::ContextToClear> contextsToClear;
+
+ // swappedStructOwners needs to be kept alive until after
+ // ProcessRestyledFrames and ClearCachedInheritedStyleDataOnDescendants
+ // calls; see comment in ElementRestyler::Restyle.
+ nsTArray<RefPtr<nsStyleContext>> swappedStructOwners;
+ nsStyleChangeList changeList;
+ ElementRestyler r(frame->PresContext(), aElement, &changeList, aMinChange,
+ aRestyleTracker, selectorsForDescendants, treeMatchContext,
+ visibleKidsOfHiddenElement, contextsToClear,
+ swappedStructOwners);
+ r.RestyleChildrenOfDisplayContentsElement(frame, aNewContext, aMinChange,
+ aRestyleTracker,
+ aRestyleHint, aRestyleHintData);
+ ProcessRestyledFrames(changeList);
+ ClearCachedInheritedStyleDataOnDescendants(contextsToClear);
+}
+
+nsStyleSet*
+ElementRestyler::StyleSet() const
+{
+ MOZ_ASSERT(mPresContext->StyleSet()->IsGecko(),
+ "ElementRestyler should only be used with a Gecko-flavored "
+ "style backend");
+ return mPresContext->StyleSet()->AsGecko();
+}
+
+AutoDisplayContentsAncestorPusher::AutoDisplayContentsAncestorPusher(
+ TreeMatchContext& aTreeMatchContext, nsPresContext* aPresContext,
+ nsIContent* aParent)
+ : mTreeMatchContext(aTreeMatchContext)
+ , mPresContext(aPresContext)
+{
+ if (aParent) {
+ nsFrameManager* fm = mPresContext->FrameManager();
+ // Push display:contents mAncestors onto mTreeMatchContext.
+ for (nsIContent* p = aParent; p && fm->GetDisplayContentsStyleFor(p);
+ p = p->GetParent()) {
+ mAncestors.AppendElement(p->AsElement());
+ }
+ bool hasFilter = mTreeMatchContext.mAncestorFilter.HasFilter();
+ nsTArray<mozilla::dom::Element*>::size_type i = mAncestors.Length();
+ while (i--) {
+ if (hasFilter) {
+ mTreeMatchContext.mAncestorFilter.PushAncestor(mAncestors[i]);
+ }
+ mTreeMatchContext.PushStyleScope(mAncestors[i]);
+ }
+ }
+}
+
+AutoDisplayContentsAncestorPusher::~AutoDisplayContentsAncestorPusher()
+{
+ // Pop the ancestors we pushed in the CTOR, if any.
+ typedef nsTArray<mozilla::dom::Element*>::size_type sz;
+ sz len = mAncestors.Length();
+ bool hasFilter = mTreeMatchContext.mAncestorFilter.HasFilter();
+ for (sz i = 0; i < len; ++i) {
+ if (hasFilter) {
+ mTreeMatchContext.mAncestorFilter.PopAncestor();
+ }
+ mTreeMatchContext.PopStyleScope(mAncestors[i]);
+ }
+}
+
+#ifdef RESTYLE_LOGGING
+uint32_t
+RestyleManager::StructsToLog()
+{
+ static bool initialized = false;
+ static uint32_t structs;
+ if (!initialized) {
+ structs = 0;
+ const char* value = getenv("MOZ_DEBUG_RESTYLE_STRUCTS");
+ if (value) {
+ nsCString s(value);
+ while (!s.IsEmpty()) {
+ int32_t index = s.FindChar(',');
+ nsStyleStructID sid;
+ bool found;
+ if (index == -1) {
+ found = nsStyleContext::LookupStruct(s, sid);
+ s.Truncate();
+ } else {
+ found = nsStyleContext::LookupStruct(Substring(s, 0, index), sid);
+ s = Substring(s, index + 1);
+ }
+ if (found) {
+ structs |= nsCachedStyleData::GetBitForSID(sid);
+ }
+ }
+ }
+ initialized = true;
+ }
+ return structs;
+}
+#endif
+
+#ifdef DEBUG
+/* static */ nsCString
+RestyleManager::StructNamesToString(uint32_t aSIDs)
+{
+ nsCString result;
+ bool any = false;
+ for (nsStyleStructID sid = nsStyleStructID(0);
+ sid < nsStyleStructID_Length;
+ sid = nsStyleStructID(sid + 1)) {
+ if (aSIDs & nsCachedStyleData::GetBitForSID(sid)) {
+ if (any) {
+ result.AppendLiteral(",");
+ }
+ result.AppendPrintf("%s", nsStyleContext::StructName(sid));
+ any = true;
+ }
+ }
+ return result;
+}
+
+/* static */ nsCString
+ElementRestyler::RestyleResultToString(RestyleResult aRestyleResult)
+{
+ nsCString result;
+ switch (aRestyleResult) {
+ case RestyleResult::eStop:
+ result.AssignLiteral("RestyleResult::eStop");
+ break;
+ case RestyleResult::eStopWithStyleChange:
+ result.AssignLiteral("RestyleResult::eStopWithStyleChange");
+ break;
+ case RestyleResult::eContinue:
+ result.AssignLiteral("RestyleResult::eContinue");
+ break;
+ case RestyleResult::eContinueAndForceDescendants:
+ result.AssignLiteral("RestyleResult::eContinueAndForceDescendants");
+ break;
+ default:
+ MOZ_ASSERT(aRestyleResult == RestyleResult::eNone,
+ "Unexpected RestyleResult");
+ }
+ return result;
+}
+#endif
+
+} // namespace mozilla