diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /layout/base/RestyleManagerBase.cpp | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'layout/base/RestyleManagerBase.cpp')
-rw-r--r-- | layout/base/RestyleManagerBase.cpp | 1378 |
1 files changed, 1378 insertions, 0 deletions
diff --git a/layout/base/RestyleManagerBase.cpp b/layout/base/RestyleManagerBase.cpp new file mode 100644 index 000000000..9a5ce43eb --- /dev/null +++ b/layout/base/RestyleManagerBase.cpp @@ -0,0 +1,1378 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#include "mozilla/RestyleManagerBase.h" +#include "mozilla/StyleSetHandle.h" +#include "nsIFrame.h" + +namespace mozilla { + +RestyleManagerBase::RestyleManagerBase(nsPresContext* aPresContext) + : mPresContext(aPresContext) + , mRestyleGeneration(1) + , mHoverGeneration(0) + , mObservingRefreshDriver(false) + , mInStyleRefresh(false) +{ + MOZ_ASSERT(mPresContext); +} + +/** + * Calculates the change hint and the restyle hint for a given content state + * change. + * + * This is called from both Restyle managers. + */ +void +RestyleManagerBase::ContentStateChangedInternal(Element* aElement, + EventStates aStateMask, + nsChangeHint* aOutChangeHint, + nsRestyleHint* aOutRestyleHint) +{ + MOZ_ASSERT(aOutChangeHint); + MOZ_ASSERT(aOutRestyleHint); + + StyleSetHandle styleSet = PresContext()->StyleSet(); + NS_ASSERTION(styleSet, "couldn't get style set"); + + *aOutChangeHint = nsChangeHint(0); + // Any change to a content state that affects which frames we construct + // must lead to a frame reconstruct here if we already have a frame. + // Note that we never decide through non-CSS means to not create frames + // based on content states, so if we already don't have a frame we don't + // need to force a reframe -- if it's needed, the HasStateDependentStyle + // call will handle things. + nsIFrame* primaryFrame = aElement->GetPrimaryFrame(); + CSSPseudoElementType pseudoType = CSSPseudoElementType::NotPseudo; + if (primaryFrame) { + // If it's generated content, ignore LOADING/etc state changes on it. + if (!primaryFrame->IsGeneratedContentFrame() && + aStateMask.HasAtLeastOneOfStates(NS_EVENT_STATE_BROKEN | + NS_EVENT_STATE_USERDISABLED | + NS_EVENT_STATE_SUPPRESSED | + NS_EVENT_STATE_LOADING)) { + *aOutChangeHint = nsChangeHint_ReconstructFrame; + } else { + uint8_t app = primaryFrame->StyleDisplay()->mAppearance; + if (app) { + nsITheme* theme = PresContext()->GetTheme(); + if (theme && + theme->ThemeSupportsWidget(PresContext(), primaryFrame, app)) { + bool repaint = false; + theme->WidgetStateChanged(primaryFrame, app, nullptr, &repaint, + nullptr); + if (repaint) { + *aOutChangeHint |= nsChangeHint_RepaintFrame; + } + } + } + } + + pseudoType = primaryFrame->StyleContext()->GetPseudoType(); + + primaryFrame->ContentStatesChanged(aStateMask); + } + + if (pseudoType >= CSSPseudoElementType::Count) { + *aOutRestyleHint = styleSet->HasStateDependentStyle(aElement, aStateMask); + } else if (nsCSSPseudoElements::PseudoElementSupportsUserActionState( + pseudoType)) { + // If aElement is a pseudo-element, we want to check to see whether there + // are any state-dependent rules applying to that pseudo. + Element* ancestor = + ElementForStyleContext(nullptr, primaryFrame, pseudoType); + *aOutRestyleHint = styleSet->HasStateDependentStyle(ancestor, pseudoType, + aElement, aStateMask); + } else { + *aOutRestyleHint = nsRestyleHint(0); + } + + if (aStateMask.HasState(NS_EVENT_STATE_HOVER) && *aOutRestyleHint != 0) { + IncrementHoverGeneration(); + } + + if (aStateMask.HasState(NS_EVENT_STATE_VISITED)) { + // Exposing information to the page about whether the link is + // visited or not isn't really something we can worry about here. + // FIXME: We could probably do this a bit better. + *aOutChangeHint |= nsChangeHint_RepaintFrame; + } +} + +/* static */ nsCString +RestyleManagerBase::RestyleHintToString(nsRestyleHint aHint) +{ + nsCString result; + bool any = false; + const char* names[] = { + "Self", "SomeDescendants", "Subtree", "LaterSiblings", "CSSTransitions", + "CSSAnimations", "SVGAttrAnimations", "StyleAttribute", + "StyleAttribute_Animations", "Force", "ForceDescendants" + }; + uint32_t hint = aHint & ((1 << ArrayLength(names)) - 1); + uint32_t rest = aHint & ~((1 << ArrayLength(names)) - 1); + for (uint32_t i = 0; i < ArrayLength(names); i++) { + if (hint & (1 << i)) { + if (any) { + result.AppendLiteral(" | "); + } + result.AppendPrintf("eRestyle_%s", names[i]); + any = true; + } + } + if (rest) { + if (any) { + result.AppendLiteral(" | "); + } + result.AppendPrintf("0x%0x", rest); + } else { + if (!any) { + result.AppendLiteral("0"); + } + } + return result; +} + +#ifdef DEBUG +/* static */ nsCString +RestyleManagerBase::ChangeHintToString(nsChangeHint aHint) +{ + nsCString result; + bool any = false; + const char* names[] = { + "RepaintFrame", "NeedReflow", "ClearAncestorIntrinsics", + "ClearDescendantIntrinsics", "NeedDirtyReflow", "SyncFrameView", + "UpdateCursor", "UpdateEffects", "UpdateOpacityLayer", + "UpdateTransformLayer", "ReconstructFrame", "UpdateOverflow", + "UpdateSubtreeOverflow", "UpdatePostTransformOverflow", + "UpdateParentOverflow", + "ChildrenOnlyTransform", "RecomputePosition", "AddOrRemoveTransform", + "BorderStyleNoneChange", "UpdateTextPath", "SchedulePaint", + "NeutralChange", "InvalidateRenderingObservers", + "ReflowChangesSizeOrPosition", "UpdateComputedBSize", + "UpdateUsesOpacity", "UpdateBackgroundPosition", + "AddOrRemoveTransform" + }; + static_assert(nsChangeHint_AllHints == (1 << ArrayLength(names)) - 1, + "Name list doesn't match change hints."); + uint32_t hint = aHint & ((1 << ArrayLength(names)) - 1); + uint32_t rest = aHint & ~((1 << ArrayLength(names)) - 1); + if (hint == nsChangeHint_Hints_NotHandledForDescendants) { + result.AppendLiteral("nsChangeHint_Hints_NotHandledForDescendants"); + hint = 0; + any = true; + } else { + if ((hint & NS_STYLE_HINT_REFLOW) == NS_STYLE_HINT_REFLOW) { + result.AppendLiteral("NS_STYLE_HINT_REFLOW"); + hint = hint & ~NS_STYLE_HINT_REFLOW; + any = true; + } else if ((hint & nsChangeHint_AllReflowHints) == nsChangeHint_AllReflowHints) { + result.AppendLiteral("nsChangeHint_AllReflowHints"); + hint = hint & ~nsChangeHint_AllReflowHints; + any = true; + } else if ((hint & NS_STYLE_HINT_VISUAL) == NS_STYLE_HINT_VISUAL) { + result.AppendLiteral("NS_STYLE_HINT_VISUAL"); + hint = hint & ~NS_STYLE_HINT_VISUAL; + any = true; + } + } + for (uint32_t i = 0; i < ArrayLength(names); i++) { + if (hint & (1 << i)) { + if (any) { + result.AppendLiteral(" | "); + } + result.AppendPrintf("nsChangeHint_%s", names[i]); + any = true; + } + } + if (rest) { + if (any) { + result.AppendLiteral(" | "); + } + result.AppendPrintf("0x%0x", rest); + } else { + if (!any) { + result.AppendLiteral("nsChangeHint(0)"); + } + } + return result; +} +#endif + +void +RestyleManagerBase::PostRestyleEventInternal(bool aForLazyConstruction) +{ + // 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. + bool inRefresh = !aForLazyConstruction && mInStyleRefresh; + nsIPresShell* presShell = PresContext()->PresShell(); + if (!ObservingRefreshDriver() && !inRefresh) { + SetObservingRefreshDriver(PresContext()->RefreshDriver()-> + AddStyleFlushObserver(presShell)); + } + + // 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->GetDocument()->SetNeedStyleFlush(); +} + +/** + * Frame construction helpers follow. + */ +#ifdef DEBUG +static bool gInApplyRenderingChangeToTree = false; +#endif + +#ifdef DEBUG +static void +DumpContext(nsIFrame* aFrame, nsStyleContext* aContext) +{ + if (aFrame) { + fputs("frame: ", stdout); + nsAutoString name; + aFrame->GetFrameName(name); + fputs(NS_LossyConvertUTF16toASCII(name).get(), stdout); + fprintf(stdout, " (%p)", static_cast<void*>(aFrame)); + } + if (aContext) { + fprintf(stdout, " style: %p ", static_cast<void*>(aContext)); + + nsIAtom* pseudoTag = aContext->GetPseudo(); + if (pseudoTag) { + nsAutoString buffer; + pseudoTag->ToString(buffer); + fputs(NS_LossyConvertUTF16toASCII(buffer).get(), stdout); + fputs(" ", stdout); + } + fputs("{}\n", stdout); + } +} + +static void +VerifySameTree(nsStyleContext* aContext1, nsStyleContext* aContext2) +{ + nsStyleContext* top1 = aContext1; + nsStyleContext* top2 = aContext2; + nsStyleContext* parent; + for (;;) { + parent = top1->GetParent(); + if (!parent) + break; + top1 = parent; + } + for (;;) { + parent = top2->GetParent(); + if (!parent) + break; + top2 = parent; + } + NS_ASSERTION(top1 == top2, + "Style contexts are not in the same style context tree"); +} + +static void +VerifyContextParent(nsIFrame* aFrame, nsStyleContext* aContext, + nsStyleContext* aParentContext) +{ + // get the contexts not provided + if (!aContext) { + aContext = aFrame->StyleContext(); + } + + if (!aParentContext) { + nsIFrame* providerFrame; + aParentContext = aFrame->GetParentStyleContext(&providerFrame); + // aParentContext could still be null + } + + NS_ASSERTION(aContext, "Failure to get required contexts"); + nsStyleContext* actualParentContext = aContext->GetParent(); + + if (aParentContext) { + if (aParentContext != actualParentContext) { + DumpContext(aFrame, aContext); + if (aContext == aParentContext) { + NS_ERROR("Using parent's style context"); + } else { + NS_ERROR("Wrong parent style context"); + fputs("Wrong parent style context: ", stdout); + DumpContext(nullptr, actualParentContext); + fputs("should be using: ", stdout); + DumpContext(nullptr, aParentContext); + VerifySameTree(actualParentContext, aParentContext); + fputs("\n", stdout); + } + } + + } else { + if (actualParentContext) { + NS_ERROR("Have parent context and shouldn't"); + DumpContext(aFrame, aContext); + fputs("Has parent context: ", stdout); + DumpContext(nullptr, actualParentContext); + fputs("Should be null\n\n", stdout); + } + } + + nsStyleContext* childStyleIfVisited = aContext->GetStyleIfVisited(); + // Either childStyleIfVisited has aContext->GetParent()->GetStyleIfVisited() + // as the parent or it has a different rulenode from aContext _and_ has + // aContext->GetParent() as the parent. + if (childStyleIfVisited && + !((childStyleIfVisited->RuleNode() != aContext->RuleNode() && + childStyleIfVisited->GetParent() == aContext->GetParent()) || + childStyleIfVisited->GetParent() == + aContext->GetParent()->GetStyleIfVisited())) { + NS_ERROR("Visited style has wrong parent"); + DumpContext(aFrame, aContext); + fputs("\n", stdout); + } +} + +static void +VerifyStyleTree(nsIFrame* aFrame) +{ + nsStyleContext* context = aFrame->StyleContext(); + VerifyContextParent(aFrame, context, nullptr); + + nsIFrame::ChildListIterator lists(aFrame); + for (; !lists.IsDone(); lists.Next()) { + for (nsIFrame* child : lists.CurrentList()) { + if (!(child->GetStateBits() & NS_FRAME_OUT_OF_FLOW)) { + // only do frames that are in flow + if (nsGkAtoms::placeholderFrame == child->GetType()) { + // placeholder: first recurse and verify the out of flow frame, + // then verify the placeholder's context + nsIFrame* outOfFlowFrame = + nsPlaceholderFrame::GetRealFrameForPlaceholder(child); + + // recurse to out of flow frame, letting the parent context get resolved + do { + VerifyStyleTree(outOfFlowFrame); + } while ((outOfFlowFrame = outOfFlowFrame->GetNextContinuation())); + + // verify placeholder using the parent frame's context as + // parent context + VerifyContextParent(child, nullptr, nullptr); + } else { // regular frame + VerifyStyleTree(child); + } + } + } + } + + // do additional contexts + int32_t contextIndex = 0; + for (nsStyleContext* extraContext; + (extraContext = aFrame->GetAdditionalStyleContext(contextIndex)); + ++contextIndex) { + VerifyContextParent(aFrame, extraContext, context); + } +} + +void +RestyleManagerBase::DebugVerifyStyleTree(nsIFrame* aFrame) +{ + if (aFrame) { + VerifyStyleTree(aFrame); + } +} + +#endif // DEBUG + +NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(ChangeListProperty, bool) + +/** + * Sync views on aFrame and all of aFrame's descendants (following placeholders), + * if aChange has nsChangeHint_SyncFrameView. + * Calls DoApplyRenderingChangeToTree on all aFrame's out-of-flow descendants + * (following placeholders), if aChange has nsChangeHint_RepaintFrame. + * aFrame should be some combination of nsChangeHint_SyncFrameView, + * nsChangeHint_RepaintFrame, nsChangeHint_UpdateOpacityLayer and + * nsChangeHint_SchedulePaint, nothing else. +*/ +static void SyncViewsAndInvalidateDescendants(nsIFrame* aFrame, + nsChangeHint aChange); + +static void StyleChangeReflow(nsIFrame* aFrame, nsChangeHint aHint); + +/** + * To handle nsChangeHint_ChildrenOnlyTransform we must iterate over the child + * frames of the SVG frame concerned. This helper function is used to find that + * SVG frame when we encounter nsChangeHint_ChildrenOnlyTransform to ensure + * that we iterate over the intended children, since sometimes we end up + * handling that hint while processing hints for one of the SVG frame's + * ancestor frames. + * + * The reason that we sometimes end up trying to process the hint for an + * ancestor of the SVG frame that the hint is intended for is due to the way we + * process restyle events. ApplyRenderingChangeToTree adjusts the frame from + * the restyled element's principle frame to one of its ancestor frames based + * on what nsCSSRendering::FindBackground returns, since the background style + * may have been propagated up to an ancestor frame. Processing hints using an + * ancestor frame is fine in general, but nsChangeHint_ChildrenOnlyTransform is + * a special case since it is intended to update the children of a specific + * frame. + */ +static nsIFrame* +GetFrameForChildrenOnlyTransformHint(nsIFrame* aFrame) +{ + if (aFrame->GetType() == nsGkAtoms::viewportFrame) { + // This happens if the root-<svg> is fixed positioned, in which case we + // can't use aFrame->GetContent() to find the primary frame, since + // GetContent() returns nullptr for ViewportFrame. + aFrame = aFrame->PrincipalChildList().FirstChild(); + } + // For an nsHTMLScrollFrame, this will get the SVG frame that has the + // children-only transforms: + aFrame = aFrame->GetContent()->GetPrimaryFrame(); + if (aFrame->GetType() == nsGkAtoms::svgOuterSVGFrame) { + aFrame = aFrame->PrincipalChildList().FirstChild(); + MOZ_ASSERT(aFrame->GetType() == nsGkAtoms::svgOuterSVGAnonChildFrame, + "Where is the nsSVGOuterSVGFrame's anon child??"); + } + MOZ_ASSERT(aFrame->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer), + "Children-only transforms only expected on SVG frames"); + return aFrame; +} + +// Returns true if this function managed to successfully move a frame, and +// false if it could not process the position change, and a reflow should +// be performed instead. +bool +RecomputePosition(nsIFrame* aFrame) +{ + // Don't process position changes on table frames, since we already handle + // the dynamic position change on the table wrapper frame, and the + // reflow-based fallback code path also ignores positions on inner table + // frames. + if (aFrame->GetType() == nsGkAtoms::tableFrame) { + return true; + } + + const nsStyleDisplay* display = aFrame->StyleDisplay(); + // Changes to the offsets of a non-positioned element can safely be ignored. + if (display->mPosition == NS_STYLE_POSITION_STATIC) { + return true; + } + + // Don't process position changes on frames which have views or the ones which + // have a view somewhere in their descendants, because the corresponding view + // needs to be repositioned properly as well. + if (aFrame->HasView() || + (aFrame->GetStateBits() & NS_FRAME_HAS_CHILD_WITH_VIEW)) { + StyleChangeReflow(aFrame, nsChangeHint_NeedReflow); + return false; + } + + aFrame->SchedulePaint(); + + // For relative positioning, we can simply update the frame rect + if (display->IsRelativelyPositionedStyle()) { + // Move the frame + if (display->mPosition == NS_STYLE_POSITION_STICKY) { + if (display->IsInnerTableStyle()) { + // We don't currently support sticky positioning of inner table + // elements (bug 975644). Bail. + // + // When this is fixed, remove the null-check for the computed + // offsets in nsTableRowFrame::ReflowChildren. + return true; + } + + // Update sticky positioning for an entire element at once, starting with + // the first continuation or ib-split sibling. + // It's rare that the frame we already have isn't already the first + // continuation or ib-split sibling, but it can happen when styles differ + // across continuations such as ::first-line or ::first-letter, and in + // those cases we will generally (but maybe not always) do the work twice. + nsIFrame* firstContinuation = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); + + StickyScrollContainer::ComputeStickyOffsets(firstContinuation); + StickyScrollContainer* ssc = + StickyScrollContainer::GetStickyScrollContainerForFrame( + firstContinuation); + if (ssc) { + ssc->PositionContinuations(firstContinuation); + } + } else { + MOZ_ASSERT(NS_STYLE_POSITION_RELATIVE == display->mPosition, + "Unexpected type of positioning"); + for (nsIFrame* cont = aFrame; cont; + cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) { + nsIFrame* cb = cont->GetContainingBlock(); + nsMargin newOffsets; + WritingMode wm = cb->GetWritingMode(); + const LogicalSize size(wm, cb->GetContentRectRelativeToSelf().Size()); + + ReflowInput::ComputeRelativeOffsets(wm, cont, size, newOffsets); + NS_ASSERTION(newOffsets.left == -newOffsets.right && + newOffsets.top == -newOffsets.bottom, + "ComputeRelativeOffsets should return valid results"); + + // ReflowInput::ApplyRelativePositioning would work here, but + // since we've already checked mPosition and aren't changing the frame's + // normal position, go ahead and add the offsets directly. + // First, we need to ensure that the normal position is stored though. + nsPoint normalPosition = cont->GetNormalPosition(); + auto props = cont->Properties(); + const auto& prop = nsIFrame::NormalPositionProperty(); + if (!props.Get(prop)) { + props.Set(prop, new nsPoint(normalPosition)); + } + cont->SetPosition(normalPosition + + nsPoint(newOffsets.left, newOffsets.top)); + } + } + + return true; + } + + // For the absolute positioning case, set up a fake HTML reflow state for + // the frame, and then get the offsets and size from it. If the frame's size + // doesn't need to change, we can simply update the frame position. Otherwise + // we fall back to a reflow. + nsRenderingContext rc( + aFrame->PresContext()->PresShell()->CreateReferenceRenderingContext()); + + // Construct a bogus parent reflow state so that there's a usable + // containing block reflow state. + nsIFrame* parentFrame = aFrame->GetParent(); + WritingMode parentWM = parentFrame->GetWritingMode(); + WritingMode frameWM = aFrame->GetWritingMode(); + LogicalSize parentSize = parentFrame->GetLogicalSize(); + + nsFrameState savedState = parentFrame->GetStateBits(); + ReflowInput parentReflowInput(aFrame->PresContext(), parentFrame, &rc, + parentSize); + parentFrame->RemoveStateBits(~nsFrameState(0)); + parentFrame->AddStateBits(savedState); + + // The bogus parent state here was created with no parent state of its own, + // and therefore it won't have an mCBReflowInput set up. + // But we may need one (for InitCBReflowInput in a child state), so let's + // try to create one here for the cases where it will be needed. + Maybe<ReflowInput> cbReflowInput; + nsIFrame* cbFrame = parentFrame->GetContainingBlock(); + if (cbFrame && (aFrame->GetContainingBlock() != parentFrame || + parentFrame->GetType() == nsGkAtoms::tableFrame)) { + LogicalSize cbSize = cbFrame->GetLogicalSize(); + cbReflowInput.emplace(cbFrame->PresContext(), cbFrame, &rc, cbSize); + cbReflowInput->ComputedPhysicalMargin() = cbFrame->GetUsedMargin(); + cbReflowInput->ComputedPhysicalPadding() = cbFrame->GetUsedPadding(); + cbReflowInput->ComputedPhysicalBorderPadding() = + cbFrame->GetUsedBorderAndPadding(); + parentReflowInput.mCBReflowInput = cbReflowInput.ptr(); + } + + NS_WARNING_ASSERTION(parentSize.ISize(parentWM) != NS_INTRINSICSIZE && + parentSize.BSize(parentWM) != NS_INTRINSICSIZE, + "parentSize should be valid"); + parentReflowInput.SetComputedISize(std::max(parentSize.ISize(parentWM), 0)); + parentReflowInput.SetComputedBSize(std::max(parentSize.BSize(parentWM), 0)); + parentReflowInput.ComputedPhysicalMargin().SizeTo(0, 0, 0, 0); + + parentReflowInput.ComputedPhysicalPadding() = parentFrame->GetUsedPadding(); + parentReflowInput.ComputedPhysicalBorderPadding() = + parentFrame->GetUsedBorderAndPadding(); + LogicalSize availSize = parentSize.ConvertTo(frameWM, parentWM); + availSize.BSize(frameWM) = NS_INTRINSICSIZE; + + ViewportFrame* viewport = do_QueryFrame(parentFrame); + nsSize cbSize = viewport ? + viewport->AdjustReflowInputAsContainingBlock(&parentReflowInput).Size() + : aFrame->GetContainingBlock()->GetSize(); + const nsMargin& parentBorder = + parentReflowInput.mStyleBorder->GetComputedBorder(); + cbSize -= nsSize(parentBorder.LeftRight(), parentBorder.TopBottom()); + LogicalSize lcbSize(frameWM, cbSize); + ReflowInput reflowInput(aFrame->PresContext(), parentReflowInput, aFrame, + availSize, &lcbSize); + nsSize computedSize(reflowInput.ComputedWidth(), + reflowInput.ComputedHeight()); + computedSize.width += reflowInput.ComputedPhysicalBorderPadding().LeftRight(); + if (computedSize.height != NS_INTRINSICSIZE) { + computedSize.height += + reflowInput.ComputedPhysicalBorderPadding().TopBottom(); + } + nsSize size = aFrame->GetSize(); + // The RecomputePosition hint is not used if any offset changed between auto + // and non-auto. If computedSize.height == NS_INTRINSICSIZE then the new + // element height will be its intrinsic height, and since 'top' and 'bottom''s + // auto-ness hasn't changed, the old height must also be its intrinsic + // height, which we can assume hasn't changed (or reflow would have + // been triggered). + if (computedSize.width == size.width && + (computedSize.height == NS_INTRINSICSIZE || computedSize.height == size.height)) { + // If we're solving for 'left' or 'top', then compute it here, in order to + // match the reflow code path. + if (NS_AUTOOFFSET == reflowInput.ComputedPhysicalOffsets().left) { + reflowInput.ComputedPhysicalOffsets().left = cbSize.width - + reflowInput.ComputedPhysicalOffsets().right - + reflowInput.ComputedPhysicalMargin().right - + size.width - + reflowInput.ComputedPhysicalMargin().left; + } + + if (NS_AUTOOFFSET == reflowInput.ComputedPhysicalOffsets().top) { + reflowInput.ComputedPhysicalOffsets().top = cbSize.height - + reflowInput.ComputedPhysicalOffsets().bottom - + reflowInput.ComputedPhysicalMargin().bottom - + size.height - + reflowInput.ComputedPhysicalMargin().top; + } + + // Move the frame + nsPoint pos(parentBorder.left + reflowInput.ComputedPhysicalOffsets().left + + reflowInput.ComputedPhysicalMargin().left, + parentBorder.top + reflowInput.ComputedPhysicalOffsets().top + + reflowInput.ComputedPhysicalMargin().top); + aFrame->SetPosition(pos); + + return true; + } + + // Fall back to a reflow + StyleChangeReflow(aFrame, nsChangeHint_NeedReflow); + return false; +} + +static bool +HasBoxAncestor(nsIFrame* aFrame) +{ + for (nsIFrame* f = aFrame; f; f = f->GetParent()) { + if (f->IsXULBoxFrame()) { + return true; + } + } + return false; +} + +/** + * Return true if aFrame's subtree has placeholders for out-of-flow content + * whose 'position' style's bit in aPositionMask is set. + */ +static bool +FrameHasPositionedPlaceholderDescendants(nsIFrame* aFrame, + uint32_t aPositionMask) +{ + MOZ_ASSERT(aPositionMask & (1 << NS_STYLE_POSITION_FIXED)); + + for (nsIFrame::ChildListIterator lists(aFrame); !lists.IsDone(); lists.Next()) { + for (nsIFrame* f : lists.CurrentList()) { + if (f->GetType() == nsGkAtoms::placeholderFrame) { + nsIFrame* outOfFlow = + nsPlaceholderFrame::GetRealFrameForPlaceholder(f); + // If SVG text frames could appear here, they could confuse us since + // they ignore their position style ... but they can't. + NS_ASSERTION(!outOfFlow->IsSVGText(), + "SVG text frames can't be out of flow"); + if (aPositionMask & (1 << outOfFlow->StyleDisplay()->mPosition)) { + return true; + } + } + uint32_t positionMask = aPositionMask; + // NOTE: It's tempting to check f->IsAbsPosContainingBlock() or + // f->IsFixedPosContainingBlock() here. However, that would only + // be testing the *new* style of the frame, which might exclude + // descendants that currently have this frame as an abs-pos + // containing block. Taking the codepath where we don't reframe + // could lead to an unsafe call to + // cont->MarkAsNotAbsoluteContainingBlock() before we've reframed + // the descendant and taken it off the absolute list. + if (FrameHasPositionedPlaceholderDescendants(f, positionMask)) { + return true; + } + } + } + return false; +} + +static bool +NeedToReframeForAddingOrRemovingTransform(nsIFrame* aFrame) +{ + static_assert(0 <= NS_STYLE_POSITION_ABSOLUTE && + NS_STYLE_POSITION_ABSOLUTE < 32, "Style constant out of range"); + static_assert(0 <= NS_STYLE_POSITION_FIXED && + NS_STYLE_POSITION_FIXED < 32, "Style constant out of range"); + + uint32_t positionMask; + // Don't call aFrame->IsPositioned here, since that returns true if + // the frame already has a transform, and we want to ignore that here + if (aFrame->IsAbsolutelyPositioned() || aFrame->IsRelativelyPositioned()) { + // This frame is a container for abs-pos descendants whether or not it + // has a transform. + // So abs-pos descendants are no problem; we only need to reframe if + // we have fixed-pos descendants. + positionMask = 1 << NS_STYLE_POSITION_FIXED; + } else { + // This frame may not be a container for abs-pos descendants already. + // So reframe if we have abs-pos or fixed-pos descendants. + positionMask = + (1 << NS_STYLE_POSITION_FIXED) | (1 << NS_STYLE_POSITION_ABSOLUTE); + } + for (nsIFrame* f = aFrame; f; + f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) { + if (FrameHasPositionedPlaceholderDescendants(f, positionMask)) { + return true; + } + } + return false; +} + +/* static */ nsIFrame* +RestyleManagerBase::GetNearestAncestorFrame(nsIContent* aContent) +{ + nsIFrame* ancestorFrame = nullptr; + for (nsIContent* ancestor = aContent->GetParent(); + ancestor && !ancestorFrame; + ancestor = ancestor->GetParent()) { + ancestorFrame = ancestor->GetPrimaryFrame(); + } + return ancestorFrame; +} + +/* static */ nsIFrame* +RestyleManagerBase::GetNextBlockInInlineSibling(FramePropertyTable* aPropTable, + nsIFrame* aFrame) +{ + NS_ASSERTION(!aFrame->GetPrevContinuation(), + "must start with the first continuation"); + // Might we have ib-split siblings? + if (!(aFrame->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT)) { + // nothing more to do here + return nullptr; + } + + return static_cast<nsIFrame*> + (aPropTable->Get(aFrame, nsIFrame::IBSplitSibling())); +} + +static void +DoApplyRenderingChangeToTree(nsIFrame* aFrame, + nsChangeHint aChange) +{ + NS_PRECONDITION(gInApplyRenderingChangeToTree, + "should only be called within ApplyRenderingChangeToTree"); + + for ( ; aFrame; aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame)) { + // Invalidate and sync views on all descendant frames, following placeholders. + // We don't need to update transforms in SyncViewsAndInvalidateDescendants, because + // there can't be any out-of-flows or popups that need to be transformed; + // all out-of-flow descendants of the transformed element must also be + // descendants of the transformed frame. + SyncViewsAndInvalidateDescendants(aFrame, + nsChangeHint(aChange & (nsChangeHint_RepaintFrame | + nsChangeHint_SyncFrameView | + nsChangeHint_UpdateOpacityLayer | + nsChangeHint_SchedulePaint))); + // This must be set to true if the rendering change needs to + // invalidate content. If it's false, a composite-only paint + // (empty transaction) will be scheduled. + bool needInvalidatingPaint = false; + + // if frame has view, will already be invalidated + if (aChange & nsChangeHint_RepaintFrame) { + // Note that this whole block will be skipped when painting is suppressed + // (due to our caller ApplyRendingChangeToTree() discarding the + // nsChangeHint_RepaintFrame hint). If you add handling for any other + // hints within this block, be sure that they too should be ignored when + // painting is suppressed. + needInvalidatingPaint = true; + aFrame->InvalidateFrameSubtree(); + if ((aChange & nsChangeHint_UpdateEffects) && + aFrame->IsFrameOfType(nsIFrame::eSVG) && + !(aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG)) { + // Need to update our overflow rects: + nsSVGUtils::ScheduleReflowSVG(aFrame); + } + } + if (aChange & nsChangeHint_UpdateTextPath) { + if (aFrame->IsSVGText()) { + // Invalidate and reflow the entire SVGTextFrame: + NS_ASSERTION(aFrame->GetContent()->IsSVGElement(nsGkAtoms::textPath), + "expected frame for a <textPath> element"); + nsIFrame* text = + nsLayoutUtils::GetClosestFrameOfType(aFrame, nsGkAtoms::svgTextFrame); + NS_ASSERTION(text, "expected to find an ancestor SVGTextFrame"); + static_cast<SVGTextFrame*>(text)->NotifyGlyphMetricsChange(); + } else { + MOZ_ASSERT(false, "unexpected frame got nsChangeHint_UpdateTextPath"); + } + } + if (aChange & nsChangeHint_UpdateOpacityLayer) { + // FIXME/bug 796697: we can get away with empty transactions for + // opacity updates in many cases. + needInvalidatingPaint = true; + + ActiveLayerTracker::NotifyRestyle(aFrame, eCSSProperty_opacity); + if (nsSVGIntegrationUtils::UsingEffectsForFrame(aFrame)) { + // SVG effects paints the opacity without using + // nsDisplayOpacity. We need to invalidate manually. + aFrame->InvalidateFrameSubtree(); + } + } + if ((aChange & nsChangeHint_UpdateTransformLayer) && + aFrame->IsTransformed()) { + ActiveLayerTracker::NotifyRestyle(aFrame, eCSSProperty_transform); + // If we're not already going to do an invalidating paint, see + // if we can get away with only updating the transform on a + // layer for this frame, and not scheduling an invalidating + // paint. + if (!needInvalidatingPaint) { + Layer* layer; + needInvalidatingPaint |= !aFrame->TryUpdateTransformOnly(&layer); + + if (!needInvalidatingPaint) { + // Since we're not going to paint, we need to resend animation + // data to the layer. + MOZ_ASSERT(layer, "this can't happen if there's no layer"); + nsDisplayListBuilder::AddAnimationsAndTransitionsToLayer( + layer, nullptr, nullptr, aFrame, eCSSProperty_transform); + } + } + } + if (aChange & nsChangeHint_ChildrenOnlyTransform) { + needInvalidatingPaint = true; + nsIFrame* childFrame = + GetFrameForChildrenOnlyTransformHint(aFrame)->PrincipalChildList().FirstChild(); + for ( ; childFrame; childFrame = childFrame->GetNextSibling()) { + ActiveLayerTracker::NotifyRestyle(childFrame, eCSSProperty_transform); + } + } + if (aChange & nsChangeHint_SchedulePaint) { + needInvalidatingPaint = true; + } + aFrame->SchedulePaint(needInvalidatingPaint + ? nsIFrame::PAINT_DEFAULT + : nsIFrame::PAINT_COMPOSITE_ONLY); + } +} + +static void +SyncViewsAndInvalidateDescendants(nsIFrame* aFrame, nsChangeHint aChange) +{ + NS_PRECONDITION(gInApplyRenderingChangeToTree, + "should only be called within ApplyRenderingChangeToTree"); + NS_ASSERTION(nsChangeHint_size_t(aChange) == + (aChange & (nsChangeHint_RepaintFrame | + nsChangeHint_SyncFrameView | + nsChangeHint_UpdateOpacityLayer | + nsChangeHint_SchedulePaint)), + "Invalid change flag"); + + nsView* view = aFrame->GetView(); + if (view) { + if (aChange & nsChangeHint_SyncFrameView) { + nsContainerFrame::SyncFrameViewProperties(aFrame->PresContext(), aFrame, + nullptr, view); + } + } + + nsIFrame::ChildListIterator lists(aFrame); + for (; !lists.IsDone(); lists.Next()) { + for (nsIFrame* child : lists.CurrentList()) { + if (!(child->GetStateBits() & NS_FRAME_OUT_OF_FLOW)) { + // only do frames that don't have placeholders + if (nsGkAtoms::placeholderFrame == child->GetType()) { + // do the out-of-flow frame and its continuations + nsIFrame* outOfFlowFrame = + nsPlaceholderFrame::GetRealFrameForPlaceholder(child); + DoApplyRenderingChangeToTree(outOfFlowFrame, aChange); + } else if (lists.CurrentID() == nsIFrame::kPopupList) { + DoApplyRenderingChangeToTree(child, aChange); + } else { // regular frame + SyncViewsAndInvalidateDescendants(child, aChange); + } + } + } + } +} + +static void +ApplyRenderingChangeToTree(nsIPresShell* aPresShell, + nsIFrame* aFrame, + nsChangeHint aChange) +{ + // We check StyleDisplay()->HasTransformStyle() in addition to checking + // IsTransformed() since we can get here for some frames that don't support + // CSS transforms. + NS_ASSERTION(!(aChange & nsChangeHint_UpdateTransformLayer) || + aFrame->IsTransformed() || + aFrame->StyleDisplay()->HasTransformStyle(), + "Unexpected UpdateTransformLayer hint"); + + if (aPresShell->IsPaintingSuppressed()) { + // Don't allow synchronous rendering changes when painting is turned off. + aChange &= ~nsChangeHint_RepaintFrame; + if (!aChange) { + return; + } + } + +// Trigger rendering updates by damaging this frame and any +// continuations of this frame. +#ifdef DEBUG + gInApplyRenderingChangeToTree = true; +#endif + if (aChange & nsChangeHint_RepaintFrame) { + // If the frame's background is propagated to an ancestor, walk up to + // that ancestor and apply the RepaintFrame change hint to it. + nsStyleContext* bgSC; + nsIFrame* propagatedFrame = aFrame; + while (!nsCSSRendering::FindBackground(propagatedFrame, &bgSC)) { + propagatedFrame = propagatedFrame->GetParent(); + NS_ASSERTION(aFrame, "root frame must paint"); + } + + if (propagatedFrame != aFrame) { + DoApplyRenderingChangeToTree(propagatedFrame, nsChangeHint_RepaintFrame); + aChange &= ~nsChangeHint_RepaintFrame; + if (!aChange) { + return; + } + } + } + DoApplyRenderingChangeToTree(aFrame, aChange); +#ifdef DEBUG + gInApplyRenderingChangeToTree = false; +#endif +} + +static void +AddSubtreeToOverflowTracker(nsIFrame* aFrame, + OverflowChangedTracker& aOverflowChangedTracker) +{ + if (aFrame->FrameMaintainsOverflow()) { + aOverflowChangedTracker.AddFrame(aFrame, + OverflowChangedTracker::CHILDREN_CHANGED); + } + nsIFrame::ChildListIterator lists(aFrame); + for (; !lists.IsDone(); lists.Next()) { + for (nsIFrame* child : lists.CurrentList()) { + AddSubtreeToOverflowTracker(child, aOverflowChangedTracker); + } + } +} + +static void +StyleChangeReflow(nsIFrame* aFrame, nsChangeHint aHint) +{ + nsIPresShell::IntrinsicDirty dirtyType; + if (aHint & nsChangeHint_ClearDescendantIntrinsics) { + NS_ASSERTION(aHint & nsChangeHint_ClearAncestorIntrinsics, + "Please read the comments in nsChangeHint.h"); + NS_ASSERTION(aHint & nsChangeHint_NeedDirtyReflow, + "ClearDescendantIntrinsics requires NeedDirtyReflow"); + dirtyType = nsIPresShell::eStyleChange; + } else if ((aHint & nsChangeHint_UpdateComputedBSize) && + aFrame->HasAnyStateBits( + NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE)) { + dirtyType = nsIPresShell::eStyleChange; + } else if (aHint & nsChangeHint_ClearAncestorIntrinsics) { + dirtyType = nsIPresShell::eTreeChange; + } else if ((aHint & nsChangeHint_UpdateComputedBSize) && + HasBoxAncestor(aFrame)) { + // The frame's computed BSize is changing, and we have a box ancestor + // whose cached intrinsic height may need to be updated. + dirtyType = nsIPresShell::eTreeChange; + } else { + dirtyType = nsIPresShell::eResize; + } + + nsFrameState dirtyBits; + if (aFrame->GetStateBits() & NS_FRAME_FIRST_REFLOW) { + dirtyBits = nsFrameState(0); + } else if ((aHint & nsChangeHint_NeedDirtyReflow) || + dirtyType == nsIPresShell::eStyleChange) { + dirtyBits = NS_FRAME_IS_DIRTY; + } else { + dirtyBits = NS_FRAME_HAS_DIRTY_CHILDREN; + } + + // If we're not going to clear any intrinsic sizes on the frames, and + // there are no dirty bits to set, then there's nothing to do. + if (dirtyType == nsIPresShell::eResize && !dirtyBits) + return; + + nsIPresShell::ReflowRootHandling rootHandling; + if (aHint & nsChangeHint_ReflowChangesSizeOrPosition) { + rootHandling = nsIPresShell::ePositionOrSizeChange; + } else { + rootHandling = nsIPresShell::eNoPositionOrSizeChange; + } + + do { + aFrame->PresContext()->PresShell()->FrameNeedsReflow( + aFrame, dirtyType, dirtyBits, rootHandling); + aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame); + } while (aFrame); +} + +/* static */ nsIFrame* +RestyleManagerBase::GetNextContinuationWithSameStyle( + nsIFrame* aFrame, nsStyleContext* aOldStyleContext, + bool* aHaveMoreContinuations) +{ + // See GetPrevContinuationWithSameStyle about {ib} splits. + + nsIFrame* nextContinuation = aFrame->GetNextContinuation(); + if (!nextContinuation && + (aFrame->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT)) { + // We're the last continuation, so we have to hop back to the first + // before getting the frame property + nextContinuation = + aFrame->FirstContinuation()->Properties().Get(nsIFrame::IBSplitSibling()); + if (nextContinuation) { + nextContinuation = + nextContinuation->Properties().Get(nsIFrame::IBSplitSibling()); + } + } + + if (!nextContinuation) { + return nullptr; + } + + NS_ASSERTION(nextContinuation->GetContent() == aFrame->GetContent(), + "unexpected content mismatch"); + + nsStyleContext* nextStyle = nextContinuation->StyleContext(); + if (nextStyle != aOldStyleContext) { + NS_ASSERTION(aOldStyleContext->GetPseudo() != nextStyle->GetPseudo() || + aOldStyleContext->GetParent() != nextStyle->GetParent(), + "continuations should have the same style context"); + nextContinuation = nullptr; + if (aHaveMoreContinuations) { + *aHaveMoreContinuations = true; + } + } + return nextContinuation; +} + +nsresult +RestyleManagerBase::ProcessRestyledFrames(nsStyleChangeList& aChangeList) +{ + NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(), + "Someone forgot a script blocker"); + if (aChangeList.IsEmpty()) + return NS_OK; + + PROFILER_LABEL("RestyleManager", "ProcessRestyledFrames", + js::ProfileEntry::Category::CSS); + + nsPresContext* presContext = PresContext(); + FramePropertyTable* propTable = presContext->PropertyTable(); + nsCSSFrameConstructor* frameConstructor = presContext->FrameConstructor(); + + // Make sure to not rebuild quote or counter lists while we're + // processing restyles + frameConstructor->BeginUpdate(); + + // Mark frames so that we skip frames that die along the way, bug 123049. + // A frame can be in the list multiple times with different hints. Further + // optmization is possible if nsStyleChangeList::AppendChange could coalesce + for (const nsStyleChangeData& data : aChangeList) { + if (data.mFrame) { + propTable->Set(data.mFrame, ChangeListProperty(), true); + } + } + + bool didUpdateCursor = false; + + for (const nsStyleChangeData& data : aChangeList) { + nsIFrame* frame = data.mFrame; + nsIContent* content = data.mContent; + nsChangeHint hint = data.mHint; + bool didReflowThisFrame = false; + + NS_ASSERTION(!(hint & nsChangeHint_AllReflowHints) || + (hint & nsChangeHint_NeedReflow), + "Reflow hint bits set without actually asking for a reflow"); + + // skip any frame that has been destroyed due to a ripple effect + if (frame && !propTable->Get(frame, ChangeListProperty())) { + continue; + } + + if (frame && frame->GetContent() != content) { + // XXXbz this is due to image maps messing with the primary frame of + // <area>s. See bug 135040. Remove this block once that's fixed. + frame = nullptr; + if (!(hint & nsChangeHint_ReconstructFrame)) { + continue; + } + } + + if ((hint & nsChangeHint_UpdateContainingBlock) && frame && + !(hint & nsChangeHint_ReconstructFrame)) { + if (NeedToReframeForAddingOrRemovingTransform(frame) || + frame->GetType() == nsGkAtoms::fieldSetFrame || + frame->GetContentInsertionFrame() != frame) { + // The frame has positioned children that need to be reparented, or + // it can't easily be converted to/from being an abs-pos container correctly. + hint |= nsChangeHint_ReconstructFrame; + } else { + for (nsIFrame* cont = frame; cont; + cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) { + // Normally frame construction would set state bits as needed, + // but we're not going to reconstruct the frame so we need to set them. + // It's because we need to set this state on each affected frame + // that we can't coalesce nsChangeHint_UpdateContainingBlock hints up + // to ancestors (i.e. it can't be an change hint that is handled for + // descendants). + if (cont->IsAbsPosContainingBlock()) { + if (!cont->IsAbsoluteContainer() && + (cont->GetStateBits() & NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN)) { + cont->MarkAsAbsoluteContainingBlock(); + } + } else { + if (cont->IsAbsoluteContainer()) { + if (cont->HasAbsolutelyPositionedChildren()) { + // If |cont| still has absolutely positioned children, + // we can't call MarkAsNotAbsoluteContainingBlock. This + // will remove a frame list that still has children in + // it that we need to keep track of. + // The optimization of removing it isn't particularly + // important, although it does mean we skip some tests. + NS_WARNING("skipping removal of absolute containing block"); + } else { + cont->MarkAsNotAbsoluteContainingBlock(); + } + } + } + } + } + } + + if ((hint & nsChangeHint_AddOrRemoveTransform) && frame && + !(hint & nsChangeHint_ReconstructFrame)) { + for (nsIFrame* cont = frame; cont; + cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) { + if (cont->StyleDisplay()->HasTransform(cont)) { + cont->AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED); + } + // Don't remove NS_FRAME_MAY_BE_TRANSFORMED since it may still be + // transformed by other means. It's OK to have the bit even if it's + // not needed. + } + } + + if (hint & nsChangeHint_ReconstructFrame) { + // If we ever start passing true here, be careful of restyles + // that involve a reframe and animations. In particular, if the + // restyle we're processing here is an animation restyle, but + // the style resolution we will do for the frame construction + // happens async when we're not in an animation restyle already, + // problems could arise. + // We could also have problems with triggering of CSS transitions + // on elements whose frames are reconstructed, since we depend on + // the reconstruction happening synchronously. + frameConstructor->RecreateFramesForContent(content, false, + nsCSSFrameConstructor::REMOVE_FOR_RECONSTRUCTION, nullptr); + } else { + NS_ASSERTION(frame, "This shouldn't happen"); + + if (!frame->FrameMaintainsOverflow()) { + // frame does not maintain overflow rects, so avoid calling + // FinishAndStoreOverflow on it: + hint &= ~(nsChangeHint_UpdateOverflow | + nsChangeHint_ChildrenOnlyTransform | + nsChangeHint_UpdatePostTransformOverflow | + nsChangeHint_UpdateParentOverflow); + } + + if (!(frame->GetStateBits() & NS_FRAME_MAY_BE_TRANSFORMED)) { + // Frame can not be transformed, and thus a change in transform will + // have no effect and we should not use the + // nsChangeHint_UpdatePostTransformOverflow hint. + hint &= ~nsChangeHint_UpdatePostTransformOverflow; + } + + if (hint & nsChangeHint_UpdateEffects) { + for (nsIFrame* cont = frame; cont; + cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) { + nsSVGEffects::UpdateEffects(cont); + } + } + if ((hint & nsChangeHint_InvalidateRenderingObservers) || + ((hint & nsChangeHint_UpdateOpacityLayer) && + frame->IsFrameOfType(nsIFrame::eSVG) && + !(frame->GetStateBits() & NS_STATE_IS_OUTER_SVG))) { + nsSVGEffects::InvalidateRenderingObservers(frame); + } + if (hint & nsChangeHint_NeedReflow) { + StyleChangeReflow(frame, hint); + didReflowThisFrame = true; + } + + if ((hint & nsChangeHint_UpdateUsesOpacity) && + frame->IsFrameOfType(nsIFrame::eTablePart)) { + NS_ASSERTION(hint & nsChangeHint_UpdateOpacityLayer, + "should only return UpdateUsesOpacity hint " + "when also returning UpdateOpacityLayer hint"); + // When an internal table part (including cells) changes between + // having opacity 1 and non-1, it changes whether its + // backgrounds (and those of table parts inside of it) are + // painted as part of the table's nsDisplayTableBorderBackground + // display item, or part of its own display item. That requires + // invalidation, so change UpdateOpacityLayer to RepaintFrame. + hint &= ~nsChangeHint_UpdateOpacityLayer; + hint |= nsChangeHint_RepaintFrame; + } + + // Opacity disables preserve-3d, so if we toggle it, then we also need + // to update the overflow areas of all potentially affected frames. + if ((hint & nsChangeHint_UpdateUsesOpacity) && + frame->StyleDisplay()->mTransformStyle == NS_STYLE_TRANSFORM_STYLE_PRESERVE_3D) { + hint |= nsChangeHint_UpdateSubtreeOverflow; + } + + if (hint & nsChangeHint_UpdateBackgroundPosition) { + // For most frame types, DLBI can detect background position changes, + // so we only need to schedule a paint. + hint |= nsChangeHint_SchedulePaint; + if (frame->IsFrameOfType(nsIFrame::eTablePart) || + frame->IsFrameOfType(nsIFrame::eMathML)) { + // Table parts and MathML frames don't build display items for their + // backgrounds, so DLBI can't detect background-position changes for + // these frames. Repaint the whole frame. + hint |= nsChangeHint_RepaintFrame; + } + } + + if (hint & (nsChangeHint_RepaintFrame | nsChangeHint_SyncFrameView | + nsChangeHint_UpdateOpacityLayer | nsChangeHint_UpdateTransformLayer | + nsChangeHint_ChildrenOnlyTransform | nsChangeHint_SchedulePaint)) { + ApplyRenderingChangeToTree(presContext->PresShell(), frame, hint); + } + if ((hint & nsChangeHint_RecomputePosition) && !didReflowThisFrame) { + ActiveLayerTracker::NotifyOffsetRestyle(frame); + // It is possible for this to fall back to a reflow + if (!RecomputePosition(frame)) { + didReflowThisFrame = true; + } + } + NS_ASSERTION(!(hint & nsChangeHint_ChildrenOnlyTransform) || + (hint & nsChangeHint_UpdateOverflow), + "nsChangeHint_UpdateOverflow should be passed too"); + if (!didReflowThisFrame && + (hint & (nsChangeHint_UpdateOverflow | + nsChangeHint_UpdatePostTransformOverflow | + nsChangeHint_UpdateParentOverflow | + nsChangeHint_UpdateSubtreeOverflow))) { + if (hint & nsChangeHint_UpdateSubtreeOverflow) { + for (nsIFrame* cont = frame; cont; cont = + nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) { + AddSubtreeToOverflowTracker(cont, mOverflowChangedTracker); + } + // The work we just did in AddSubtreeToOverflowTracker + // subsumes some of the other hints: + hint &= ~(nsChangeHint_UpdateOverflow | + nsChangeHint_UpdatePostTransformOverflow); + } + if (hint & nsChangeHint_ChildrenOnlyTransform) { + // The overflow areas of the child frames need to be updated: + nsIFrame* hintFrame = GetFrameForChildrenOnlyTransformHint(frame); + nsIFrame* childFrame = hintFrame->PrincipalChildList().FirstChild(); + NS_ASSERTION(!nsLayoutUtils::GetNextContinuationOrIBSplitSibling(frame), + "SVG frames should not have continuations " + "or ib-split siblings"); + NS_ASSERTION(!nsLayoutUtils::GetNextContinuationOrIBSplitSibling(hintFrame), + "SVG frames should not have continuations " + "or ib-split siblings"); + for ( ; childFrame; childFrame = childFrame->GetNextSibling()) { + MOZ_ASSERT(childFrame->IsFrameOfType(nsIFrame::eSVG), + "Not expecting non-SVG children"); + // If |childFrame| is dirty or has dirty children, we don't bother + // updating overflows since that will happen when it's reflowed. + if (!(childFrame->GetStateBits() & + (NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN))) { + mOverflowChangedTracker.AddFrame(childFrame, + OverflowChangedTracker::CHILDREN_CHANGED); + } + NS_ASSERTION(!nsLayoutUtils::GetNextContinuationOrIBSplitSibling(childFrame), + "SVG frames should not have continuations " + "or ib-split siblings"); + NS_ASSERTION(childFrame->GetParent() == hintFrame, + "SVG child frame not expected to have different parent"); + } + } + // If |frame| is dirty or has dirty children, we don't bother updating + // overflows since that will happen when it's reflowed. + if (!(frame->GetStateBits() & + (NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN))) { + if (hint & (nsChangeHint_UpdateOverflow | + nsChangeHint_UpdatePostTransformOverflow)) { + OverflowChangedTracker::ChangeKind changeKind; + // If we have both nsChangeHint_UpdateOverflow and + // nsChangeHint_UpdatePostTransformOverflow, + // CHILDREN_CHANGED is selected as it is + // strictly stronger. + if (hint & nsChangeHint_UpdateOverflow) { + changeKind = OverflowChangedTracker::CHILDREN_CHANGED; + } else { + changeKind = OverflowChangedTracker::TRANSFORM_CHANGED; + } + for (nsIFrame* cont = frame; cont; cont = + nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) { + mOverflowChangedTracker.AddFrame(cont, changeKind); + } + } + // UpdateParentOverflow hints need to be processed in addition + // to the above, since if the processing of the above hints + // yields no change, the update will not propagate to the + // parent. + if (hint & nsChangeHint_UpdateParentOverflow) { + MOZ_ASSERT(frame->GetParent(), + "shouldn't get style hints for the root frame"); + for (nsIFrame* cont = frame; cont; cont = + nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) { + mOverflowChangedTracker.AddFrame(cont->GetParent(), + OverflowChangedTracker::CHILDREN_CHANGED); + } + } + } + } + if ((hint & nsChangeHint_UpdateCursor) && !didUpdateCursor) { + presContext->PresShell()->SynthesizeMouseMove(false); + didUpdateCursor = true; + } + } + } + + frameConstructor->EndUpdate(); + + // cleanup references and verify the style tree. Note that the latter needs + // to happen once we've processed the whole list, since until then the tree + // is not in fact in a consistent state. + for (const nsStyleChangeData& data : aChangeList) { + if (data.mFrame) { + propTable->Delete(data.mFrame, ChangeListProperty()); + } + +#ifdef DEBUG + // reget frame from content since it may have been regenerated... + if (data.mContent) { + nsIFrame* frame = data.mContent->GetPrimaryFrame(); + if (frame) { + DebugVerifyStyleTree(frame); + } + } else if (!data.mFrame || + data.mFrame->GetType() != nsGkAtoms::viewportFrame) { + NS_WARNING("Unable to test style tree integrity -- no content node " + "(and not a viewport frame)"); + } +#endif + } + + aChangeList.Clear(); + return NS_OK; +} + +} // namespace mozilla |