/* -*- 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