gecko-dev/layout/base/GeckoRestyleManager.cpp
Markus Stange 6bc4c8de32 Bug 1354255 - Remove ElementRestyler::ComputeStyleChangeFor profiler instrumentation due to overhead. r=Ehsan
This only has overhead if the profiler is running, but it means that it has
the potential to skew restyle times in profiles.
We haven't measured the overhead of this, but it's probably non-zero, and at
the moment our profiling efforts are more focused on getting accurate times
than on getting useful information about restyling cost sources.

MozReview-Commit-ID: 3KmiiyGrxZH

--HG--
extra : rebase_source : df7047f1af5f36f7a1b3a18498d8eb5508ee0b93
2017-04-06 17:20:40 -04:00

3699 lines
148 KiB
C++

/* -*- 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/GeckoRestyleManager.h"
#include <algorithm> // For std::max
#include "mozilla/EffectSet.h"
#include "mozilla/EventStates.h"
#include "mozilla/ViewportFrame.h"
#include "mozilla/css/StyleRule.h" // For nsCSSSelector
#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 "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
GeckoRestyleManager::GeckoRestyleManager(nsPresContext* aPresContext)
: RestyleManager(StyleBackendType::Gecko, aPresContext)
, mDoRebuildAllStyleData(false)
, mInRebuildAllStyleData(false)
, mSkipAnimationRules(false)
, mHavePendingNonAnimationRestyles(false)
, mRebuildAllExtraHint(nsChangeHint(0))
, mRebuildAllRestyleHint(nsRestyleHint(0))
, mReframingStyleContexts(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
GeckoRestyleManager::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);
}
}
}
GeckoRestyleManager::ReframingStyleContexts
::ReframingStyleContexts(
GeckoRestyleManager* aRestyleManager)
: mRestyleManager(aRestyleManager)
, mRestorePointer(mRestyleManager->mReframingStyleContexts)
{
MOZ_ASSERT(!mRestyleManager->mReframingStyleContexts,
"shouldn't construct recursively");
mRestyleManager->mReframingStyleContexts = this;
}
GeckoRestyleManager::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();
}
static inline dom::Element*
ElementForStyleContext(nsIContent* aParentContent,
nsIFrame* aFrame,
CSSPseudoElementType aPseudoType);
// Forwarded nsIDocumentObserver method, to handle restyling (and
// passing the notification to the frame).
void
GeckoRestyleManager::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;
}
Element* aElement = aContent->AsElement();
nsChangeHint changeHint;
nsRestyleHint restyleHint;
ContentStateChangedInternal(aElement, aStateMask, &changeHint, &restyleHint);
PostRestyleEvent(aElement, restyleHint, changeHint);
}
// Forwarded nsIMutationObserver method, to handle restyling.
void
GeckoRestyleManager::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
GeckoRestyleManager::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->UsedAppearance()) {
nsITheme* theme = PresContext()->GetTheme();
if (theme && theme->ThemeSupportsWidget(PresContext(), primaryFrame, disp->UsedAppearance())) {
bool repaint = false;
theme->WidgetStateChanged(primaryFrame, disp->UsedAppearance(), 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);
}
void
GeckoRestyleManager::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(FlushType::ContentAndNotify);
nsAutoScriptBlocker scriptBlocker;
mDoRebuildAllStyleData = true;
ProcessPendingRestyles();
}
void
GeckoRestyleManager::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
GeckoRestyleManager::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
GeckoRestyleManager::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
GeckoRestyleManager::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
GeckoRestyleManager::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
GeckoRestyleManager::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) {
PresContext()->EffectCompositor()->AddStyleUpdatesTo(tracker);
}
if (doSMIL) {
animationController->AddStyleUpdatesTo(tracker);
}
ProcessRestyles(tracker);
transitionManager->SetInAnimationOnlyStyleUpdate(false);
}
void
GeckoRestyleManager::PostRestyleEventInternal()
{
// Make sure we're not in a style refresh; if we are, we still have
// a call to ProcessPendingRestyles coming and there's no need to
// add ourselves as a refresh observer until then.
nsIPresShell* presShell = PresContext()->PresShell();
if (!mInStyleRefresh) {
presShell->ObserveStyleFlushes();
}
// Unconditionally flag our document as needing a flush. The other
// option here would be a dedicated boolean to track whether we need
// to do so (set here and unset in ProcessPendingRestyles).
presShell->SetNeedStyleFlush();
}
void
GeckoRestyleManager::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();
}
void
GeckoRestyleManager::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();
}
// aContent must be the content for the frame in question, which may be
// :before/:after content
/* static */ bool
GeckoRestyleManager::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::InheritingAnonBox ||
aPseudoType == CSSPseudoElementType::NonInheritingAnonBox ||
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::InheritingAnonBox ||
aPseudoType == CSSPseudoElementType::NonInheritingAnonBox) {
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();
}
Element* frameElement = aFrame->GetContent()->AsElement();
if (frameElement->IsNativeAnonymous() &&
nsCSSPseudoElements::PseudoElementIsJSCreatedNAC(aPseudoType)) {
// NAC-implemented pseudos use the closest non-NAC element as their
// element to inherit from.
//
// FIXME(heycam): In theory we shouldn't need to limit this only to
// JS-created pseudo-implementing NAC, as all pseudo-implementing
// should use the closest non-native anonymous ancestor element as
// its originating element. But removing that part of the condition
// reveals some bugs in style resultion with display:contents and
// XBL. See bug 1345809.
Element* originatingElement =
nsContentUtils::GetClosestNonNativeAnonymousAncestor(frameElement);
if (originatingElement) {
return originatingElement;
}
}
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
GeckoRestyleManager::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;
}
#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
if (!newParentContext && !oldContext->GetParent()) {
// No need to do anything here.
#ifdef DEBUG
// Make sure we have no children, so we really know there is nothing to do.
nsIFrame::ChildListIterator lists(aFrame);
for (; !lists.IsDone(); lists.Next()) {
MOZ_ASSERT(lists.CurrentList().IsEmpty(),
"Failing to reparent style context for child of "
"non-inheriting anon box");
}
#endif // DEBUG
return NS_OK;
}
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.
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
// Ensure the new context ends up resolving all the structs the old
// context resolved.
if (!copyFromContinuation) {
newContext->EnsureSameStructsCached(oldContext);
}
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) {
// Ensure the new context ends up resolving all the structs the old
// context resolved.
newContext->EnsureSameStructsCached(oldContext);
}
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)
, mHintsHandledByAncestors(aHintsHandledByAncestors)
, mHintsHandledBySelf(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());
MOZ_ASSERT(!(mHintsHandledByAncestors & nsChangeHint_ReconstructFrame),
"why restyle descendants if we are reconstructing the frame for "
"an ancestor?");
}
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)
, mHintsHandledByAncestors(
// Note that when FOR_OUT_OF_FLOW_CHILD, the out-of-flow may not be a
// geometric descendant of the frame where we started the reresolve.
// Therefore, even if mHintsHandledByAncestors 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.
(aParentRestyler.mHintsHandledByAncestors |
aParentRestyler.mHintsHandledBySelf) &
((aConstructorFlags & FOR_OUT_OF_FLOW_CHILD) ?
~nsChangeHint_AllReflowHints : ~nsChangeHint(0)))
, mHintsHandledBySelf(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());
MOZ_ASSERT(!(mHintsHandledByAncestors & nsChangeHint_ReconstructFrame),
"why restyle descendants if we are reconstructing the frame for "
"an ancestor?");
}
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)
, mHintsHandledByAncestors(aParentRestyler.mHintsHandledByAncestors |
aParentRestyler.mHintsHandledBySelf)
, mHintsHandledBySelf(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());
MOZ_ASSERT(!(mHintsHandledByAncestors & nsChangeHint_ReconstructFrame),
"why restyle descendants if we are reconstructing the frame for "
"an ancestor?");
}
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)
, mHintsHandledByAncestors(aHintsHandledByAncestors)
, mHintsHandledBySelf(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
{
MOZ_ASSERT(!(mHintsHandledByAncestors & nsChangeHint_ReconstructFrame),
"why restyle descendants if we are reconstructing the frame for "
"an ancestor?");
}
void
ElementRestyler::AddLayerChangesForAnimation()
{
uint64_t frameGeneration =
GeckoRestyleManager::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,
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",
GeckoRestyleManager::ChangeHintToString(ourChange).get(),
GeckoRestyleManager::ChangeHintToString(aChangeToAssume).get());
LOG_RESTYLE_INDENT();
// nsChangeHint_UpdateEffects is not handled for descendants, 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;
nsChangeHint changeToAppend =
NS_RemoveSubsumedHints(ourChange, mHintsHandledByAncestors);
// mHintsHandledBySelf starts off as nsChangeHint(0), when restyling a given
// frame, and accumulates change hints for each same-style-continuation and
// {ib}-split sibling following it. Most of the time, any subsequent frames
// we restyle with this ElementRestyler will generate exactly the same
// |changeToAppend| that we have already stored in mHintsHandledBySelf. If
// we generate some hints that weren't handled by an earler same-style-
// continuation or {ib}-split sibling, then we record the entire
// |changeToAppend| value. (We could use something like
// NS_RemoveSubsumedHints, but aimed at removing hints handled only for the
// current element instead. However, we should probably just fix these rare
// cases as part of bug 918064.)
if (!NS_IsHintSubset(changeToAppend, mHintsHandledBySelf)) {
mHintsHandledBySelf |= changeToAppend;
if (!(ourChange & nsChangeHint_ReconstructFrame) || mContent) {
LOG_RESTYLE("appending change %s",
RestyleManager::ChangeHintToString(changeToAppend).get());
mChangeList->AppendChange(mFrame, mContent, changeToAppend);
} else {
LOG_RESTYLE("ignoring ReconstructFrame change with no content");
}
} else {
LOG_RESTYLE("change has already been handled");
}
}
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 = GeckoRestyleManager::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->getNext()) {
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 = GeckoRestyleManager::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 mHintsHandledByAncestors 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)) {
nsChangeHint changeToAppend =
NS_RemoveSubsumedHints(restyleData->mChangeHint,
mHintsHandledByAncestors);
// See the comment in CaptureChange about why we use NS_IsHintSubset here.
if (!NS_IsHintSubset(changeToAppend, mHintsHandledBySelf)) {
mHintsHandledBySelf |= changeToAppend;
mChangeList->AppendChange(mFrame, mContent, changeToAppend);
}
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 = GeckoRestyleManager::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 (!(mHintsHandledBySelf & nsChangeHint_ReconstructFrame)) {
InitializeAccessibilityNotifications(mFrame->StyleContext());
SendAccessibilityNotifications();
}
mRestyleTracker.AddRestyleRootsIfAwaitingRestyle(descendants);
if (aRestyleHint & eRestyle_SomeDescendants) {
ConditionallyRestyleChildren();
}
return;
}
if (result == RestyleResult::eStopWithStyleChange &&
!(mHintsHandledBySelf & 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 (!(mHintsHandledBySelf & 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 mHintsHandledBySelf since we use
// mFrame->StyleContext(), which is out of date if mHintsHandledBySelf
// has a ReconstructFrame hint. Using an out of date style
// context could trigger assertions about mismatched rule trees.
if (!(mHintsHandledBySelf & 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;
}
// Each NAC element inherits from the first non-NAC ancestor, so child
// NAC may inherit from our parent instead of us. That means we can't
// cull traversal if our style context didn't change.
if (aSelf->GetContent() && aSelf->GetContent()->IsNativeAnonymous()) {
LOG_RESTYLE_CONTINUE("native anonymous content");
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::firstLetterContinuation) {
MOZ_ASSERT(parentPseudoTag != nsCSSAnonBoxes::mozText,
"Style of text node should not be parent of anything");
MOZ_ASSERT(parentPseudoTag != nsCSSAnonBoxes::oofPlaceholder,
"Style of placeholder 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(),
RestyleManager::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;
}
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 (pseudoTag == nsCSSAnonBoxes::firstLetterContinuation) {
newContext = styleSet->ResolveStyleForFirstLetterContinuation(parentContext);
} else if (pseudoTag == nsCSSAnonBoxes::oofPlaceholder) {
// We still need to ResolveStyleForPlaceholder() here, because we may be
// doing a ruletree reconstruct and hence actually changing our style
// context.
newContext = styleSet->ResolveStyleForPlaceholder();
} else if (pseudoType == CSSPseudoElementType::NonInheritingAnonBox) {
// We still need to ResolveNonInheritingAnonymousBoxStyle() here, because we
// may be doing a ruletree reconstruct and hence actually changing our style
// context.
newContext = styleSet->ResolveNonInheritingAnonymousBoxStyle(pseudoTag);
}
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::InheritingAnonBox) {
newContext = styleSet->ResolveInheritingAnonymousBoxStyle(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
mHintsHandledBySelf |= 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,
&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",
GeckoRestyleManager::StructNamesToString(
~equalStructs & NS_STYLE_INHERIT_MASK).get());
result = RestyleResult::eContinue;
}
}
} else {
bool changedStyle =
GeckoRestyleManager::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",
GeckoRestyleManager::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 (!(mHintsHandledBySelf & 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 = GeckoRestyleManager::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::InheritingAnonBox &&
extraPseudoType != CSSPseudoElementType::NonInheritingAnonBox)
? mContent->AsElement() : nullptr;
if (extraPseudoType == CSSPseudoElementType::NonInheritingAnonBox) {
newExtraContext =
styleSet->ResolveNonInheritingAnonymousBoxStyle(extraPseudoTag);
} else 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::InheritingAnonBox) {
newExtraContext = styleSet->
ResolveInheritingAnonymousBoxStyle(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 (!(mHintsHandledBySelf & 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(!(mHintsHandledBySelf & 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 mHintsHandledBySelf since we use
// mFrame->StyleContext(), which is out of date if mHintsHandledBySelf
// has a ReconstructFrame hint. Using an out of date style context could
// trigger assertions about mismatched rule trees.
if (!(mHintsHandledBySelf & 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 mHintsHandledBySelf since reresolving the
// kids would use mFrame->StyleContext(), which is out of date if
// mHintsHandledBySelf has a ReconstructFrame hint; doing this could
// trigger assertions about mismatched rule trees.
nsIFrame* lastContinuation;
if (!(mHintsHandledBySelf & nsChangeHint_ReconstructFrame)) {
InitializeAccessibilityNotifications(mFrame->StyleContext());
for (nsIFrame* f = mFrame; f;
f = GeckoRestyleManager::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 (!(mHintsHandledBySelf & nsChangeHint_ReconstructFrame) &&
mightReframePseudos) {
MaybeReframeForAfterPseudo(lastContinuation);
}
}
void
ElementRestyler::RestyleChildrenOfDisplayContentsElement(
nsIFrame* aParentFrame,
nsStyleContext* aNewContext,
nsChangeHint aMinHint,
RestyleTracker& aRestyleTracker,
nsRestyleHint aRestyleHint,
const RestyleHintData& aRestyleHintData)
{
MOZ_ASSERT(!(mHintsHandledBySelf & nsChangeHint_ReconstructFrame),
"why call me?");
const bool mightReframePseudos = aRestyleHint & eRestyle_Subtree;
DoRestyleUndisplayedDescendants(nsRestyleHint(0), mContent, aNewContext);
if (!(mHintsHandledBySelf & nsChangeHint_ReconstructFrame) &&
mightReframePseudos) {
MaybeReframeForPseudo(CSSPseudoElementType::before,
aParentFrame, nullptr, mContent, aNewContext);
}
if (!(mHintsHandledBySelf & nsChangeHint_ReconstructFrame) &&
mightReframePseudos) {
MaybeReframeForPseudo(CSSPseudoElementType::after,
aParentFrame, nullptr, mContent, aNewContext);
}
if (!(mHintsHandledBySelf & 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 (!(mHintsHandledBySelf & 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)
{
PROFILER_LABEL("ElementRestyler", "ComputeStyleChangeFor",
js::ProfileEntry::Category::CSS);
nsIContent* content = aFrame->GetContent();
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 =
GeckoRestyleManager::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->getNext()) {
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");
mHintsHandledBySelf |= 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
GeckoRestyleManager::ComputeAndProcessStyleChange(
nsIFrame* aFrame,
nsChangeHint aMinChange,
RestyleTracker& aRestyleTracker,
nsRestyleHint aRestyleHint,
const RestyleHintData& aRestyleHintData)
{
MOZ_ASSERT(mReframingStyleContexts, "should have rsc");
nsStyleChangeList changeList(StyleBackendType::Gecko);
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
GeckoRestyleManager::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(StyleBackendType::Gecko);
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
GeckoRestyleManager::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
GeckoRestyleManager::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