michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /** michael@0: * Code responsible for managing style changes: tracking what style michael@0: * changes need to happen, scheduling them, and doing them. michael@0: */ michael@0: michael@0: #include "RestyleManager.h" michael@0: #include "mozilla/EventStates.h" michael@0: #include "nsLayoutUtils.h" michael@0: #include "GeckoProfiler.h" michael@0: #include "nsStyleChangeList.h" michael@0: #include "nsRuleProcessorData.h" michael@0: #include "nsStyleUtil.h" michael@0: #include "nsCSSFrameConstructor.h" michael@0: #include "nsSVGEffects.h" michael@0: #include "nsCSSRendering.h" michael@0: #include "nsAnimationManager.h" michael@0: #include "nsTransitionManager.h" michael@0: #include "nsViewManager.h" michael@0: #include "nsRenderingContext.h" michael@0: #include "nsSVGIntegrationUtils.h" michael@0: #include "nsCSSAnonBoxes.h" michael@0: #include "nsContainerFrame.h" michael@0: #include "nsPlaceholderFrame.h" michael@0: #include "nsBlockFrame.h" michael@0: #include "nsViewportFrame.h" michael@0: #include "SVGTextFrame.h" michael@0: #include "StickyScrollContainer.h" michael@0: #include "nsIRootBox.h" michael@0: #include "nsIDOMMutationEvent.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsIFrameInlines.h" michael@0: #include "ActiveLayerTracker.h" michael@0: #include "nsDisplayList.h" michael@0: michael@0: #ifdef ACCESSIBILITY michael@0: #include "nsAccessibilityService.h" michael@0: #endif michael@0: michael@0: namespace mozilla { michael@0: michael@0: using namespace layers; michael@0: michael@0: RestyleManager::RestyleManager(nsPresContext* aPresContext) michael@0: : mPresContext(aPresContext) michael@0: , mRebuildAllStyleData(false) michael@0: , mObservingRefreshDriver(false) michael@0: , mInStyleRefresh(false) michael@0: , mHoverGeneration(0) michael@0: , mRebuildAllExtraHint(nsChangeHint(0)) michael@0: , mAnimationGeneration(0) michael@0: , mPendingRestyles(ELEMENT_HAS_PENDING_RESTYLE | michael@0: ELEMENT_IS_POTENTIAL_RESTYLE_ROOT) michael@0: , mPendingAnimationRestyles(ELEMENT_HAS_PENDING_ANIMATION_RESTYLE | michael@0: ELEMENT_IS_POTENTIAL_ANIMATION_RESTYLE_ROOT) michael@0: { michael@0: mPendingRestyles.Init(this); michael@0: mPendingAnimationRestyles.Init(this); michael@0: } michael@0: michael@0: void michael@0: RestyleManager::NotifyDestroyingFrame(nsIFrame* aFrame) michael@0: { michael@0: mOverflowChangedTracker.RemoveFrame(aFrame); michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: // To ensure that the functions below are only called within michael@0: // |ApplyRenderingChangeToTree|. michael@0: static bool gInApplyRenderingChangeToTree = false; michael@0: #endif michael@0: michael@0: static void michael@0: DoApplyRenderingChangeToTree(nsIFrame* aFrame, michael@0: nsChangeHint aChange); michael@0: michael@0: /** michael@0: * Sync views on aFrame and all of aFrame's descendants (following placeholders), michael@0: * if aChange has nsChangeHint_SyncFrameView. michael@0: * Calls DoApplyRenderingChangeToTree on all aFrame's out-of-flow descendants michael@0: * (following placeholders), if aChange has nsChangeHint_RepaintFrame. michael@0: * aFrame should be some combination of nsChangeHint_SyncFrameView and michael@0: * nsChangeHint_RepaintFrame and nsChangeHint_UpdateOpacityLayer, nothing else. michael@0: */ michael@0: static void michael@0: SyncViewsAndInvalidateDescendants(nsIFrame* aFrame, michael@0: nsChangeHint aChange) michael@0: { michael@0: NS_PRECONDITION(gInApplyRenderingChangeToTree, michael@0: "should only be called within ApplyRenderingChangeToTree"); michael@0: NS_ASSERTION(aChange == (aChange & (nsChangeHint_RepaintFrame | michael@0: nsChangeHint_SyncFrameView | michael@0: nsChangeHint_UpdateOpacityLayer)), michael@0: "Invalid change flag"); michael@0: michael@0: nsView* view = aFrame->GetView(); michael@0: if (view) { michael@0: if (aChange & nsChangeHint_SyncFrameView) { michael@0: nsContainerFrame::SyncFrameViewProperties(aFrame->PresContext(), michael@0: aFrame, nullptr, view); michael@0: } michael@0: } michael@0: michael@0: nsIFrame::ChildListIterator lists(aFrame); michael@0: for (; !lists.IsDone(); lists.Next()) { michael@0: nsFrameList::Enumerator childFrames(lists.CurrentList()); michael@0: for (; !childFrames.AtEnd(); childFrames.Next()) { michael@0: nsIFrame* child = childFrames.get(); michael@0: if (!(child->GetStateBits() & NS_FRAME_OUT_OF_FLOW)) { michael@0: // only do frames that don't have placeholders michael@0: if (nsGkAtoms::placeholderFrame == child->GetType()) { michael@0: // do the out-of-flow frame and its continuations michael@0: nsIFrame* outOfFlowFrame = michael@0: nsPlaceholderFrame::GetRealFrameForPlaceholder(child); michael@0: DoApplyRenderingChangeToTree(outOfFlowFrame, aChange); michael@0: } else if (lists.CurrentID() == nsIFrame::kPopupList) { michael@0: DoApplyRenderingChangeToTree(child, aChange); michael@0: } else { // regular frame michael@0: SyncViewsAndInvalidateDescendants(child, aChange); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * To handle nsChangeHint_ChildrenOnlyTransform we must iterate over the child michael@0: * frames of the SVG frame concerned. This helper function is used to find that michael@0: * SVG frame when we encounter nsChangeHint_ChildrenOnlyTransform to ensure michael@0: * that we iterate over the intended children, since sometimes we end up michael@0: * handling that hint while processing hints for one of the SVG frame's michael@0: * ancestor frames. michael@0: * michael@0: * The reason that we sometimes end up trying to process the hint for an michael@0: * ancestor of the SVG frame that the hint is intended for is due to the way we michael@0: * process restyle events. ApplyRenderingChangeToTree adjusts the frame from michael@0: * the restyled element's principle frame to one of its ancestor frames based michael@0: * on what nsCSSRendering::FindBackground returns, since the background style michael@0: * may have been propagated up to an ancestor frame. Processing hints using an michael@0: * ancestor frame is fine in general, but nsChangeHint_ChildrenOnlyTransform is michael@0: * a special case since it is intended to update the children of a specific michael@0: * frame. michael@0: */ michael@0: static nsIFrame* michael@0: GetFrameForChildrenOnlyTransformHint(nsIFrame *aFrame) michael@0: { michael@0: if (aFrame->GetType() == nsGkAtoms::viewportFrame) { michael@0: // This happens if the root- is fixed positioned, in which case we michael@0: // can't use aFrame->GetContent() to find the primary frame, since michael@0: // GetContent() returns nullptr for ViewportFrame. michael@0: aFrame = aFrame->GetFirstPrincipalChild(); michael@0: } michael@0: // For an nsHTMLScrollFrame, this will get the SVG frame that has the michael@0: // children-only transforms: michael@0: aFrame = aFrame->GetContent()->GetPrimaryFrame(); michael@0: if (aFrame->GetType() == nsGkAtoms::svgOuterSVGFrame) { michael@0: aFrame = aFrame->GetFirstPrincipalChild(); michael@0: NS_ABORT_IF_FALSE(aFrame->GetType() == nsGkAtoms::svgOuterSVGAnonChildFrame, michael@0: "Where is the nsSVGOuterSVGFrame's anon child??"); michael@0: } michael@0: NS_ABORT_IF_FALSE(aFrame->IsFrameOfType(nsIFrame::eSVG | michael@0: nsIFrame::eSVGContainer), michael@0: "Children-only transforms only expected on SVG frames"); michael@0: return aFrame; michael@0: } michael@0: michael@0: static void michael@0: DoApplyRenderingChangeToTree(nsIFrame* aFrame, michael@0: nsChangeHint aChange) michael@0: { michael@0: NS_PRECONDITION(gInApplyRenderingChangeToTree, michael@0: "should only be called within ApplyRenderingChangeToTree"); michael@0: michael@0: for ( ; aFrame; aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame)) { michael@0: // Invalidate and sync views on all descendant frames, following placeholders. michael@0: // We don't need to update transforms in SyncViewsAndInvalidateDescendants, because michael@0: // there can't be any out-of-flows or popups that need to be transformed; michael@0: // all out-of-flow descendants of the transformed element must also be michael@0: // descendants of the transformed frame. michael@0: SyncViewsAndInvalidateDescendants(aFrame, michael@0: nsChangeHint(aChange & (nsChangeHint_RepaintFrame | michael@0: nsChangeHint_SyncFrameView | michael@0: nsChangeHint_UpdateOpacityLayer))); michael@0: // This must be set to true if the rendering change needs to michael@0: // invalidate content. If it's false, a composite-only paint michael@0: // (empty transaction) will be scheduled. michael@0: bool needInvalidatingPaint = false; michael@0: michael@0: // if frame has view, will already be invalidated michael@0: if (aChange & nsChangeHint_RepaintFrame) { michael@0: // Note that this whole block will be skipped when painting is suppressed michael@0: // (due to our caller ApplyRendingChangeToTree() discarding the michael@0: // nsChangeHint_RepaintFrame hint). If you add handling for any other michael@0: // hints within this block, be sure that they too should be ignored when michael@0: // painting is suppressed. michael@0: needInvalidatingPaint = true; michael@0: aFrame->InvalidateFrameSubtree(); michael@0: if (aChange & nsChangeHint_UpdateEffects && michael@0: aFrame->IsFrameOfType(nsIFrame::eSVG) && michael@0: !(aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG)) { michael@0: // Need to update our overflow rects: michael@0: nsSVGUtils::ScheduleReflowSVG(aFrame); michael@0: } michael@0: } michael@0: if (aChange & nsChangeHint_UpdateTextPath) { michael@0: if (aFrame->IsSVGText()) { michael@0: // Invalidate and reflow the entire SVGTextFrame: michael@0: NS_ASSERTION(aFrame->GetContent()->IsSVG(nsGkAtoms::textPath), michael@0: "expected frame for a element"); michael@0: nsIFrame* text = nsLayoutUtils::GetClosestFrameOfType( michael@0: aFrame, michael@0: nsGkAtoms::svgTextFrame); michael@0: NS_ASSERTION(text, "expected to find an ancestor SVGTextFrame"); michael@0: static_cast(text)->NotifyGlyphMetricsChange(); michael@0: } else { michael@0: NS_ABORT_IF_FALSE(false, "unexpected frame got " michael@0: "nsChangeHint_UpdateTextPath"); michael@0: } michael@0: } michael@0: if (aChange & nsChangeHint_UpdateOpacityLayer) { michael@0: // FIXME/bug 796697: we can get away with empty transactions for michael@0: // opacity updates in many cases. michael@0: needInvalidatingPaint = true; michael@0: michael@0: ActiveLayerTracker::NotifyRestyle(aFrame, eCSSProperty_opacity); michael@0: if (nsSVGIntegrationUtils::UsingEffectsForFrame(aFrame)) { michael@0: // SVG effects paints the opacity without using michael@0: // nsDisplayOpacity. We need to invalidate manually. michael@0: aFrame->InvalidateFrameSubtree(); michael@0: } michael@0: } michael@0: if ((aChange & nsChangeHint_UpdateTransformLayer) && michael@0: aFrame->IsTransformed()) { michael@0: ActiveLayerTracker::NotifyRestyle(aFrame, eCSSProperty_transform); michael@0: // If we're not already going to do an invalidating paint, see michael@0: // if we can get away with only updating the transform on a michael@0: // layer for this frame, and not scheduling an invalidating michael@0: // paint. michael@0: if (!needInvalidatingPaint) { michael@0: Layer* layer; michael@0: needInvalidatingPaint |= !aFrame->TryUpdateTransformOnly(&layer); michael@0: michael@0: if (!needInvalidatingPaint) { michael@0: // Since we're not going to paint, we need to resend animation michael@0: // data to the layer. michael@0: MOZ_ASSERT(layer, "this can't happen if there's no layer"); michael@0: nsDisplayListBuilder::AddAnimationsAndTransitionsToLayer(layer, michael@0: nullptr, nullptr, aFrame, eCSSProperty_transform); michael@0: } michael@0: } michael@0: } michael@0: if (aChange & nsChangeHint_ChildrenOnlyTransform) { michael@0: needInvalidatingPaint = true; michael@0: nsIFrame* childFrame = michael@0: GetFrameForChildrenOnlyTransformHint(aFrame)->GetFirstPrincipalChild(); michael@0: for ( ; childFrame; childFrame = childFrame->GetNextSibling()) { michael@0: ActiveLayerTracker::NotifyRestyle(childFrame, eCSSProperty_transform); michael@0: } michael@0: } michael@0: aFrame->SchedulePaint(needInvalidatingPaint ? michael@0: nsIFrame::PAINT_DEFAULT : michael@0: nsIFrame::PAINT_COMPOSITE_ONLY); michael@0: } michael@0: } michael@0: michael@0: static void michael@0: ApplyRenderingChangeToTree(nsPresContext* aPresContext, michael@0: nsIFrame* aFrame, michael@0: nsChangeHint aChange) michael@0: { michael@0: // We check StyleDisplay()->HasTransform() in addition to checking michael@0: // IsTransformed() since we can get here for some frames that don't support michael@0: // CSS transforms. michael@0: NS_ASSERTION(!(aChange & nsChangeHint_UpdateTransformLayer) || michael@0: aFrame->IsTransformed() || michael@0: aFrame->StyleDisplay()->HasTransformStyle(), michael@0: "Unexpected UpdateTransformLayer hint"); michael@0: michael@0: nsIPresShell *shell = aPresContext->PresShell(); michael@0: if (shell->IsPaintingSuppressed()) { michael@0: // Don't allow synchronous rendering changes when painting is turned off. michael@0: aChange = NS_SubtractHint(aChange, nsChangeHint_RepaintFrame); michael@0: if (!aChange) { michael@0: return; michael@0: } michael@0: } michael@0: michael@0: // If the frame's background is propagated to an ancestor, walk up to michael@0: // that ancestor. michael@0: nsStyleContext *bgSC; michael@0: while (!nsCSSRendering::FindBackground(aFrame, &bgSC)) { michael@0: aFrame = aFrame->GetParent(); michael@0: NS_ASSERTION(aFrame, "root frame must paint"); michael@0: } michael@0: michael@0: // Trigger rendering updates by damaging this frame and any michael@0: // continuations of this frame. michael@0: michael@0: // XXX this needs to detect the need for a view due to an opacity change and deal with it... michael@0: michael@0: #ifdef DEBUG michael@0: gInApplyRenderingChangeToTree = true; michael@0: #endif michael@0: DoApplyRenderingChangeToTree(aFrame, aChange); michael@0: #ifdef DEBUG michael@0: gInApplyRenderingChangeToTree = false; michael@0: #endif michael@0: } michael@0: michael@0: bool michael@0: RestyleManager::RecomputePosition(nsIFrame* aFrame) michael@0: { michael@0: // Don't process position changes on table frames, since we already handle michael@0: // the dynamic position change on the outer table frame, and the reflow-based michael@0: // fallback code path also ignores positions on inner table frames. michael@0: if (aFrame->GetType() == nsGkAtoms::tableFrame) { michael@0: return true; michael@0: } michael@0: michael@0: const nsStyleDisplay* display = aFrame->StyleDisplay(); michael@0: // Changes to the offsets of a non-positioned element can safely be ignored. michael@0: if (display->mPosition == NS_STYLE_POSITION_STATIC) { michael@0: return true; michael@0: } michael@0: michael@0: // Don't process position changes on frames which have views or the ones which michael@0: // have a view somewhere in their descendants, because the corresponding view michael@0: // needs to be repositioned properly as well. michael@0: if (aFrame->HasView() || michael@0: (aFrame->GetStateBits() & NS_FRAME_HAS_CHILD_WITH_VIEW)) { michael@0: StyleChangeReflow(aFrame, nsChangeHint_NeedReflow); michael@0: return false; michael@0: } michael@0: michael@0: aFrame->SchedulePaint(); michael@0: michael@0: // For relative positioning, we can simply update the frame rect michael@0: if (display->IsRelativelyPositionedStyle()) { michael@0: if (display->IsInnerTableStyle()) { michael@0: // We don't currently support relative positioning of inner table michael@0: // elements (bug 35168). If we apply offsets to things we haven't michael@0: // previously offset, we'll get confused. So bail. michael@0: return true; michael@0: } michael@0: michael@0: michael@0: // Move the frame michael@0: if (display->mPosition == NS_STYLE_POSITION_STICKY) { michael@0: // Update sticky positioning for an entire element at once when michael@0: // RecomputePosition is called with the first continuation in a chain. michael@0: StickyScrollContainer::ComputeStickyOffsets(aFrame); michael@0: StickyScrollContainer* ssc = michael@0: StickyScrollContainer::GetStickyScrollContainerForFrame(aFrame); michael@0: if (ssc) { michael@0: ssc->PositionContinuations(aFrame); michael@0: } michael@0: } else { michael@0: MOZ_ASSERT(NS_STYLE_POSITION_RELATIVE == display->mPosition, michael@0: "Unexpected type of positioning"); michael@0: for (nsIFrame *cont = aFrame; cont; michael@0: cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) { michael@0: nsIFrame* cb = cont->GetContainingBlock(); michael@0: nsMargin newOffsets; michael@0: const nsSize size = cb->GetContentRectRelativeToSelf().Size(); michael@0: michael@0: nsHTMLReflowState::ComputeRelativeOffsets( michael@0: cb->StyleVisibility()->mDirection, michael@0: cont, size.width, size.height, newOffsets); michael@0: NS_ASSERTION(newOffsets.left == -newOffsets.right && michael@0: newOffsets.top == -newOffsets.bottom, michael@0: "ComputeRelativeOffsets should return valid results"); michael@0: michael@0: // nsHTMLReflowState::ApplyRelativePositioning would work here, but michael@0: // since we've already checked mPosition and aren't changing the frame's michael@0: // normal position, go ahead and add the offsets directly. michael@0: cont->SetPosition(cont->GetNormalPosition() + michael@0: nsPoint(newOffsets.left, newOffsets.top)); michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: // For the absolute positioning case, set up a fake HTML reflow state for michael@0: // the frame, and then get the offsets and size from it. If the frame's size michael@0: // doesn't need to change, we can simply update the frame position. Otherwise michael@0: // we fall back to a reflow. michael@0: nsRefPtr rc = michael@0: aFrame->PresContext()->PresShell()->CreateReferenceRenderingContext(); michael@0: michael@0: // Construct a bogus parent reflow state so that there's a usable michael@0: // containing block reflow state. michael@0: nsIFrame* parentFrame = aFrame->GetParent(); michael@0: nsSize parentSize = parentFrame->GetSize(); michael@0: michael@0: nsFrameState savedState = parentFrame->GetStateBits(); michael@0: nsHTMLReflowState parentReflowState(aFrame->PresContext(), parentFrame, michael@0: rc, parentSize); michael@0: parentFrame->RemoveStateBits(~nsFrameState(0)); michael@0: parentFrame->AddStateBits(savedState); michael@0: michael@0: NS_WARN_IF_FALSE(parentSize.width != NS_INTRINSICSIZE && michael@0: parentSize.height != NS_INTRINSICSIZE, michael@0: "parentSize should be valid"); michael@0: parentReflowState.SetComputedWidth(std::max(parentSize.width, 0)); michael@0: parentReflowState.SetComputedHeight(std::max(parentSize.height, 0)); michael@0: parentReflowState.ComputedPhysicalMargin().SizeTo(0, 0, 0, 0); michael@0: michael@0: parentReflowState.ComputedPhysicalPadding() = parentFrame->GetUsedPadding(); michael@0: parentReflowState.ComputedPhysicalBorderPadding() = michael@0: parentFrame->GetUsedBorderAndPadding(); michael@0: nsSize availSize(parentSize.width, NS_INTRINSICSIZE); michael@0: michael@0: ViewportFrame* viewport = do_QueryFrame(parentFrame); michael@0: nsSize cbSize = viewport ? michael@0: viewport->AdjustReflowStateAsContainingBlock(&parentReflowState).Size() michael@0: : aFrame->GetContainingBlock()->GetSize(); michael@0: const nsMargin& parentBorder = michael@0: parentReflowState.mStyleBorder->GetComputedBorder(); michael@0: cbSize -= nsSize(parentBorder.LeftRight(), parentBorder.TopBottom()); michael@0: nsHTMLReflowState reflowState(aFrame->PresContext(), parentReflowState, michael@0: aFrame, availSize, cbSize.width, michael@0: cbSize.height); michael@0: nsSize computedSize(reflowState.ComputedWidth(), reflowState.ComputedHeight()); michael@0: computedSize.width += reflowState.ComputedPhysicalBorderPadding().LeftRight(); michael@0: if (computedSize.height != NS_INTRINSICSIZE) { michael@0: computedSize.height += reflowState.ComputedPhysicalBorderPadding().TopBottom(); michael@0: } michael@0: nsSize size = aFrame->GetSize(); michael@0: // The RecomputePosition hint is not used if any offset changed between auto michael@0: // and non-auto. If computedSize.height == NS_INTRINSICSIZE then the new michael@0: // element height will be its intrinsic height, and since 'top' and 'bottom''s michael@0: // auto-ness hasn't changed, the old height must also be its intrinsic michael@0: // height, which we can assume hasn't changed (or reflow would have michael@0: // been triggered). michael@0: if (computedSize.width == size.width && michael@0: (computedSize.height == NS_INTRINSICSIZE || computedSize.height == size.height)) { michael@0: // If we're solving for 'left' or 'top', then compute it here, in order to michael@0: // match the reflow code path. michael@0: if (NS_AUTOOFFSET == reflowState.ComputedPhysicalOffsets().left) { michael@0: reflowState.ComputedPhysicalOffsets().left = cbSize.width - michael@0: reflowState.ComputedPhysicalOffsets().right - michael@0: reflowState.ComputedPhysicalMargin().right - michael@0: size.width - michael@0: reflowState.ComputedPhysicalMargin().left; michael@0: } michael@0: michael@0: if (NS_AUTOOFFSET == reflowState.ComputedPhysicalOffsets().top) { michael@0: reflowState.ComputedPhysicalOffsets().top = cbSize.height - michael@0: reflowState.ComputedPhysicalOffsets().bottom - michael@0: reflowState.ComputedPhysicalMargin().bottom - michael@0: size.height - michael@0: reflowState.ComputedPhysicalMargin().top; michael@0: } michael@0: michael@0: // Move the frame michael@0: nsPoint pos(parentBorder.left + reflowState.ComputedPhysicalOffsets().left + michael@0: reflowState.ComputedPhysicalMargin().left, michael@0: parentBorder.top + reflowState.ComputedPhysicalOffsets().top + michael@0: reflowState.ComputedPhysicalMargin().top); michael@0: aFrame->SetPosition(pos); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: // Fall back to a reflow michael@0: StyleChangeReflow(aFrame, nsChangeHint_NeedReflow); michael@0: return false; michael@0: } michael@0: michael@0: nsresult michael@0: RestyleManager::StyleChangeReflow(nsIFrame* aFrame, nsChangeHint aHint) michael@0: { michael@0: nsIPresShell::IntrinsicDirty dirtyType; michael@0: if (aHint & nsChangeHint_ClearDescendantIntrinsics) { michael@0: NS_ASSERTION(aHint & nsChangeHint_ClearAncestorIntrinsics, michael@0: "Please read the comments in nsChangeHint.h"); michael@0: dirtyType = nsIPresShell::eStyleChange; michael@0: } else if (aHint & nsChangeHint_ClearAncestorIntrinsics) { michael@0: dirtyType = nsIPresShell::eTreeChange; michael@0: } else { michael@0: dirtyType = nsIPresShell::eResize; michael@0: } michael@0: michael@0: nsFrameState dirtyBits; michael@0: if (aFrame->GetStateBits() & NS_FRAME_FIRST_REFLOW) { michael@0: dirtyBits = nsFrameState(0); michael@0: } else if (aHint & nsChangeHint_NeedDirtyReflow) { michael@0: dirtyBits = NS_FRAME_IS_DIRTY; michael@0: } else { michael@0: dirtyBits = NS_FRAME_HAS_DIRTY_CHILDREN; michael@0: } michael@0: michael@0: // If we're not going to clear any intrinsic sizes on the frames, and michael@0: // there are no dirty bits to set, then there's nothing to do. michael@0: if (dirtyType == nsIPresShell::eResize && !dirtyBits) michael@0: return NS_OK; michael@0: michael@0: do { michael@0: mPresContext->PresShell()->FrameNeedsReflow(aFrame, dirtyType, dirtyBits); michael@0: aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame); michael@0: } while (aFrame); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_DECLARE_FRAME_PROPERTY(ChangeListProperty, nullptr) michael@0: michael@0: /** michael@0: * Return true if aFrame's subtree has placeholders for out-of-flow content michael@0: * whose 'position' style's bit in aPositionMask is set. michael@0: */ michael@0: static bool michael@0: FrameHasPositionedPlaceholderDescendants(nsIFrame* aFrame, uint32_t aPositionMask) michael@0: { michael@0: const nsIFrame::ChildListIDs skip(nsIFrame::kAbsoluteList | michael@0: nsIFrame::kFixedList); michael@0: for (nsIFrame::ChildListIterator lists(aFrame); !lists.IsDone(); lists.Next()) { michael@0: if (!skip.Contains(lists.CurrentID())) { michael@0: for (nsFrameList::Enumerator childFrames(lists.CurrentList()); michael@0: !childFrames.AtEnd(); childFrames.Next()) { michael@0: nsIFrame* f = childFrames.get(); michael@0: if (f->GetType() == nsGkAtoms::placeholderFrame) { michael@0: nsIFrame* outOfFlow = nsPlaceholderFrame::GetRealFrameForPlaceholder(f); michael@0: // If SVG text frames could appear here, they could confuse us since michael@0: // they ignore their position style ... but they can't. michael@0: NS_ASSERTION(!outOfFlow->IsSVGText(), michael@0: "SVG text frames can't be out of flow"); michael@0: if (aPositionMask & (1 << outOfFlow->StyleDisplay()->mPosition)) { michael@0: return true; michael@0: } michael@0: } michael@0: if (FrameHasPositionedPlaceholderDescendants(f, aPositionMask)) { michael@0: return true; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: static bool michael@0: NeedToReframeForAddingOrRemovingTransform(nsIFrame* aFrame) michael@0: { michael@0: static_assert(0 <= NS_STYLE_POSITION_ABSOLUTE && michael@0: NS_STYLE_POSITION_ABSOLUTE < 32, "Style constant out of range"); michael@0: static_assert(0 <= NS_STYLE_POSITION_FIXED && michael@0: NS_STYLE_POSITION_FIXED < 32, "Style constant out of range"); michael@0: michael@0: uint32_t positionMask; michael@0: // Don't call aFrame->IsPositioned here, since that returns true if michael@0: // the frame already has a transform, and we want to ignore that here michael@0: if (aFrame->IsAbsolutelyPositioned() || michael@0: aFrame->IsRelativelyPositioned()) { michael@0: // This frame is a container for abs-pos descendants whether or not it michael@0: // has a transform. michael@0: // So abs-pos descendants are no problem; we only need to reframe if michael@0: // we have fixed-pos descendants. michael@0: positionMask = 1 << NS_STYLE_POSITION_FIXED; michael@0: } else { michael@0: // This frame may not be a container for abs-pos descendants already. michael@0: // So reframe if we have abs-pos or fixed-pos descendants. michael@0: positionMask = (1 << NS_STYLE_POSITION_FIXED) | michael@0: (1 << NS_STYLE_POSITION_ABSOLUTE); michael@0: } michael@0: for (nsIFrame* f = aFrame; f; michael@0: f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) { michael@0: if (FrameHasPositionedPlaceholderDescendants(f, positionMask)) { michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: nsresult michael@0: RestyleManager::ProcessRestyledFrames(nsStyleChangeList& aChangeList) michael@0: { michael@0: NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(), michael@0: "Someone forgot a script blocker"); michael@0: int32_t count = aChangeList.Count(); michael@0: if (!count) michael@0: return NS_OK; michael@0: michael@0: PROFILER_LABEL("CSS", "ProcessRestyledFrames"); michael@0: michael@0: // Make sure to not rebuild quote or counter lists while we're michael@0: // processing restyles michael@0: FrameConstructor()->BeginUpdate(); michael@0: michael@0: FramePropertyTable* propTable = mPresContext->PropertyTable(); michael@0: michael@0: // Mark frames so that we skip frames that die along the way, bug 123049. michael@0: // A frame can be in the list multiple times with different hints. Further michael@0: // optmization is possible if nsStyleChangeList::AppendChange could coalesce michael@0: int32_t index = count; michael@0: michael@0: while (0 <= --index) { michael@0: const nsStyleChangeData* changeData; michael@0: aChangeList.ChangeAt(index, &changeData); michael@0: if (changeData->mFrame) { michael@0: propTable->Set(changeData->mFrame, ChangeListProperty(), michael@0: NS_INT32_TO_PTR(1)); michael@0: } michael@0: } michael@0: michael@0: index = count; michael@0: michael@0: bool didUpdateCursor = false; michael@0: michael@0: while (0 <= --index) { michael@0: nsIFrame* frame; michael@0: nsIContent* content; michael@0: bool didReflowThisFrame = false; michael@0: nsChangeHint hint; michael@0: aChangeList.ChangeAt(index, frame, content, hint); michael@0: michael@0: NS_ASSERTION(!(hint & nsChangeHint_AllReflowHints) || michael@0: (hint & nsChangeHint_NeedReflow), michael@0: "Reflow hint bits set without actually asking for a reflow"); michael@0: michael@0: // skip any frame that has been destroyed due to a ripple effect michael@0: if (frame && !propTable->Get(frame, ChangeListProperty())) { michael@0: continue; michael@0: } michael@0: michael@0: if (frame && frame->GetContent() != content) { michael@0: // XXXbz this is due to image maps messing with the primary frame of michael@0: // s. See bug 135040. Remove this block once that's fixed. michael@0: frame = nullptr; michael@0: if (!(hint & nsChangeHint_ReconstructFrame)) { michael@0: continue; michael@0: } michael@0: } michael@0: michael@0: if ((hint & nsChangeHint_AddOrRemoveTransform) && frame && michael@0: !(hint & nsChangeHint_ReconstructFrame)) { michael@0: if (NeedToReframeForAddingOrRemovingTransform(frame) || michael@0: frame->GetType() == nsGkAtoms::fieldSetFrame || michael@0: frame->GetContentInsertionFrame() != frame) { michael@0: // The frame has positioned children that need to be reparented, or michael@0: // it can't easily be converted to/from being an abs-pos container correctly. michael@0: NS_UpdateHint(hint, nsChangeHint_ReconstructFrame); michael@0: } else { michael@0: for (nsIFrame *cont = frame; cont; michael@0: cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) { michael@0: // Normally frame construction would set state bits as needed, michael@0: // but we're not going to reconstruct the frame so we need to set them. michael@0: // It's because we need to set this state on each affected frame michael@0: // that we can't coalesce nsChangeHint_AddOrRemoveTransform hints up michael@0: // to ancestors (i.e. it can't be an inherited change hint). michael@0: if (cont->IsPositioned()) { michael@0: // If a transform has been added, we'll be taking this path, michael@0: // but we may be taking this path even if a transform has been michael@0: // removed. It's OK to add the bit even if it's not needed. michael@0: cont->AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED); michael@0: if (!cont->IsAbsoluteContainer() && michael@0: (cont->GetStateBits() & NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN)) { michael@0: cont->MarkAsAbsoluteContainingBlock(); michael@0: } michael@0: } else { michael@0: // Don't remove NS_FRAME_MAY_BE_TRANSFORMED since it may still by michael@0: // transformed by other means. It's OK to have the bit even if it's michael@0: // not needed. michael@0: if (cont->IsAbsoluteContainer()) { michael@0: cont->MarkAsNotAbsoluteContainingBlock(); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: if (hint & nsChangeHint_ReconstructFrame) { michael@0: // If we ever start passing true here, be careful of restyles michael@0: // that involve a reframe and animations. In particular, if the michael@0: // restyle we're processing here is an animation restyle, but michael@0: // the style resolution we will do for the frame construction michael@0: // happens async when we're not in an animation restyle already, michael@0: // problems could arise. michael@0: FrameConstructor()->RecreateFramesForContent(content, false); michael@0: } else { michael@0: NS_ASSERTION(frame, "This shouldn't happen"); michael@0: michael@0: if ((frame->GetStateBits() & NS_FRAME_SVG_LAYOUT) && michael@0: (frame->GetStateBits() & NS_FRAME_IS_NONDISPLAY)) { michael@0: // frame does not maintain overflow rects, so avoid calling michael@0: // FinishAndStoreOverflow on it: michael@0: hint = NS_SubtractHint(hint, michael@0: NS_CombineHint(nsChangeHint_UpdateOverflow, michael@0: NS_CombineHint(nsChangeHint_ChildrenOnlyTransform, michael@0: nsChangeHint_UpdatePostTransformOverflow))); michael@0: } michael@0: michael@0: if (!(frame->GetStateBits() & NS_FRAME_MAY_BE_TRANSFORMED)) { michael@0: // Frame can not be transformed, and thus a change in transform will michael@0: // have no effect and we should not use the michael@0: // nsChangeHint_UpdatePostTransformOverflow hint. michael@0: hint = NS_SubtractHint(hint, nsChangeHint_UpdatePostTransformOverflow); michael@0: } michael@0: michael@0: if (hint & nsChangeHint_UpdateEffects) { michael@0: for (nsIFrame *cont = frame; cont; michael@0: cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) { michael@0: nsSVGEffects::UpdateEffects(cont); michael@0: } michael@0: } michael@0: if (hint & nsChangeHint_NeedReflow) { michael@0: StyleChangeReflow(frame, hint); michael@0: didReflowThisFrame = true; michael@0: } michael@0: if (hint & (nsChangeHint_RepaintFrame | nsChangeHint_SyncFrameView | michael@0: nsChangeHint_UpdateOpacityLayer | nsChangeHint_UpdateTransformLayer | michael@0: nsChangeHint_ChildrenOnlyTransform)) { michael@0: ApplyRenderingChangeToTree(mPresContext, frame, hint); michael@0: } michael@0: if ((hint & nsChangeHint_RecomputePosition) && !didReflowThisFrame) { michael@0: ActiveLayerTracker::NotifyOffsetRestyle(frame); michael@0: // It is possible for this to fall back to a reflow michael@0: if (!RecomputePosition(frame)) { michael@0: didReflowThisFrame = true; michael@0: } michael@0: } michael@0: NS_ASSERTION(!(hint & nsChangeHint_ChildrenOnlyTransform) || michael@0: (hint & nsChangeHint_UpdateOverflow), michael@0: "nsChangeHint_UpdateOverflow should be passed too"); michael@0: if (!didReflowThisFrame && michael@0: (hint & (nsChangeHint_UpdateOverflow | michael@0: nsChangeHint_UpdatePostTransformOverflow))) { michael@0: OverflowChangedTracker::ChangeKind changeKind; michael@0: if (hint & nsChangeHint_ChildrenOnlyTransform) { michael@0: // The overflow areas of the child frames need to be updated: michael@0: nsIFrame* hintFrame = GetFrameForChildrenOnlyTransformHint(frame); michael@0: nsIFrame* childFrame = hintFrame->GetFirstPrincipalChild(); michael@0: NS_ASSERTION(!nsLayoutUtils::GetNextContinuationOrIBSplitSibling(frame), michael@0: "SVG frames should not have continuations " michael@0: "or ib-split siblings"); michael@0: NS_ASSERTION(!nsLayoutUtils::GetNextContinuationOrIBSplitSibling(hintFrame), michael@0: "SVG frames should not have continuations " michael@0: "or ib-split siblings"); michael@0: for ( ; childFrame; childFrame = childFrame->GetNextSibling()) { michael@0: NS_ABORT_IF_FALSE(childFrame->IsFrameOfType(nsIFrame::eSVG), michael@0: "Not expecting non-SVG children"); michael@0: // If |childFrame| is dirty or has dirty children, we don't bother michael@0: // updating overflows since that will happen when it's reflowed. michael@0: if (!(childFrame->GetStateBits() & michael@0: (NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN))) { michael@0: mOverflowChangedTracker.AddFrame(childFrame, michael@0: OverflowChangedTracker::CHILDREN_AND_PARENT_CHANGED); michael@0: } michael@0: NS_ASSERTION(!nsLayoutUtils::GetNextContinuationOrIBSplitSibling(childFrame), michael@0: "SVG frames should not have continuations " michael@0: "or ib-split siblings"); michael@0: NS_ASSERTION(childFrame->GetParent() == hintFrame, michael@0: "SVG child frame not expected to have different parent"); michael@0: } michael@0: } michael@0: // If |frame| is dirty or has dirty children, we don't bother updating michael@0: // overflows since that will happen when it's reflowed. michael@0: if (!(frame->GetStateBits() & michael@0: (NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN))) { michael@0: // If we have both nsChangeHint_UpdateOverflow and michael@0: // nsChangeHint_UpdatePostTransformOverflow, CHILDREN_AND_PARENT_CHANGED michael@0: // is selected as it is stronger. michael@0: if (hint & nsChangeHint_UpdateOverflow) { michael@0: changeKind = OverflowChangedTracker::CHILDREN_AND_PARENT_CHANGED; michael@0: } else { michael@0: changeKind = OverflowChangedTracker::TRANSFORM_CHANGED; michael@0: } michael@0: for (nsIFrame *cont = frame; cont; cont = michael@0: nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) { michael@0: mOverflowChangedTracker.AddFrame(cont, changeKind); michael@0: } michael@0: } michael@0: } michael@0: if ((hint & nsChangeHint_UpdateCursor) && !didUpdateCursor) { michael@0: mPresContext->PresShell()->SynthesizeMouseMove(false); michael@0: didUpdateCursor = true; michael@0: } michael@0: } michael@0: } michael@0: michael@0: FrameConstructor()->EndUpdate(); michael@0: michael@0: // cleanup references and verify the style tree. Note that the latter needs michael@0: // to happen once we've processed the whole list, since until then the tree michael@0: // is not in fact in a consistent state. michael@0: index = count; michael@0: while (0 <= --index) { michael@0: const nsStyleChangeData* changeData; michael@0: aChangeList.ChangeAt(index, &changeData); michael@0: if (changeData->mFrame) { michael@0: propTable->Delete(changeData->mFrame, ChangeListProperty()); michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: // reget frame from content since it may have been regenerated... michael@0: if (changeData->mContent) { michael@0: if (!nsAnimationManager::ContentOrAncestorHasAnimation(changeData->mContent) && michael@0: !nsTransitionManager::ContentOrAncestorHasTransition(changeData->mContent)) { michael@0: nsIFrame* frame = changeData->mContent->GetPrimaryFrame(); michael@0: if (frame) { michael@0: DebugVerifyStyleTree(frame); michael@0: } michael@0: } michael@0: } else if (!changeData->mFrame || michael@0: changeData->mFrame->GetType() != nsGkAtoms::viewportFrame) { michael@0: NS_WARNING("Unable to test style tree integrity -- no content node " michael@0: "(and not a viewport frame)"); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: aChangeList.Clear(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: RestyleManager::RestyleElement(Element* aElement, michael@0: nsIFrame* aPrimaryFrame, michael@0: nsChangeHint aMinHint, michael@0: RestyleTracker& aRestyleTracker, michael@0: bool aRestyleDescendants) michael@0: { michael@0: NS_ASSERTION(aPrimaryFrame == aElement->GetPrimaryFrame(), michael@0: "frame/content mismatch"); michael@0: if (aPrimaryFrame && aPrimaryFrame->GetContent() != aElement) { michael@0: // XXXbz this is due to image maps messing with the primary frame pointer michael@0: // of s. See bug 135040. We can remove this block once that's fixed. michael@0: aPrimaryFrame = nullptr; michael@0: } michael@0: NS_ASSERTION(!aPrimaryFrame || aPrimaryFrame->GetContent() == aElement, michael@0: "frame/content mismatch"); michael@0: michael@0: // If we're restyling the root element and there are 'rem' units in michael@0: // use, handle dynamic changes to the definition of a 'rem' here. michael@0: if (mPresContext->UsesRootEMUnits() && aPrimaryFrame) { michael@0: nsStyleContext *oldContext = aPrimaryFrame->StyleContext(); michael@0: if (!oldContext->GetParent()) { // check that we're the root element michael@0: nsRefPtr newContext = mPresContext->StyleSet()-> michael@0: ResolveStyleFor(aElement, nullptr /* == oldContext->GetParent() */); michael@0: if (oldContext->StyleFont()->mFont.size != michael@0: newContext->StyleFont()->mFont.size) { michael@0: // The basis for 'rem' units has changed. michael@0: newContext = nullptr; michael@0: DoRebuildAllStyleData(aRestyleTracker, nsChangeHint(0)); michael@0: if (aMinHint == 0) { michael@0: return; michael@0: } michael@0: aPrimaryFrame = aElement->GetPrimaryFrame(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (aMinHint & nsChangeHint_ReconstructFrame) { michael@0: FrameConstructor()->RecreateFramesForContent(aElement, false); michael@0: } else if (aPrimaryFrame) { michael@0: nsStyleChangeList changeList; michael@0: ComputeStyleChangeFor(aPrimaryFrame, &changeList, aMinHint, michael@0: aRestyleTracker, aRestyleDescendants); michael@0: ProcessRestyledFrames(changeList); michael@0: } else { michael@0: // no frames, reconstruct for content michael@0: FrameConstructor()->MaybeRecreateFramesForElement(aElement); michael@0: } michael@0: } michael@0: michael@0: static inline dom::Element* michael@0: ElementForStyleContext(nsIContent* aParentContent, michael@0: nsIFrame* aFrame, michael@0: nsCSSPseudoElements::Type aPseudoType); michael@0: michael@0: // Forwarded nsIDocumentObserver method, to handle restyling (and michael@0: // passing the notification to the frame). michael@0: nsresult michael@0: RestyleManager::ContentStateChanged(nsIContent* aContent, michael@0: EventStates aStateMask) michael@0: { michael@0: // XXXbz it would be good if this function only took Elements, but michael@0: // we'd have to make ESM guarantee that usefully. michael@0: if (!aContent->IsElement()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: Element* aElement = aContent->AsElement(); michael@0: michael@0: nsStyleSet* styleSet = mPresContext->StyleSet(); michael@0: NS_ASSERTION(styleSet, "couldn't get style set"); michael@0: michael@0: nsChangeHint hint = NS_STYLE_HINT_NONE; michael@0: // Any change to a content state that affects which frames we construct michael@0: // must lead to a frame reconstruct here if we already have a frame. michael@0: // Note that we never decide through non-CSS means to not create frames michael@0: // based on content states, so if we already don't have a frame we don't michael@0: // need to force a reframe -- if it's needed, the HasStateDependentStyle michael@0: // call will handle things. michael@0: nsIFrame* primaryFrame = aElement->GetPrimaryFrame(); michael@0: nsCSSPseudoElements::Type pseudoType = michael@0: nsCSSPseudoElements::ePseudo_NotPseudoElement; michael@0: if (primaryFrame) { michael@0: // If it's generated content, ignore LOADING/etc state changes on it. michael@0: if (!primaryFrame->IsGeneratedContentFrame() && michael@0: aStateMask.HasAtLeastOneOfStates(NS_EVENT_STATE_BROKEN | michael@0: NS_EVENT_STATE_USERDISABLED | michael@0: NS_EVENT_STATE_SUPPRESSED | michael@0: NS_EVENT_STATE_LOADING)) { michael@0: hint = nsChangeHint_ReconstructFrame; michael@0: } else { michael@0: uint8_t app = primaryFrame->StyleDisplay()->mAppearance; michael@0: if (app) { michael@0: nsITheme *theme = mPresContext->GetTheme(); michael@0: if (theme && theme->ThemeSupportsWidget(mPresContext, michael@0: primaryFrame, app)) { michael@0: bool repaint = false; michael@0: theme->WidgetStateChanged(primaryFrame, app, nullptr, &repaint); michael@0: if (repaint) { michael@0: NS_UpdateHint(hint, nsChangeHint_RepaintFrame); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: pseudoType = primaryFrame->StyleContext()->GetPseudoType(); michael@0: michael@0: primaryFrame->ContentStatesChanged(aStateMask); michael@0: } michael@0: michael@0: michael@0: nsRestyleHint rshint; michael@0: michael@0: if (pseudoType >= nsCSSPseudoElements::ePseudo_PseudoElementCount) { michael@0: rshint = styleSet->HasStateDependentStyle(mPresContext, aElement, michael@0: aStateMask); michael@0: } else if (nsCSSPseudoElements::PseudoElementSupportsUserActionState( michael@0: pseudoType)) { michael@0: // If aElement is a pseudo-element, we want to check to see whether there michael@0: // are any state-dependent rules applying to that pseudo. michael@0: Element* ancestor = ElementForStyleContext(nullptr, primaryFrame, michael@0: pseudoType); michael@0: rshint = styleSet->HasStateDependentStyle(mPresContext, ancestor, michael@0: pseudoType, aElement, michael@0: aStateMask); michael@0: } else { michael@0: rshint = nsRestyleHint(0); michael@0: } michael@0: michael@0: if (aStateMask.HasState(NS_EVENT_STATE_HOVER) && rshint != 0) { michael@0: ++mHoverGeneration; michael@0: } michael@0: michael@0: if (aStateMask.HasState(NS_EVENT_STATE_VISITED)) { michael@0: // Exposing information to the page about whether the link is michael@0: // visited or not isn't really something we can worry about here. michael@0: // FIXME: We could probably do this a bit better. michael@0: NS_UpdateHint(hint, nsChangeHint_RepaintFrame); michael@0: } michael@0: michael@0: PostRestyleEvent(aElement, rshint, hint); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Forwarded nsIMutationObserver method, to handle restyling. michael@0: void michael@0: RestyleManager::AttributeWillChange(Element* aElement, michael@0: int32_t aNameSpaceID, michael@0: nsIAtom* aAttribute, michael@0: int32_t aModType) michael@0: { michael@0: nsRestyleHint rshint = michael@0: mPresContext->StyleSet()->HasAttributeDependentStyle(mPresContext, michael@0: aElement, michael@0: aAttribute, michael@0: aModType, michael@0: false); michael@0: PostRestyleEvent(aElement, rshint, NS_STYLE_HINT_NONE); michael@0: } michael@0: michael@0: // Forwarded nsIMutationObserver method, to handle restyling (and michael@0: // passing the notification to the frame). michael@0: void michael@0: RestyleManager::AttributeChanged(Element* aElement, michael@0: int32_t aNameSpaceID, michael@0: nsIAtom* aAttribute, michael@0: int32_t aModType) michael@0: { michael@0: // Hold onto the PresShell to prevent ourselves from being destroyed. michael@0: // XXXbz how, exactly, would this attribute change cause us to be michael@0: // destroyed from inside this function? michael@0: nsCOMPtr shell = mPresContext->GetPresShell(); michael@0: michael@0: // Get the frame associated with the content which is the highest in the frame tree michael@0: nsIFrame* primaryFrame = aElement->GetPrimaryFrame(); michael@0: michael@0: #if 0 michael@0: NS_FRAME_LOG(NS_FRAME_TRACE_CALLS, michael@0: ("RestyleManager::AttributeChanged: content=%p[%s] frame=%p", michael@0: aContent, ContentTag(aElement, 0), frame)); michael@0: #endif michael@0: michael@0: // the style tag has its own interpretation based on aHint michael@0: nsChangeHint hint = aElement->GetAttributeChangeHint(aAttribute, aModType); michael@0: michael@0: bool reframe = (hint & nsChangeHint_ReconstructFrame) != 0; michael@0: michael@0: #ifdef MOZ_XUL michael@0: // The following listbox widget trap prevents offscreen listbox widget michael@0: // content from being removed and re-inserted (which is what would michael@0: // happen otherwise). michael@0: if (!primaryFrame && !reframe) { michael@0: int32_t namespaceID; michael@0: nsIAtom* tag = mPresContext->Document()->BindingManager()-> michael@0: ResolveTag(aElement, &namespaceID); michael@0: michael@0: if (namespaceID == kNameSpaceID_XUL && michael@0: (tag == nsGkAtoms::listitem || michael@0: tag == nsGkAtoms::listcell)) michael@0: return; michael@0: } michael@0: michael@0: if (aAttribute == nsGkAtoms::tooltiptext || michael@0: aAttribute == nsGkAtoms::tooltip) michael@0: { michael@0: nsIRootBox* rootBox = nsIRootBox::GetRootBox(mPresContext->GetPresShell()); michael@0: if (rootBox) { michael@0: if (aModType == nsIDOMMutationEvent::REMOVAL) michael@0: rootBox->RemoveTooltipSupport(aElement); michael@0: if (aModType == nsIDOMMutationEvent::ADDITION) michael@0: rootBox->AddTooltipSupport(aElement); michael@0: } michael@0: } michael@0: michael@0: #endif // MOZ_XUL michael@0: michael@0: if (primaryFrame) { michael@0: // See if we have appearance information for a theme. michael@0: const nsStyleDisplay* disp = primaryFrame->StyleDisplay(); michael@0: if (disp->mAppearance) { michael@0: nsITheme *theme = mPresContext->GetTheme(); michael@0: if (theme && theme->ThemeSupportsWidget(mPresContext, primaryFrame, disp->mAppearance)) { michael@0: bool repaint = false; michael@0: theme->WidgetStateChanged(primaryFrame, disp->mAppearance, aAttribute, &repaint); michael@0: if (repaint) michael@0: NS_UpdateHint(hint, nsChangeHint_RepaintFrame); michael@0: } michael@0: } michael@0: michael@0: // let the frame deal with it now, so we don't have to deal later michael@0: primaryFrame->AttributeChanged(aNameSpaceID, aAttribute, aModType); michael@0: // XXXwaterson should probably check for IB split siblings michael@0: // here, and propagate the AttributeChanged notification to michael@0: // them, as well. Currently, inline frames don't do anything on michael@0: // this notification, so it's not that big a deal. michael@0: } michael@0: michael@0: // See if we can optimize away the style re-resolution -- must be called after michael@0: // the frame's AttributeChanged() in case it does something that affects the style michael@0: nsRestyleHint rshint = michael@0: mPresContext->StyleSet()->HasAttributeDependentStyle(mPresContext, michael@0: aElement, michael@0: aAttribute, michael@0: aModType, michael@0: true); michael@0: michael@0: PostRestyleEvent(aElement, rshint, hint); michael@0: } michael@0: michael@0: void michael@0: RestyleManager::RestyleForEmptyChange(Element* aContainer) michael@0: { michael@0: // In some cases (:empty + E, :empty ~ E), a change if the content of michael@0: // an element requires restyling its parent's siblings. michael@0: nsRestyleHint hint = eRestyle_Subtree; michael@0: nsIContent* grandparent = aContainer->GetParent(); michael@0: if (grandparent && michael@0: (grandparent->GetFlags() & NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS)) { michael@0: hint = nsRestyleHint(hint | eRestyle_LaterSiblings); michael@0: } michael@0: PostRestyleEvent(aContainer, hint, NS_STYLE_HINT_NONE); michael@0: } michael@0: michael@0: void michael@0: RestyleManager::RestyleForAppend(Element* aContainer, michael@0: nsIContent* aFirstNewContent) michael@0: { michael@0: NS_ASSERTION(aContainer, "must have container for append"); michael@0: #ifdef DEBUG michael@0: { michael@0: for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) { michael@0: NS_ASSERTION(!cur->IsRootOfAnonymousSubtree(), michael@0: "anonymous nodes should not be in child lists"); michael@0: } michael@0: } michael@0: #endif michael@0: uint32_t selectorFlags = michael@0: aContainer->GetFlags() & (NODE_ALL_SELECTOR_FLAGS & michael@0: ~NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS); michael@0: if (selectorFlags == 0) michael@0: return; michael@0: michael@0: if (selectorFlags & NODE_HAS_EMPTY_SELECTOR) { michael@0: // see whether we need to restyle the container michael@0: bool wasEmpty = true; // :empty or :-moz-only-whitespace michael@0: for (nsIContent* cur = aContainer->GetFirstChild(); michael@0: cur != aFirstNewContent; michael@0: cur = cur->GetNextSibling()) { michael@0: // We don't know whether we're testing :empty or :-moz-only-whitespace, michael@0: // so be conservative and assume :-moz-only-whitespace (i.e., make michael@0: // IsSignificantChild less likely to be true, and thus make us more michael@0: // likely to restyle). michael@0: if (nsStyleUtil::IsSignificantChild(cur, true, false)) { michael@0: wasEmpty = false; michael@0: break; michael@0: } michael@0: } michael@0: if (wasEmpty) { michael@0: RestyleForEmptyChange(aContainer); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: if (selectorFlags & NODE_HAS_SLOW_SELECTOR) { michael@0: PostRestyleEvent(aContainer, eRestyle_Subtree, NS_STYLE_HINT_NONE); michael@0: // Restyling the container is the most we can do here, so we're done. michael@0: return; michael@0: } michael@0: michael@0: if (selectorFlags & NODE_HAS_EDGE_CHILD_SELECTOR) { michael@0: // restyle the last element child before this node michael@0: for (nsIContent* cur = aFirstNewContent->GetPreviousSibling(); michael@0: cur; michael@0: cur = cur->GetPreviousSibling()) { michael@0: if (cur->IsElement()) { michael@0: PostRestyleEvent(cur->AsElement(), eRestyle_Subtree, NS_STYLE_HINT_NONE); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Needed since we can't use PostRestyleEvent on non-elements (with michael@0: // eRestyle_LaterSiblings or nsRestyleHint(eRestyle_Subtree | michael@0: // eRestyle_LaterSiblings) as appropriate). michael@0: static void michael@0: RestyleSiblingsStartingWith(RestyleManager* aRestyleManager, michael@0: nsIContent* aStartingSibling /* may be null */) michael@0: { michael@0: for (nsIContent *sibling = aStartingSibling; sibling; michael@0: sibling = sibling->GetNextSibling()) { michael@0: if (sibling->IsElement()) { michael@0: aRestyleManager-> michael@0: PostRestyleEvent(sibling->AsElement(), michael@0: nsRestyleHint(eRestyle_Subtree | eRestyle_LaterSiblings), michael@0: NS_STYLE_HINT_NONE); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Restyling for a ContentInserted or CharacterDataChanged notification. michael@0: // This could be used for ContentRemoved as well if we got the michael@0: // notification before the removal happened (and sometimes michael@0: // CharacterDataChanged is more like a removal than an addition). michael@0: // The comments are written and variables are named in terms of it being michael@0: // a ContentInserted notification. michael@0: void michael@0: RestyleManager::RestyleForInsertOrChange(Element* aContainer, michael@0: nsIContent* aChild) michael@0: { michael@0: NS_ASSERTION(!aChild->IsRootOfAnonymousSubtree(), michael@0: "anonymous nodes should not be in child lists"); michael@0: uint32_t selectorFlags = michael@0: aContainer ? (aContainer->GetFlags() & NODE_ALL_SELECTOR_FLAGS) : 0; michael@0: if (selectorFlags == 0) michael@0: return; michael@0: michael@0: if (selectorFlags & NODE_HAS_EMPTY_SELECTOR) { michael@0: // see whether we need to restyle the container michael@0: bool wasEmpty = true; // :empty or :-moz-only-whitespace michael@0: for (nsIContent* child = aContainer->GetFirstChild(); michael@0: child; michael@0: child = child->GetNextSibling()) { michael@0: if (child == aChild) michael@0: continue; michael@0: // We don't know whether we're testing :empty or :-moz-only-whitespace, michael@0: // so be conservative and assume :-moz-only-whitespace (i.e., make michael@0: // IsSignificantChild less likely to be true, and thus make us more michael@0: // likely to restyle). michael@0: if (nsStyleUtil::IsSignificantChild(child, true, false)) { michael@0: wasEmpty = false; michael@0: break; michael@0: } michael@0: } michael@0: if (wasEmpty) { michael@0: RestyleForEmptyChange(aContainer); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: if (selectorFlags & NODE_HAS_SLOW_SELECTOR) { michael@0: PostRestyleEvent(aContainer, eRestyle_Subtree, NS_STYLE_HINT_NONE); michael@0: // Restyling the container is the most we can do here, so we're done. michael@0: return; michael@0: } michael@0: michael@0: if (selectorFlags & NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS) { michael@0: // Restyle all later siblings. michael@0: RestyleSiblingsStartingWith(this, aChild->GetNextSibling()); michael@0: } michael@0: michael@0: if (selectorFlags & NODE_HAS_EDGE_CHILD_SELECTOR) { michael@0: // restyle the previously-first element child if it is after this node michael@0: bool passedChild = false; michael@0: for (nsIContent* content = aContainer->GetFirstChild(); michael@0: content; michael@0: content = content->GetNextSibling()) { michael@0: if (content == aChild) { michael@0: passedChild = true; michael@0: continue; michael@0: } michael@0: if (content->IsElement()) { michael@0: if (passedChild) { michael@0: PostRestyleEvent(content->AsElement(), eRestyle_Subtree, michael@0: NS_STYLE_HINT_NONE); michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: // restyle the previously-last element child if it is before this node michael@0: passedChild = false; michael@0: for (nsIContent* content = aContainer->GetLastChild(); michael@0: content; michael@0: content = content->GetPreviousSibling()) { michael@0: if (content == aChild) { michael@0: passedChild = true; michael@0: continue; michael@0: } michael@0: if (content->IsElement()) { michael@0: if (passedChild) { michael@0: PostRestyleEvent(content->AsElement(), eRestyle_Subtree, michael@0: NS_STYLE_HINT_NONE); michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: RestyleManager::RestyleForRemove(Element* aContainer, michael@0: nsIContent* aOldChild, michael@0: nsIContent* aFollowingSibling) michael@0: { michael@0: if (aOldChild->IsRootOfAnonymousSubtree()) { michael@0: // This should be an assert, but this is called incorrectly in michael@0: // nsHTMLEditor::DeleteRefToAnonymousNode and the assertions were clogging michael@0: // up the logs. Make it an assert again when that's fixed. michael@0: NS_WARNING("anonymous nodes should not be in child lists (bug 439258)"); michael@0: } michael@0: uint32_t selectorFlags = michael@0: aContainer ? (aContainer->GetFlags() & NODE_ALL_SELECTOR_FLAGS) : 0; michael@0: if (selectorFlags == 0) michael@0: return; michael@0: michael@0: if (selectorFlags & NODE_HAS_EMPTY_SELECTOR) { michael@0: // see whether we need to restyle the container michael@0: bool isEmpty = true; // :empty or :-moz-only-whitespace michael@0: for (nsIContent* child = aContainer->GetFirstChild(); michael@0: child; michael@0: child = child->GetNextSibling()) { michael@0: // We don't know whether we're testing :empty or :-moz-only-whitespace, michael@0: // so be conservative and assume :-moz-only-whitespace (i.e., make michael@0: // IsSignificantChild less likely to be true, and thus make us more michael@0: // likely to restyle). michael@0: if (nsStyleUtil::IsSignificantChild(child, true, false)) { michael@0: isEmpty = false; michael@0: break; michael@0: } michael@0: } michael@0: if (isEmpty) { michael@0: RestyleForEmptyChange(aContainer); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: if (selectorFlags & NODE_HAS_SLOW_SELECTOR) { michael@0: PostRestyleEvent(aContainer, eRestyle_Subtree, NS_STYLE_HINT_NONE); michael@0: // Restyling the container is the most we can do here, so we're done. michael@0: return; michael@0: } michael@0: michael@0: if (selectorFlags & NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS) { michael@0: // Restyle all later siblings. michael@0: RestyleSiblingsStartingWith(this, aFollowingSibling); michael@0: } michael@0: michael@0: if (selectorFlags & NODE_HAS_EDGE_CHILD_SELECTOR) { michael@0: // restyle the now-first element child if it was after aOldChild michael@0: bool reachedFollowingSibling = false; michael@0: for (nsIContent* content = aContainer->GetFirstChild(); michael@0: content; michael@0: content = content->GetNextSibling()) { michael@0: if (content == aFollowingSibling) { michael@0: reachedFollowingSibling = true; michael@0: // do NOT continue here; we might want to restyle this node michael@0: } michael@0: if (content->IsElement()) { michael@0: if (reachedFollowingSibling) { michael@0: PostRestyleEvent(content->AsElement(), eRestyle_Subtree, michael@0: NS_STYLE_HINT_NONE); michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: // restyle the now-last element child if it was before aOldChild michael@0: reachedFollowingSibling = (aFollowingSibling == nullptr); michael@0: for (nsIContent* content = aContainer->GetLastChild(); michael@0: content; michael@0: content = content->GetPreviousSibling()) { michael@0: if (content->IsElement()) { michael@0: if (reachedFollowingSibling) { michael@0: PostRestyleEvent(content->AsElement(), eRestyle_Subtree, NS_STYLE_HINT_NONE); michael@0: } michael@0: break; michael@0: } michael@0: if (content == aFollowingSibling) { michael@0: reachedFollowingSibling = true; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: RestyleManager::RebuildAllStyleData(nsChangeHint aExtraHint) michael@0: { michael@0: NS_ASSERTION(!(aExtraHint & nsChangeHint_ReconstructFrame), michael@0: "Should not reconstruct the root of the frame tree. " michael@0: "Use ReconstructDocElementHierarchy instead."); michael@0: michael@0: mRebuildAllStyleData = false; michael@0: NS_UpdateHint(aExtraHint, mRebuildAllExtraHint); michael@0: mRebuildAllExtraHint = nsChangeHint(0); michael@0: michael@0: nsIPresShell* presShell = mPresContext->GetPresShell(); michael@0: if (!presShell || !presShell->GetRootFrame()) michael@0: return; michael@0: michael@0: // Make sure that the viewmanager will outlive the presshell michael@0: nsRefPtr vm = presShell->GetViewManager(); michael@0: michael@0: // Processing the style changes could cause a flush that propagates to michael@0: // the parent frame and thus destroys the pres shell. michael@0: nsCOMPtr kungFuDeathGrip(presShell); michael@0: michael@0: // We may reconstruct frames below and hence process anything that is in the michael@0: // tree. We don't want to get notified to process those items again after. michael@0: presShell->GetDocument()->FlushPendingNotifications(Flush_ContentAndNotify); michael@0: michael@0: nsAutoScriptBlocker scriptBlocker; michael@0: michael@0: mPresContext->SetProcessingRestyles(true); michael@0: michael@0: DoRebuildAllStyleData(mPendingRestyles, aExtraHint); michael@0: michael@0: mPresContext->SetProcessingRestyles(false); michael@0: michael@0: // Make sure that we process any pending animation restyles from the michael@0: // above style change. Note that we can *almost* implement the above michael@0: // by just posting a style change -- except we really need to restyle michael@0: // the root frame rather than the root element's primary frame. michael@0: ProcessPendingRestyles(); michael@0: } michael@0: michael@0: void michael@0: RestyleManager::DoRebuildAllStyleData(RestyleTracker& aRestyleTracker, michael@0: nsChangeHint aExtraHint) michael@0: { michael@0: // Tell the style set to get the old rule tree out of the way michael@0: // so we can recalculate while maintaining rule tree immutability michael@0: nsresult rv = mPresContext->StyleSet()->BeginReconstruct(); michael@0: if (NS_FAILED(rv)) { michael@0: return; michael@0: } michael@0: michael@0: // Recalculate all of the style contexts for the document michael@0: // Note that we can ignore the return value of ComputeStyleChangeFor michael@0: // because we never need to reframe the root frame michael@0: // XXX This could be made faster by not rerunning rule matching michael@0: // (but note that nsPresShell::SetPreferenceStyleRules currently depends michael@0: // on us re-running rule matching here michael@0: nsStyleChangeList changeList; michael@0: // XXX Does it matter that we're passing aExtraHint to the real root michael@0: // frame and not the root node's primary frame? michael@0: // Note: The restyle tracker we pass in here doesn't matter. michael@0: ComputeStyleChangeFor(mPresContext->PresShell()->GetRootFrame(), michael@0: &changeList, aExtraHint, michael@0: aRestyleTracker, true); michael@0: // Process the required changes michael@0: ProcessRestyledFrames(changeList); michael@0: FlushOverflowChangedTracker(); michael@0: michael@0: // Tell the style set it's safe to destroy the old rule tree. We michael@0: // must do this after the ProcessRestyledFrames call in case the michael@0: // change list has frame reconstructs in it (since frames to be michael@0: // reconstructed will still have their old style context pointers michael@0: // until they are destroyed). michael@0: mPresContext->StyleSet()->EndReconstruct(); michael@0: } michael@0: michael@0: void michael@0: RestyleManager::ProcessPendingRestyles() michael@0: { michael@0: NS_PRECONDITION(mPresContext->Document(), "No document? Pshaw!"); michael@0: NS_PRECONDITION(!nsContentUtils::IsSafeToRunScript(), michael@0: "Missing a script blocker!"); michael@0: michael@0: // First do any queued-up frame creation. (We should really michael@0: // merge this into the rest of the process, though; see bug 827239.) michael@0: mPresContext->FrameConstructor()->CreateNeededFrames(); michael@0: michael@0: // Process non-animation restyles... michael@0: NS_ABORT_IF_FALSE(!mPresContext->IsProcessingRestyles(), michael@0: "Nesting calls to ProcessPendingRestyles?"); michael@0: mPresContext->SetProcessingRestyles(true); michael@0: michael@0: // Before we process any restyles, we need to ensure that style michael@0: // resulting from any throttled animations (animations that we're michael@0: // running entirely on the compositor thread) is up-to-date, so that michael@0: // if any style changes we cause trigger transitions, we have the michael@0: // correct old style for starting the transition. michael@0: if (nsLayoutUtils::AreAsyncAnimationsEnabled() && michael@0: mPendingRestyles.Count() > 0) { michael@0: ++mAnimationGeneration; michael@0: mPresContext->TransitionManager()->UpdateAllThrottledStyles(); michael@0: } michael@0: michael@0: mPendingRestyles.ProcessRestyles(); michael@0: michael@0: #ifdef DEBUG michael@0: uint32_t oldPendingRestyleCount = mPendingRestyles.Count(); michael@0: #endif michael@0: michael@0: // ...and then process animation restyles. This needs to happen michael@0: // second because we need to start animations that resulted from the michael@0: // first set of restyles (e.g., CSS transitions with negative michael@0: // transition-delay), and because we need to immediately michael@0: // restyle-with-animation any just-restyled elements that are michael@0: // mid-transition (since processing the non-animation restyle ignores michael@0: // the running transition so it can check for a new change on the same michael@0: // property, and then posts an immediate animation style change). michael@0: mPresContext->SetProcessingAnimationStyleChange(true); michael@0: mPendingAnimationRestyles.ProcessRestyles(); michael@0: mPresContext->SetProcessingAnimationStyleChange(false); michael@0: michael@0: mPresContext->SetProcessingRestyles(false); michael@0: NS_POSTCONDITION(mPendingRestyles.Count() == oldPendingRestyleCount, michael@0: "We should not have posted new non-animation restyles while " michael@0: "processing animation restyles"); michael@0: michael@0: if (mRebuildAllStyleData) { michael@0: // We probably wasted a lot of work up above, but this seems safest michael@0: // and it should be rarely used. michael@0: // This might add us as a refresh observer again; that's ok. michael@0: RebuildAllStyleData(nsChangeHint(0)); michael@0: } michael@0: } michael@0: michael@0: void michael@0: RestyleManager::BeginProcessingRestyles() michael@0: { michael@0: // Make sure to not rebuild quote or counter lists while we're michael@0: // processing restyles michael@0: mPresContext->FrameConstructor()->BeginUpdate(); michael@0: michael@0: mInStyleRefresh = true; michael@0: } michael@0: michael@0: void michael@0: RestyleManager::EndProcessingRestyles() michael@0: { michael@0: FlushOverflowChangedTracker(); michael@0: michael@0: // Set mInStyleRefresh to false now, since the EndUpdate call might michael@0: // add more restyles. michael@0: mInStyleRefresh = false; michael@0: michael@0: mPresContext->FrameConstructor()->EndUpdate(); michael@0: michael@0: #ifdef DEBUG michael@0: mPresContext->PresShell()->VerifyStyleTree(); michael@0: #endif michael@0: } michael@0: michael@0: void michael@0: RestyleManager::PostRestyleEventCommon(Element* aElement, michael@0: nsRestyleHint aRestyleHint, michael@0: nsChangeHint aMinChangeHint, michael@0: bool aForAnimation) michael@0: { michael@0: if (MOZ_UNLIKELY(mPresContext->PresShell()->IsDestroying())) { michael@0: return; michael@0: } michael@0: michael@0: if (aRestyleHint == 0 && !aMinChangeHint) { michael@0: // Nothing to do here michael@0: return; michael@0: } michael@0: michael@0: RestyleTracker& tracker = michael@0: aForAnimation ? mPendingAnimationRestyles : mPendingRestyles; michael@0: tracker.AddPendingRestyle(aElement, aRestyleHint, aMinChangeHint); michael@0: michael@0: PostRestyleEventInternal(false); michael@0: } michael@0: michael@0: void michael@0: RestyleManager::PostRestyleEventInternal(bool aForLazyConstruction) michael@0: { michael@0: // Make sure we're not in a style refresh; if we are, we still have michael@0: // a call to ProcessPendingRestyles coming and there's no need to michael@0: // add ourselves as a refresh observer until then. michael@0: bool inRefresh = !aForLazyConstruction && mInStyleRefresh; michael@0: nsIPresShell* presShell = mPresContext->PresShell(); michael@0: if (!mObservingRefreshDriver && !inRefresh) { michael@0: mObservingRefreshDriver = mPresContext->RefreshDriver()-> michael@0: AddStyleFlushObserver(presShell); michael@0: } michael@0: michael@0: // Unconditionally flag our document as needing a flush. The other michael@0: // option here would be a dedicated boolean to track whether we need michael@0: // to do so (set here and unset in ProcessPendingRestyles). michael@0: presShell->GetDocument()->SetNeedStyleFlush(); michael@0: } michael@0: michael@0: void michael@0: RestyleManager::PostRebuildAllStyleDataEvent(nsChangeHint aExtraHint) michael@0: { michael@0: NS_ASSERTION(!(aExtraHint & nsChangeHint_ReconstructFrame), michael@0: "Should not reconstruct the root of the frame tree. " michael@0: "Use ReconstructDocElementHierarchy instead."); michael@0: michael@0: mRebuildAllStyleData = true; michael@0: NS_UpdateHint(mRebuildAllExtraHint, aExtraHint); michael@0: michael@0: // Get a restyle event posted if necessary michael@0: PostRestyleEventInternal(false); michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: static void michael@0: DumpContext(nsIFrame* aFrame, nsStyleContext* aContext) michael@0: { michael@0: if (aFrame) { michael@0: fputs("frame: ", stdout); michael@0: nsAutoString name; michael@0: aFrame->GetFrameName(name); michael@0: fputs(NS_LossyConvertUTF16toASCII(name).get(), stdout); michael@0: fprintf(stdout, " (%p)", static_cast(aFrame)); michael@0: } michael@0: if (aContext) { michael@0: fprintf(stdout, " style: %p ", static_cast(aContext)); michael@0: michael@0: nsIAtom* pseudoTag = aContext->GetPseudo(); michael@0: if (pseudoTag) { michael@0: nsAutoString buffer; michael@0: pseudoTag->ToString(buffer); michael@0: fputs(NS_LossyConvertUTF16toASCII(buffer).get(), stdout); michael@0: fputs(" ", stdout); michael@0: } michael@0: fputs("{}\n", stdout); michael@0: } michael@0: } michael@0: michael@0: static void michael@0: VerifySameTree(nsStyleContext* aContext1, nsStyleContext* aContext2) michael@0: { michael@0: nsStyleContext* top1 = aContext1; michael@0: nsStyleContext* top2 = aContext2; michael@0: nsStyleContext* parent; michael@0: for (;;) { michael@0: parent = top1->GetParent(); michael@0: if (!parent) michael@0: break; michael@0: top1 = parent; michael@0: } michael@0: for (;;) { michael@0: parent = top2->GetParent(); michael@0: if (!parent) michael@0: break; michael@0: top2 = parent; michael@0: } michael@0: NS_ASSERTION(top1 == top2, michael@0: "Style contexts are not in the same style context tree"); michael@0: } michael@0: michael@0: static void michael@0: VerifyContextParent(nsPresContext* aPresContext, nsIFrame* aFrame, michael@0: nsStyleContext* aContext, nsStyleContext* aParentContext) michael@0: { michael@0: // get the contexts not provided michael@0: if (!aContext) { michael@0: aContext = aFrame->StyleContext(); michael@0: } michael@0: michael@0: if (!aParentContext) { michael@0: // Get the correct parent context from the frame michael@0: // - if the frame is a placeholder, we get the out of flow frame's context michael@0: // as the parent context instead of asking the frame michael@0: michael@0: // get the parent context from the frame (indirectly) michael@0: nsIFrame* providerFrame = aFrame->GetParentStyleContextFrame(); michael@0: if (providerFrame) michael@0: aParentContext = providerFrame->StyleContext(); michael@0: // aParentContext could still be null michael@0: } michael@0: michael@0: NS_ASSERTION(aContext, "Failure to get required contexts"); michael@0: nsStyleContext* actualParentContext = aContext->GetParent(); michael@0: michael@0: if (aParentContext) { michael@0: if (aParentContext != actualParentContext) { michael@0: DumpContext(aFrame, aContext); michael@0: if (aContext == aParentContext) { michael@0: NS_ERROR("Using parent's style context"); michael@0: } michael@0: else { michael@0: NS_ERROR("Wrong parent style context"); michael@0: fputs("Wrong parent style context: ", stdout); michael@0: DumpContext(nullptr, actualParentContext); michael@0: fputs("should be using: ", stdout); michael@0: DumpContext(nullptr, aParentContext); michael@0: VerifySameTree(actualParentContext, aParentContext); michael@0: fputs("\n", stdout); michael@0: } michael@0: } michael@0: michael@0: } michael@0: else { michael@0: if (actualParentContext) { michael@0: NS_ERROR("Have parent context and shouldn't"); michael@0: DumpContext(aFrame, aContext); michael@0: fputs("Has parent context: ", stdout); michael@0: DumpContext(nullptr, actualParentContext); michael@0: fputs("Should be null\n\n", stdout); michael@0: } michael@0: } michael@0: michael@0: nsStyleContext* childStyleIfVisited = aContext->GetStyleIfVisited(); michael@0: // Either childStyleIfVisited has aContext->GetParent()->GetStyleIfVisited() michael@0: // as the parent or it has a different rulenode from aContext _and_ has michael@0: // aContext->GetParent() as the parent. michael@0: if (childStyleIfVisited && michael@0: !((childStyleIfVisited->RuleNode() != aContext->RuleNode() && michael@0: childStyleIfVisited->GetParent() == aContext->GetParent()) || michael@0: childStyleIfVisited->GetParent() == michael@0: aContext->GetParent()->GetStyleIfVisited())) { michael@0: NS_ERROR("Visited style has wrong parent"); michael@0: DumpContext(aFrame, aContext); michael@0: fputs("\n", stdout); michael@0: } michael@0: } michael@0: michael@0: static void michael@0: VerifyStyleTree(nsPresContext* aPresContext, nsIFrame* aFrame, michael@0: nsStyleContext* aParentContext) michael@0: { michael@0: nsStyleContext* context = aFrame->StyleContext(); michael@0: VerifyContextParent(aPresContext, aFrame, context, nullptr); michael@0: michael@0: nsIFrame::ChildListIterator lists(aFrame); michael@0: for (; !lists.IsDone(); lists.Next()) { michael@0: nsFrameList::Enumerator childFrames(lists.CurrentList()); michael@0: for (; !childFrames.AtEnd(); childFrames.Next()) { michael@0: nsIFrame* child = childFrames.get(); michael@0: if (!(child->GetStateBits() & NS_FRAME_OUT_OF_FLOW)) { michael@0: // only do frames that are in flow michael@0: if (nsGkAtoms::placeholderFrame == child->GetType()) { michael@0: // placeholder: first recurse and verify the out of flow frame, michael@0: // then verify the placeholder's context michael@0: nsIFrame* outOfFlowFrame = michael@0: nsPlaceholderFrame::GetRealFrameForPlaceholder(child); michael@0: michael@0: // recurse to out of flow frame, letting the parent context get resolved michael@0: do { michael@0: VerifyStyleTree(aPresContext, outOfFlowFrame, nullptr); michael@0: } while ((outOfFlowFrame = outOfFlowFrame->GetNextContinuation())); michael@0: michael@0: // verify placeholder using the parent frame's context as michael@0: // parent context michael@0: VerifyContextParent(aPresContext, child, nullptr, nullptr); michael@0: } michael@0: else { // regular frame michael@0: VerifyStyleTree(aPresContext, child, nullptr); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // do additional contexts michael@0: int32_t contextIndex = 0; michael@0: for (nsStyleContext* extraContext; michael@0: (extraContext = aFrame->GetAdditionalStyleContext(contextIndex)); michael@0: ++contextIndex) { michael@0: VerifyContextParent(aPresContext, aFrame, extraContext, context); michael@0: } michael@0: } michael@0: michael@0: void michael@0: RestyleManager::DebugVerifyStyleTree(nsIFrame* aFrame) michael@0: { michael@0: if (aFrame) { michael@0: nsStyleContext* context = aFrame->StyleContext(); michael@0: nsStyleContext* parentContext = context->GetParent(); michael@0: VerifyStyleTree(mPresContext, aFrame, parentContext); michael@0: } michael@0: } michael@0: michael@0: #endif // DEBUG michael@0: michael@0: // aContent must be the content for the frame in question, which may be michael@0: // :before/:after content michael@0: static void michael@0: TryStartingTransition(nsPresContext *aPresContext, nsIContent *aContent, michael@0: nsStyleContext *aOldStyleContext, michael@0: nsRefPtr *aNewStyleContext /* inout */) michael@0: { michael@0: if (!aContent || !aContent->IsElement()) { michael@0: return; michael@0: } michael@0: michael@0: // Notify the transition manager, and if it starts a transition, michael@0: // it will give us back a transition-covering style rule which michael@0: // we'll use to get *another* style context. We want to ignore michael@0: // any already-running transitions, but cover up any that we're michael@0: // currently starting with their start value so we don't start michael@0: // them again for descendants that inherit that value. michael@0: nsCOMPtr coverRule = michael@0: aPresContext->TransitionManager()->StyleContextChanged( michael@0: aContent->AsElement(), aOldStyleContext, *aNewStyleContext); michael@0: if (coverRule) { michael@0: nsCOMArray rules; michael@0: rules.AppendObject(coverRule); michael@0: *aNewStyleContext = aPresContext->StyleSet()-> michael@0: ResolveStyleByAddingRules(*aNewStyleContext, rules); michael@0: } michael@0: } michael@0: michael@0: static inline dom::Element* michael@0: ElementForStyleContext(nsIContent* aParentContent, michael@0: nsIFrame* aFrame, michael@0: nsCSSPseudoElements::Type aPseudoType) michael@0: { michael@0: // We don't expect XUL tree stuff here. michael@0: NS_PRECONDITION(aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement || michael@0: aPseudoType == nsCSSPseudoElements::ePseudo_AnonBox || michael@0: aPseudoType < nsCSSPseudoElements::ePseudo_PseudoElementCount, michael@0: "Unexpected pseudo"); michael@0: // XXX see the comments about the various element confusion in michael@0: // ElementRestyler::Restyle. michael@0: if (aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement) { michael@0: return aFrame->GetContent()->AsElement(); michael@0: } michael@0: michael@0: if (aPseudoType == nsCSSPseudoElements::ePseudo_AnonBox) { michael@0: return nullptr; michael@0: } michael@0: michael@0: if (aPseudoType == nsCSSPseudoElements::ePseudo_firstLetter) { michael@0: NS_ASSERTION(aFrame->GetType() == nsGkAtoms::letterFrame, michael@0: "firstLetter pseudoTag without a nsFirstLetterFrame"); michael@0: nsBlockFrame* block = nsBlockFrame::GetNearestAncestorBlock(aFrame); michael@0: return block->GetContent()->AsElement(); michael@0: } michael@0: michael@0: if (aPseudoType == nsCSSPseudoElements::ePseudo_mozColorSwatch) { michael@0: MOZ_ASSERT(aFrame->GetParent() && michael@0: aFrame->GetParent()->GetParent(), michael@0: "Color swatch frame should have a parent & grandparent"); michael@0: michael@0: nsIFrame* grandparentFrame = aFrame->GetParent()->GetParent(); michael@0: MOZ_ASSERT(grandparentFrame->GetType() == nsGkAtoms::colorControlFrame, michael@0: "Color swatch's grandparent should be nsColorControlFrame"); michael@0: michael@0: return grandparentFrame->GetContent()->AsElement(); michael@0: } michael@0: michael@0: if (aPseudoType == nsCSSPseudoElements::ePseudo_mozNumberText || michael@0: aPseudoType == nsCSSPseudoElements::ePseudo_mozNumberWrapper || michael@0: aPseudoType == nsCSSPseudoElements::ePseudo_mozNumberSpinBox || michael@0: aPseudoType == nsCSSPseudoElements::ePseudo_mozNumberSpinUp || michael@0: aPseudoType == nsCSSPseudoElements::ePseudo_mozNumberSpinDown) { michael@0: // Get content for nearest nsNumberControlFrame: michael@0: nsIFrame* f = aFrame->GetParent(); michael@0: MOZ_ASSERT(f); michael@0: while (f->GetType() != nsGkAtoms::numberControlFrame) { michael@0: f = f->GetParent(); michael@0: MOZ_ASSERT(f); michael@0: } michael@0: return f->GetContent()->AsElement(); michael@0: } michael@0: michael@0: if (aParentContent) { michael@0: return aParentContent->AsElement(); michael@0: } michael@0: michael@0: MOZ_ASSERT(aFrame->GetContent()->GetParent(), michael@0: "should not have got here for the root element"); michael@0: return aFrame->GetContent()->GetParent()->AsElement(); michael@0: } michael@0: michael@0: /** michael@0: * FIXME: Temporary. Should merge with following function. michael@0: */ michael@0: static nsIFrame* michael@0: GetPrevContinuationWithPossiblySameStyle(nsIFrame* aFrame) michael@0: { michael@0: // Account for {ib} splits when looking for "prevContinuation". In michael@0: // particular, for the first-continuation of a part of an {ib} split michael@0: // we want to use the previous ib-split sibling of the previous michael@0: // ib-split sibling of aFrame, which should have the same style michael@0: // context as aFrame itself. In particular, if aFrame is the first michael@0: // continuation of an inline part of a block-in-inline split then its michael@0: // previous ib-split sibling is a block, and the previous ib-split michael@0: // sibling of _that_ is an inline, just like aFrame. Similarly, if michael@0: // aFrame is the first continuation of a block part of an michael@0: // block-in-inline split (a block-in-inline wrapper block), then its michael@0: // previous ib-split sibling is an inline and the previous ib-split michael@0: // sibling of that is either another block-in-inline wrapper block box michael@0: // or null. michael@0: nsIFrame *prevContinuation = aFrame->GetPrevContinuation(); michael@0: if (!prevContinuation && michael@0: (aFrame->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT)) { michael@0: // We're the first continuation, so we can just get the frame michael@0: // property directly michael@0: prevContinuation = static_cast( michael@0: aFrame->Properties().Get(nsIFrame::IBSplitPrevSibling())); michael@0: if (prevContinuation) { michael@0: prevContinuation = static_cast( michael@0: prevContinuation->Properties().Get(nsIFrame::IBSplitPrevSibling())); michael@0: } michael@0: } michael@0: michael@0: NS_ASSERTION(!prevContinuation || michael@0: prevContinuation->GetContent() == aFrame->GetContent(), michael@0: "unexpected content mismatch"); michael@0: michael@0: return prevContinuation; michael@0: } michael@0: michael@0: /** michael@0: * Get the previous continuation or similar ib-split sibling (assuming michael@0: * block/inline alternation), conditionally on it having the same style. michael@0: * This assumes that we're not between resolving the two (i.e., that michael@0: * they're both already resolved. michael@0: */ michael@0: static nsIFrame* michael@0: GetPrevContinuationWithSameStyle(nsIFrame* aFrame) michael@0: { michael@0: nsIFrame* prevContinuation = GetPrevContinuationWithPossiblySameStyle(aFrame); michael@0: if (!prevContinuation) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsStyleContext* prevStyle = prevContinuation->StyleContext(); michael@0: nsStyleContext* selfStyle = aFrame->StyleContext(); michael@0: if (prevStyle != selfStyle) { michael@0: NS_ASSERTION(prevStyle->GetPseudo() != selfStyle->GetPseudo() || michael@0: prevStyle->GetParent() != selfStyle->GetParent(), michael@0: "continuations should have the same style context"); michael@0: prevContinuation = nullptr; michael@0: } michael@0: return prevContinuation; michael@0: } michael@0: michael@0: /** michael@0: * Get the next continuation or similar ib-split sibling (assuming michael@0: * block/inline alternation), conditionally on it having the same style. michael@0: * michael@0: * Since this is used when deciding to copy the new style context, it michael@0: * takes as an argument the old style context to check if the style is michael@0: * the same. When it is used in other contexts (i.e., where the next michael@0: * continuation would already have the new style context), the current michael@0: * style context should be passed. michael@0: */ michael@0: static nsIFrame* michael@0: GetNextContinuationWithSameStyle(nsIFrame* aFrame, michael@0: nsStyleContext* aOldStyleContext) michael@0: { michael@0: // See GetPrevContinuationWithSameStyle about {ib} splits. michael@0: michael@0: nsIFrame *nextContinuation = aFrame->GetNextContinuation(); michael@0: if (!nextContinuation && michael@0: (aFrame->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT)) { michael@0: // We're the last continuation, so we have to hop back to the first michael@0: // before getting the frame property michael@0: nextContinuation = static_cast(aFrame->FirstContinuation()-> michael@0: Properties().Get(nsIFrame::IBSplitSibling())); michael@0: if (nextContinuation) { michael@0: nextContinuation = static_cast( michael@0: nextContinuation->Properties().Get(nsIFrame::IBSplitSibling())); michael@0: } michael@0: } michael@0: michael@0: if (!nextContinuation) { michael@0: return nullptr; michael@0: } michael@0: michael@0: NS_ASSERTION(nextContinuation->GetContent() == aFrame->GetContent(), michael@0: "unexpected content mismatch"); michael@0: michael@0: nsStyleContext* nextStyle = nextContinuation->StyleContext(); michael@0: if (nextStyle != aOldStyleContext) { michael@0: NS_ASSERTION(aOldStyleContext->GetPseudo() != nextStyle->GetPseudo() || michael@0: aOldStyleContext->GetParent() != nextStyle->GetParent(), michael@0: "continuations should have the same style context"); michael@0: nextContinuation = nullptr; michael@0: } michael@0: return nextContinuation; michael@0: } michael@0: michael@0: nsresult michael@0: RestyleManager::ReparentStyleContext(nsIFrame* aFrame) michael@0: { michael@0: if (nsGkAtoms::placeholderFrame == aFrame->GetType()) { michael@0: // Also reparent the out-of-flow and all its continuations. michael@0: nsIFrame* outOfFlow = michael@0: nsPlaceholderFrame::GetRealFrameForPlaceholder(aFrame); michael@0: NS_ASSERTION(outOfFlow, "no out-of-flow frame"); michael@0: do { michael@0: ReparentStyleContext(outOfFlow); michael@0: } while ((outOfFlow = outOfFlow->GetNextContinuation())); michael@0: } michael@0: michael@0: // DO NOT verify the style tree before reparenting. The frame michael@0: // tree has already been changed, so this check would just fail. michael@0: nsStyleContext* oldContext = aFrame->StyleContext(); michael@0: michael@0: nsRefPtr newContext; michael@0: nsIFrame* providerFrame = aFrame->GetParentStyleContextFrame(); michael@0: bool isChild = providerFrame && providerFrame->GetParent() == aFrame; michael@0: nsStyleContext* newParentContext = nullptr; michael@0: nsIFrame* providerChild = nullptr; michael@0: if (isChild) { michael@0: ReparentStyleContext(providerFrame); michael@0: newParentContext = providerFrame->StyleContext(); michael@0: providerChild = providerFrame; michael@0: } else if (providerFrame) { michael@0: newParentContext = providerFrame->StyleContext(); michael@0: } else { michael@0: NS_NOTREACHED("Reparenting something that has no usable parent? " michael@0: "Shouldn't happen!"); michael@0: } michael@0: // XXX need to do something here to produce the correct style context for michael@0: // an IB split whose first inline part is inside a first-line frame. michael@0: // Currently the first IB anonymous block's style context takes the first michael@0: // part's style context as parent, which is wrong since first-line style michael@0: // should not apply to the anonymous block. michael@0: michael@0: #ifdef DEBUG michael@0: { michael@0: // Check that our assumption that continuations of the same michael@0: // pseudo-type and with the same style context parent have the michael@0: // same style context is valid before the reresolution. (We need michael@0: // to check the pseudo-type and style context parent because of michael@0: // :first-letter and :first-line, where we create styled and michael@0: // unstyled letter/line frames distinguished by pseudo-type, and michael@0: // then need to distinguish their descendants based on having michael@0: // different parents.) michael@0: nsIFrame *nextContinuation = aFrame->GetNextContinuation(); michael@0: if (nextContinuation) { michael@0: nsStyleContext *nextContinuationContext = michael@0: nextContinuation->StyleContext(); michael@0: NS_ASSERTION(oldContext == nextContinuationContext || michael@0: oldContext->GetPseudo() != michael@0: nextContinuationContext->GetPseudo() || michael@0: oldContext->GetParent() != michael@0: nextContinuationContext->GetParent(), michael@0: "continuations should have the same style context"); michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: nsIFrame *prevContinuation = michael@0: GetPrevContinuationWithPossiblySameStyle(aFrame); michael@0: nsStyleContext *prevContinuationContext; michael@0: bool copyFromContinuation = michael@0: prevContinuation && michael@0: (prevContinuationContext = prevContinuation->StyleContext()) michael@0: ->GetPseudo() == oldContext->GetPseudo() && michael@0: prevContinuationContext->GetParent() == newParentContext; michael@0: if (copyFromContinuation) { michael@0: // Just use the style context from the frame's previous michael@0: // continuation (see assertion about aFrame->GetNextContinuation() michael@0: // above, which we would have previously hit for aFrame's previous michael@0: // continuation). michael@0: newContext = prevContinuationContext; michael@0: } else { michael@0: nsIFrame* parentFrame = aFrame->GetParent(); michael@0: Element* element = michael@0: ElementForStyleContext(parentFrame ? parentFrame->GetContent() : nullptr, michael@0: aFrame, michael@0: oldContext->GetPseudoType()); michael@0: newContext = mPresContext->StyleSet()-> michael@0: ReparentStyleContext(oldContext, newParentContext, element); michael@0: } michael@0: michael@0: if (newContext) { michael@0: if (newContext != oldContext) { michael@0: // We probably don't want to initiate transitions from michael@0: // ReparentStyleContext, since we call it during frame michael@0: // construction rather than in response to dynamic changes. michael@0: // Also see the comment at the start of michael@0: // nsTransitionManager::ConsiderStartingTransition. michael@0: #if 0 michael@0: if (!copyFromContinuation) { michael@0: TryStartingTransition(mPresContext, aFrame->GetContent(), michael@0: oldContext, &newContext); michael@0: } michael@0: #endif michael@0: michael@0: // Make sure to call CalcStyleDifference so that the new context ends michael@0: // up resolving all the structs the old context resolved. michael@0: if (!copyFromContinuation) { michael@0: DebugOnly styleChange = michael@0: oldContext->CalcStyleDifference(newContext, nsChangeHint(0)); michael@0: // The style change is always 0 because we have the same rulenode and michael@0: // CalcStyleDifference optimizes us away. That's OK, though: michael@0: // reparenting should never trigger a frame reconstruct, and whenever michael@0: // it's happening we already plan to reflow and repaint the frames. michael@0: NS_ASSERTION(!(styleChange & nsChangeHint_ReconstructFrame), michael@0: "Our frame tree is likely to be bogus!"); michael@0: } michael@0: michael@0: aFrame->SetStyleContext(newContext); michael@0: michael@0: nsIFrame::ChildListIterator lists(aFrame); michael@0: for (; !lists.IsDone(); lists.Next()) { michael@0: nsFrameList::Enumerator childFrames(lists.CurrentList()); michael@0: for (; !childFrames.AtEnd(); childFrames.Next()) { michael@0: nsIFrame* child = childFrames.get(); michael@0: // only do frames that are in flow michael@0: if (!(child->GetStateBits() & NS_FRAME_OUT_OF_FLOW) && michael@0: child != providerChild) { michael@0: #ifdef DEBUG michael@0: if (nsGkAtoms::placeholderFrame == child->GetType()) { michael@0: nsIFrame* outOfFlowFrame = michael@0: nsPlaceholderFrame::GetRealFrameForPlaceholder(child); michael@0: NS_ASSERTION(outOfFlowFrame, "no out-of-flow frame"); michael@0: michael@0: NS_ASSERTION(outOfFlowFrame != providerChild, michael@0: "Out of flow provider?"); michael@0: } michael@0: #endif michael@0: ReparentStyleContext(child); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // If this frame is part of an IB split, then the style context of michael@0: // the next part of the split might be a child of our style context. michael@0: // Reparent its style context just in case one of our ancestors michael@0: // (split or not) hasn't done so already). It's not a problem to michael@0: // reparent the same frame twice because the "if (newContext != michael@0: // oldContext)" check will prevent us from redoing work. michael@0: if ((aFrame->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT) && michael@0: !aFrame->GetPrevContinuation()) { michael@0: nsIFrame* sib = static_cast michael@0: (aFrame->Properties().Get(nsIFrame::IBSplitSibling())); michael@0: if (sib) { michael@0: ReparentStyleContext(sib); michael@0: } michael@0: } michael@0: michael@0: // do additional contexts michael@0: int32_t contextIndex = 0; michael@0: for (nsStyleContext* oldExtraContext; michael@0: (oldExtraContext = aFrame->GetAdditionalStyleContext(contextIndex)); michael@0: ++contextIndex) { michael@0: nsRefPtr newExtraContext; michael@0: newExtraContext = mPresContext->StyleSet()-> michael@0: ReparentStyleContext(oldExtraContext, michael@0: newContext, nullptr); michael@0: if (newExtraContext) { michael@0: if (newExtraContext != oldExtraContext) { michael@0: // Make sure to call CalcStyleDifference so that the new michael@0: // context ends up resolving all the structs the old context michael@0: // resolved. michael@0: DebugOnly styleChange = michael@0: oldExtraContext->CalcStyleDifference(newExtraContext, michael@0: nsChangeHint(0)); michael@0: // The style change is always 0 because we have the same michael@0: // rulenode and CalcStyleDifference optimizes us away. That's michael@0: // OK, though: reparenting should never trigger a frame michael@0: // reconstruct, and whenever it's happening we already plan to michael@0: // reflow and repaint the frames. michael@0: NS_ASSERTION(!(styleChange & nsChangeHint_ReconstructFrame), michael@0: "Our frame tree is likely to be bogus!"); michael@0: } michael@0: michael@0: aFrame->SetAdditionalStyleContext(contextIndex, newExtraContext); michael@0: } michael@0: } michael@0: #ifdef DEBUG michael@0: VerifyStyleTree(mPresContext, aFrame, newParentContext); michael@0: #endif michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: ElementRestyler::ElementRestyler(nsPresContext* aPresContext, michael@0: nsIFrame* aFrame, michael@0: nsStyleChangeList* aChangeList, michael@0: nsChangeHint aHintsHandledByAncestors, michael@0: RestyleTracker& aRestyleTracker, michael@0: TreeMatchContext& aTreeMatchContext, michael@0: nsTArray& michael@0: aVisibleKidsOfHiddenElement) michael@0: : mPresContext(aPresContext) michael@0: , mFrame(aFrame) michael@0: , mParentContent(nullptr) michael@0: // XXXldb Why does it make sense to use aParentContent? (See michael@0: // comment above assertion at start of ElementRestyler::Restyle.) michael@0: , mContent(mFrame->GetContent() ? mFrame->GetContent() : mParentContent) michael@0: , mChangeList(aChangeList) michael@0: , mHintsHandled(NS_SubtractHint(aHintsHandledByAncestors, michael@0: NS_HintsNotHandledForDescendantsIn(aHintsHandledByAncestors))) michael@0: , mParentFrameHintsNotHandledForDescendants(nsChangeHint(0)) michael@0: , mHintsNotHandledForDescendants(nsChangeHint(0)) michael@0: , mRestyleTracker(aRestyleTracker) michael@0: , mTreeMatchContext(aTreeMatchContext) michael@0: , mResolvedChild(nullptr) michael@0: #ifdef ACCESSIBILITY michael@0: , mDesiredA11yNotifications(eSendAllNotifications) michael@0: , mKidsDesiredA11yNotifications(mDesiredA11yNotifications) michael@0: , mOurA11yNotification(eDontNotify) michael@0: , mVisibleKidsOfHiddenElement(aVisibleKidsOfHiddenElement) michael@0: #endif michael@0: { michael@0: } michael@0: michael@0: ElementRestyler::ElementRestyler(const ElementRestyler& aParentRestyler, michael@0: nsIFrame* aFrame, michael@0: uint32_t aConstructorFlags) michael@0: : mPresContext(aParentRestyler.mPresContext) michael@0: , mFrame(aFrame) michael@0: , mParentContent(aParentRestyler.mContent) michael@0: // XXXldb Why does it make sense to use aParentContent? (See michael@0: // comment above assertion at start of ElementRestyler::Restyle.) michael@0: , mContent(mFrame->GetContent() ? mFrame->GetContent() : mParentContent) michael@0: , mChangeList(aParentRestyler.mChangeList) michael@0: , mHintsHandled(NS_SubtractHint(aParentRestyler.mHintsHandled, michael@0: NS_HintsNotHandledForDescendantsIn(aParentRestyler.mHintsHandled))) michael@0: , mParentFrameHintsNotHandledForDescendants( michael@0: aParentRestyler.mHintsNotHandledForDescendants) michael@0: , mHintsNotHandledForDescendants(nsChangeHint(0)) michael@0: , mRestyleTracker(aParentRestyler.mRestyleTracker) michael@0: , mTreeMatchContext(aParentRestyler.mTreeMatchContext) michael@0: , mResolvedChild(nullptr) michael@0: #ifdef ACCESSIBILITY michael@0: , mDesiredA11yNotifications(aParentRestyler.mKidsDesiredA11yNotifications) michael@0: , mKidsDesiredA11yNotifications(mDesiredA11yNotifications) michael@0: , mOurA11yNotification(eDontNotify) michael@0: , mVisibleKidsOfHiddenElement(aParentRestyler.mVisibleKidsOfHiddenElement) michael@0: #endif michael@0: { michael@0: if (aConstructorFlags & FOR_OUT_OF_FLOW_CHILD) { michael@0: // Note that the out-of-flow may not be a geometric descendant of michael@0: // the frame where we started the reresolve. Therefore, even if michael@0: // mHintsHandled already includes nsChangeHint_AllReflowHints we michael@0: // don't want to pass that on to the out-of-flow reresolve, since michael@0: // that can lead to the out-of-flow not getting reflowed when it michael@0: // should be (eg a reresolve starting at that involves michael@0: // reflowing the would miss reflowing fixed-pos nodes that michael@0: // also need reflow). In the cases when the out-of-flow _is_ a michael@0: // geometric descendant of a frame we already have a reflow hint michael@0: // for, reflow coalescing should keep us from doing the work twice. michael@0: mHintsHandled = NS_SubtractHint(mHintsHandled, nsChangeHint_AllReflowHints); michael@0: } michael@0: } michael@0: michael@0: ElementRestyler::ElementRestyler(ParentContextFromChildFrame, michael@0: const ElementRestyler& aParentRestyler, michael@0: nsIFrame* aFrame) michael@0: : mPresContext(aParentRestyler.mPresContext) michael@0: , mFrame(aFrame) michael@0: , mParentContent(aParentRestyler.mParentContent) michael@0: // XXXldb Why does it make sense to use aParentContent? (See michael@0: // comment above assertion at start of ElementRestyler::Restyle.) michael@0: , mContent(mFrame->GetContent() ? mFrame->GetContent() : mParentContent) michael@0: , mChangeList(aParentRestyler.mChangeList) michael@0: , mHintsHandled(NS_SubtractHint(aParentRestyler.mHintsHandled, michael@0: NS_HintsNotHandledForDescendantsIn(aParentRestyler.mHintsHandled))) michael@0: , mParentFrameHintsNotHandledForDescendants( michael@0: // assume the worst michael@0: nsChangeHint_Hints_NotHandledForDescendants) michael@0: , mHintsNotHandledForDescendants(nsChangeHint(0)) michael@0: , mRestyleTracker(aParentRestyler.mRestyleTracker) michael@0: , mTreeMatchContext(aParentRestyler.mTreeMatchContext) michael@0: , mResolvedChild(nullptr) michael@0: #ifdef ACCESSIBILITY michael@0: , mDesiredA11yNotifications(aParentRestyler.mDesiredA11yNotifications) michael@0: , mKidsDesiredA11yNotifications(mDesiredA11yNotifications) michael@0: , mOurA11yNotification(eDontNotify) michael@0: , mVisibleKidsOfHiddenElement(aParentRestyler.mVisibleKidsOfHiddenElement) michael@0: #endif michael@0: { michael@0: } michael@0: michael@0: void michael@0: ElementRestyler::CaptureChange(nsStyleContext* aOldContext, michael@0: nsStyleContext* aNewContext, michael@0: nsChangeHint aChangeToAssume) michael@0: { michael@0: // Check some invariants about replacing one style context with another. michael@0: NS_ASSERTION(aOldContext->GetPseudo() == aNewContext->GetPseudo(), michael@0: "old and new style contexts should have the same pseudo"); michael@0: NS_ASSERTION(aOldContext->GetPseudoType() == aNewContext->GetPseudoType(), michael@0: "old and new style contexts should have the same pseudo"); michael@0: michael@0: nsChangeHint ourChange = aOldContext->CalcStyleDifference(aNewContext, michael@0: mParentFrameHintsNotHandledForDescendants); michael@0: NS_ASSERTION(!(ourChange & nsChangeHint_AllReflowHints) || michael@0: (ourChange & nsChangeHint_NeedReflow), michael@0: "Reflow hint bits set without actually asking for a reflow"); michael@0: michael@0: // nsChangeHint_UpdateEffects is inherited, but it can be set due to changes michael@0: // in inherited properties (fill and stroke). Avoid propagating it into michael@0: // text nodes. michael@0: if ((ourChange & nsChangeHint_UpdateEffects) && michael@0: mContent && !mContent->IsElement()) { michael@0: ourChange = NS_SubtractHint(ourChange, nsChangeHint_UpdateEffects); michael@0: } michael@0: michael@0: NS_UpdateHint(ourChange, aChangeToAssume); michael@0: if (NS_UpdateHint(mHintsHandled, ourChange)) { michael@0: if (!(ourChange & nsChangeHint_ReconstructFrame) || mContent) { michael@0: mChangeList->AppendChange(mFrame, mContent, ourChange); michael@0: } michael@0: } michael@0: NS_UpdateHint(mHintsNotHandledForDescendants, michael@0: NS_HintsNotHandledForDescendantsIn(ourChange)); michael@0: } michael@0: michael@0: /** michael@0: * Recompute style for mFrame (which should not have a prev continuation michael@0: * with the same style), all of its next continuations with the same michael@0: * style, and all ib-split siblings of the same type (either block or michael@0: * inline, skipping the intermediates of the other type) and accumulate michael@0: * changes into mChangeList given that mHintsHandled is already accumulated michael@0: * for an ancestor. michael@0: * mParentContent is the content node used to resolve the parent style michael@0: * context. This means that, for pseudo-elements, it is the content michael@0: * that should be used for selector matching (rather than the fake michael@0: * content node attached to the frame). michael@0: */ michael@0: void michael@0: ElementRestyler::Restyle(nsRestyleHint aRestyleHint) michael@0: { michael@0: // It would be nice if we could make stronger assertions here; they michael@0: // would let us simplify the ?: expressions below setting |content| michael@0: // and |pseudoContent| in sensible ways as well as making what michael@0: // |content| and |pseudoContent| mean, and their relationship to michael@0: // |mFrame->GetContent()|, make more sense. However, we can't, michael@0: // because of frame trees like the one in michael@0: // https://bugzilla.mozilla.org/show_bug.cgi?id=472353#c14 . Once we michael@0: // fix bug 242277 we should be able to make this make more sense. michael@0: NS_ASSERTION(mFrame->GetContent() || !mParentContent || michael@0: !mParentContent->GetParent(), michael@0: "frame must have content (unless at the top of the tree)"); michael@0: michael@0: NS_ASSERTION(!GetPrevContinuationWithSameStyle(mFrame), michael@0: "should not be trying to restyle this frame separately"); michael@0: michael@0: if (mContent && mContent->IsElement()) { michael@0: mContent->OwnerDoc()->FlushPendingLinkUpdates(); michael@0: RestyleTracker::RestyleData restyleData; michael@0: if (mRestyleTracker.GetRestyleData(mContent->AsElement(), &restyleData)) { michael@0: if (NS_UpdateHint(mHintsHandled, restyleData.mChangeHint)) { michael@0: mChangeList->AppendChange(mFrame, mContent, restyleData.mChangeHint); michael@0: } michael@0: aRestyleHint = nsRestyleHint(aRestyleHint | restyleData.mRestyleHint); michael@0: } michael@0: } michael@0: michael@0: nsRestyleHint childRestyleHint = aRestyleHint; michael@0: michael@0: if (childRestyleHint == eRestyle_Self) { michael@0: childRestyleHint = nsRestyleHint(0); michael@0: } michael@0: michael@0: { michael@0: nsRefPtr oldContext = mFrame->StyleContext(); michael@0: michael@0: // TEMPORARY (until bug 918064): Call RestyleSelf for each michael@0: // continuation or block-in-inline sibling. michael@0: michael@0: for (nsIFrame* f = mFrame; f; michael@0: f = GetNextContinuationWithSameStyle(f, oldContext)) { michael@0: RestyleSelf(f, aRestyleHint); michael@0: } michael@0: } michael@0: michael@0: RestyleChildren(childRestyleHint); michael@0: } michael@0: michael@0: void michael@0: ElementRestyler::RestyleSelf(nsIFrame* aSelf, nsRestyleHint aRestyleHint) michael@0: { michael@0: // XXXldb get new context from prev-in-flow if possible, to avoid michael@0: // duplication. (Or should we just let |GetContext| handle that?) michael@0: // Getting the hint would be nice too, but that's harder. michael@0: michael@0: // XXXbryner we may be able to avoid some of the refcounting goop here. michael@0: // We do need a reference to oldContext for the lifetime of this function, and it's possible michael@0: // that the frame has the last reference to it, so AddRef it here. michael@0: michael@0: nsChangeHint assumeDifferenceHint = NS_STYLE_HINT_NONE; michael@0: nsRefPtr oldContext = aSelf->StyleContext(); michael@0: nsStyleSet* styleSet = mPresContext->StyleSet(); michael@0: michael@0: #ifdef ACCESSIBILITY michael@0: mWasFrameVisible = nsIPresShell::IsAccessibilityActive() ? michael@0: oldContext->StyleVisibility()->IsVisible() : false; michael@0: #endif michael@0: michael@0: nsIAtom* const pseudoTag = oldContext->GetPseudo(); michael@0: const nsCSSPseudoElements::Type pseudoType = oldContext->GetPseudoType(); michael@0: michael@0: nsStyleContext* parentContext; michael@0: // Get the frame providing the parent style context. If it is a michael@0: // child, then resolve the provider first. michael@0: nsIFrame* providerFrame = aSelf->GetParentStyleContextFrame(); michael@0: bool isChild = providerFrame && providerFrame->GetParent() == aSelf; michael@0: if (!isChild) { michael@0: if (providerFrame) michael@0: parentContext = providerFrame->StyleContext(); michael@0: else michael@0: parentContext = nullptr; michael@0: } michael@0: else { michael@0: MOZ_ASSERT(providerFrame->GetContent() == aSelf->GetContent(), michael@0: "Postcondition for GetParentStyleContextFrame() violated. " michael@0: "That means we need to add the current element to the " michael@0: "ancestor filter."); michael@0: michael@0: // resolve the provider here (before aSelf below). michael@0: michael@0: // assumeDifferenceHint forces the parent's change to be also michael@0: // applied to this frame, no matter what michael@0: // nsStyleContext::CalcStyleDifference says. CalcStyleDifference michael@0: // can't be trusted because it assumes any changes to the parent michael@0: // style context provider will be automatically propagated to michael@0: // the frame(s) with child style contexts. michael@0: michael@0: ElementRestyler providerRestyler(PARENT_CONTEXT_FROM_CHILD_FRAME, michael@0: *this, providerFrame); michael@0: providerRestyler.Restyle(aRestyleHint); michael@0: assumeDifferenceHint = providerRestyler.HintsHandledForFrame(); michael@0: michael@0: // The provider's new context becomes the parent context of michael@0: // aSelf's context. michael@0: parentContext = providerFrame->StyleContext(); michael@0: // Set |mResolvedChild| so we don't bother resolving the michael@0: // provider again. michael@0: mResolvedChild = providerFrame; michael@0: } michael@0: michael@0: if (providerFrame != aSelf->GetParent()) { michael@0: // We don't actually know what the parent style context's michael@0: // non-inherited hints were, so assume the worst. michael@0: mParentFrameHintsNotHandledForDescendants = michael@0: nsChangeHint_Hints_NotHandledForDescendants; michael@0: } michael@0: michael@0: // do primary context michael@0: nsRefPtr newContext; michael@0: nsIFrame *prevContinuation = michael@0: GetPrevContinuationWithPossiblySameStyle(aSelf); michael@0: nsStyleContext *prevContinuationContext; michael@0: bool copyFromContinuation = michael@0: prevContinuation && michael@0: (prevContinuationContext = prevContinuation->StyleContext()) michael@0: ->GetPseudo() == oldContext->GetPseudo() && michael@0: prevContinuationContext->GetParent() == parentContext; michael@0: if (copyFromContinuation) { michael@0: // Just use the style context from the frame's previous michael@0: // continuation. michael@0: newContext = prevContinuationContext; michael@0: } michael@0: else if (pseudoTag == nsCSSAnonBoxes::mozNonElement) { michael@0: NS_ASSERTION(aSelf->GetContent(), michael@0: "non pseudo-element frame without content node"); michael@0: newContext = styleSet->ResolveStyleForNonElement(parentContext); michael@0: } michael@0: else if (!aRestyleHint && !prevContinuation) { michael@0: // Unfortunately, if prevContinuation is non-null then we may have michael@0: // already stolen the restyle tracker entry for this element while michael@0: // processing prevContinuation. So we don't know whether aRestyleHint michael@0: // should really be 0 here or whether it should be eRestyle_Self. Be michael@0: // pessimistic and force an actual reresolve in that situation. The good michael@0: // news is that in the common case when prevContinuation is non-null we michael@0: // just used prevContinuationContext anyway and aren't reaching this code michael@0: // to start with. michael@0: newContext = michael@0: styleSet->ReparentStyleContext(oldContext, parentContext, michael@0: ElementForStyleContext(mParentContent, michael@0: aSelf, pseudoType)); michael@0: } else if (pseudoType == nsCSSPseudoElements::ePseudo_AnonBox) { michael@0: newContext = styleSet->ResolveAnonymousBoxStyle(pseudoTag, michael@0: parentContext); michael@0: } michael@0: else { michael@0: Element* element = ElementForStyleContext(mParentContent, aSelf, pseudoType); michael@0: if (pseudoTag) { michael@0: if (pseudoTag == nsCSSPseudoElements::before || michael@0: pseudoTag == nsCSSPseudoElements::after) { michael@0: // XXX what other pseudos do we need to treat like this? michael@0: newContext = styleSet->ProbePseudoElementStyle(element, michael@0: pseudoType, michael@0: parentContext, michael@0: mTreeMatchContext); michael@0: if (!newContext) { michael@0: // This pseudo should no longer exist; gotta reframe michael@0: NS_UpdateHint(mHintsHandled, nsChangeHint_ReconstructFrame); michael@0: mChangeList->AppendChange(aSelf, element, michael@0: nsChangeHint_ReconstructFrame); michael@0: // We're reframing anyway; just keep the same context michael@0: newContext = oldContext; michael@0: } michael@0: } else { michael@0: // Don't expect XUL tree stuff here, since it needs a comparator and michael@0: // all. michael@0: NS_ASSERTION(pseudoType < michael@0: nsCSSPseudoElements::ePseudo_PseudoElementCount, michael@0: "Unexpected pseudo type"); michael@0: Element* pseudoElement = michael@0: nsCSSPseudoElements::PseudoElementSupportsStyleAttribute(pseudoType) || michael@0: nsCSSPseudoElements::PseudoElementSupportsUserActionState(pseudoType) ? michael@0: aSelf->GetContent()->AsElement() : nullptr; michael@0: MOZ_ASSERT(element != pseudoElement); michael@0: newContext = styleSet->ResolvePseudoElementStyle(element, michael@0: pseudoType, michael@0: parentContext, michael@0: pseudoElement); michael@0: } michael@0: } michael@0: else { michael@0: NS_ASSERTION(aSelf->GetContent(), michael@0: "non pseudo-element frame without content node"); michael@0: // Skip flex-item style fixup for anonymous subtrees: michael@0: TreeMatchContext::AutoFlexItemStyleFixupSkipper michael@0: flexFixupSkipper(mTreeMatchContext, michael@0: element->IsRootOfNativeAnonymousSubtree()); michael@0: newContext = styleSet->ResolveStyleFor(element, parentContext, michael@0: mTreeMatchContext); michael@0: } michael@0: } michael@0: michael@0: MOZ_ASSERT(newContext); michael@0: michael@0: if (!parentContext) { michael@0: if (oldContext->RuleNode() == newContext->RuleNode() && michael@0: oldContext->IsLinkContext() == newContext->IsLinkContext() && michael@0: oldContext->RelevantLinkVisited() == michael@0: newContext->RelevantLinkVisited()) { michael@0: // We're the root of the style context tree and the new style michael@0: // context returned has the same rule node. This means that michael@0: // we can use FindChildWithRules to keep a lot of the old michael@0: // style contexts around. However, we need to start from the michael@0: // same root. michael@0: newContext = oldContext; michael@0: } michael@0: } michael@0: michael@0: if (newContext != oldContext) { michael@0: if (!copyFromContinuation) { michael@0: TryStartingTransition(mPresContext, aSelf->GetContent(), michael@0: oldContext, &newContext); michael@0: michael@0: CaptureChange(oldContext, newContext, assumeDifferenceHint); michael@0: } michael@0: michael@0: if (!(mHintsHandled & nsChangeHint_ReconstructFrame)) { michael@0: // If the frame gets regenerated, let it keep its old context, michael@0: // which is important to maintain various invariants about michael@0: // frame types matching their style contexts. michael@0: // Note that this check even makes sense if we didn't call michael@0: // CaptureChange because of copyFromContinuation being true, michael@0: // since we'll have copied the existing context from the michael@0: // previous continuation, so newContext == oldContext. michael@0: aSelf->SetStyleContext(newContext); michael@0: } michael@0: } michael@0: oldContext = nullptr; michael@0: michael@0: // do additional contexts michael@0: // XXXbz might be able to avoid selector matching here in some michael@0: // cases; won't worry about it for now. michael@0: int32_t contextIndex = 0; michael@0: for (nsStyleContext* oldExtraContext; michael@0: (oldExtraContext = aSelf->GetAdditionalStyleContext(contextIndex)); michael@0: ++contextIndex) { michael@0: nsRefPtr newExtraContext; michael@0: nsIAtom* const extraPseudoTag = oldExtraContext->GetPseudo(); michael@0: const nsCSSPseudoElements::Type extraPseudoType = michael@0: oldExtraContext->GetPseudoType(); michael@0: NS_ASSERTION(extraPseudoTag && michael@0: extraPseudoTag != nsCSSAnonBoxes::mozNonElement, michael@0: "extra style context is not pseudo element"); michael@0: if (extraPseudoType == nsCSSPseudoElements::ePseudo_AnonBox) { michael@0: newExtraContext = styleSet->ResolveAnonymousBoxStyle(extraPseudoTag, michael@0: newContext); michael@0: } michael@0: else { michael@0: // Don't expect XUL tree stuff here, since it needs a comparator and michael@0: // all. michael@0: NS_ASSERTION(extraPseudoType < michael@0: nsCSSPseudoElements::ePseudo_PseudoElementCount, michael@0: "Unexpected type"); michael@0: newExtraContext = styleSet->ResolvePseudoElementStyle(mContent->AsElement(), michael@0: extraPseudoType, michael@0: newContext, michael@0: nullptr); michael@0: } michael@0: michael@0: MOZ_ASSERT(newExtraContext); michael@0: michael@0: if (oldExtraContext != newExtraContext) { michael@0: CaptureChange(oldExtraContext, newExtraContext, assumeDifferenceHint); michael@0: if (!(mHintsHandled & nsChangeHint_ReconstructFrame)) { michael@0: aSelf->SetAdditionalStyleContext(contextIndex, newExtraContext); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: ElementRestyler::RestyleChildren(nsRestyleHint aChildRestyleHint) michael@0: { michael@0: RestyleUndisplayedChildren(aChildRestyleHint); michael@0: michael@0: // Check whether we might need to create a new ::before frame. michael@0: // There's no need to do this if we're planning to reframe already michael@0: // or if we're not forcing restyles on kids. michael@0: // It's also important to check mHintsHandled since we use michael@0: // mFrame->StyleContext(), which is out of date if mHintsHandled has a michael@0: // ReconstructFrame hint. Using an out of date style context could michael@0: // trigger assertions about mismatched rule trees. michael@0: if (!(mHintsHandled & nsChangeHint_ReconstructFrame) && michael@0: aChildRestyleHint) { michael@0: RestyleBeforePseudo(); michael@0: } michael@0: michael@0: // There is no need to waste time crawling into a frame's children michael@0: // on a frame change. The act of reconstructing frames will force michael@0: // new style contexts to be resolved on all of this frame's michael@0: // descendants anyway, so we want to avoid wasting time processing michael@0: // style contexts that we're just going to throw away anyway. - dwh michael@0: // It's also important to check mHintsHandled since reresolving the michael@0: // kids would use mFrame->StyleContext(), which is out of date if michael@0: // mHintsHandled has a ReconstructFrame hint; doing this could trigger michael@0: // assertions about mismatched rule trees. michael@0: nsIFrame *lastContinuation; michael@0: if (!(mHintsHandled & nsChangeHint_ReconstructFrame)) { michael@0: InitializeAccessibilityNotifications(); michael@0: michael@0: for (nsIFrame* f = mFrame; f; michael@0: f = GetNextContinuationWithSameStyle(f, f->StyleContext())) { michael@0: lastContinuation = f; michael@0: RestyleContentChildren(f, aChildRestyleHint); michael@0: } michael@0: michael@0: SendAccessibilityNotifications(); michael@0: } michael@0: michael@0: // Check whether we might need to create a new ::after frame. michael@0: // See comments above regarding :before. michael@0: if (!(mHintsHandled & nsChangeHint_ReconstructFrame) && michael@0: aChildRestyleHint) { michael@0: RestyleAfterPseudo(lastContinuation); michael@0: } michael@0: } michael@0: michael@0: void michael@0: ElementRestyler::RestyleUndisplayedChildren(nsRestyleHint aChildRestyleHint) michael@0: { michael@0: // When the root element is display:none, we still construct *some* michael@0: // frames that have the root element as their mContent, down to the michael@0: // DocElementContainingBlock. michael@0: bool checkUndisplayed; michael@0: nsIContent* undisplayedParent; michael@0: nsCSSFrameConstructor* frameConstructor = mPresContext->FrameConstructor(); michael@0: if (mFrame->StyleContext()->GetPseudo()) { michael@0: checkUndisplayed = mFrame == frameConstructor-> michael@0: GetDocElementContainingBlock(); michael@0: undisplayedParent = nullptr; michael@0: } else { michael@0: checkUndisplayed = !!mFrame->GetContent(); michael@0: undisplayedParent = mFrame->GetContent(); michael@0: } michael@0: if (checkUndisplayed && michael@0: // No need to do this if we're planning to reframe already. michael@0: // It's also important to check mHintsHandled since we use michael@0: // mFrame->StyleContext(), which is out of date if mHintsHandled michael@0: // has a ReconstructFrame hint. Using an out of date style michael@0: // context could trigger assertions about mismatched rule trees. michael@0: !(mHintsHandled & nsChangeHint_ReconstructFrame)) { michael@0: UndisplayedNode* undisplayed = michael@0: frameConstructor->GetAllUndisplayedContentIn(undisplayedParent); michael@0: TreeMatchContext::AutoAncestorPusher pusher(mTreeMatchContext); michael@0: if (undisplayed) { michael@0: pusher.PushAncestorAndStyleScope(undisplayedParent); michael@0: } michael@0: for (; undisplayed; undisplayed = undisplayed->mNext) { michael@0: NS_ASSERTION(undisplayedParent || michael@0: undisplayed->mContent == michael@0: mPresContext->Document()->GetRootElement(), michael@0: "undisplayed node child of null must be root"); michael@0: NS_ASSERTION(!undisplayed->mStyle->GetPseudo(), michael@0: "Shouldn't have random pseudo style contexts in the " michael@0: "undisplayed map"); michael@0: michael@0: // Get the parent of the undisplayed content and check if it is a XBL michael@0: // children element. Push the children element as an ancestor here because it does michael@0: // not have a frame and would not otherwise be pushed as an ancestor. michael@0: nsIContent* parent = undisplayed->mContent->GetParent(); michael@0: TreeMatchContext::AutoAncestorPusher insertionPointPusher(mTreeMatchContext); michael@0: if (parent && nsContentUtils::IsContentInsertionPoint(parent)) { michael@0: insertionPointPusher.PushAncestorAndStyleScope(parent); michael@0: } michael@0: michael@0: nsRestyleHint thisChildHint = aChildRestyleHint; michael@0: RestyleTracker::RestyleData undisplayedRestyleData; michael@0: if (mRestyleTracker.GetRestyleData(undisplayed->mContent->AsElement(), michael@0: &undisplayedRestyleData)) { michael@0: thisChildHint = michael@0: nsRestyleHint(thisChildHint | undisplayedRestyleData.mRestyleHint); michael@0: } michael@0: nsRefPtr undisplayedContext; michael@0: nsStyleSet* styleSet = mPresContext->StyleSet(); michael@0: if (thisChildHint) { michael@0: undisplayedContext = michael@0: styleSet->ResolveStyleFor(undisplayed->mContent->AsElement(), michael@0: mFrame->StyleContext(), michael@0: mTreeMatchContext); michael@0: } else { michael@0: undisplayedContext = michael@0: styleSet->ReparentStyleContext(undisplayed->mStyle, michael@0: mFrame->StyleContext(), michael@0: undisplayed->mContent->AsElement()); michael@0: } michael@0: const nsStyleDisplay* display = undisplayedContext->StyleDisplay(); michael@0: if (display->mDisplay != NS_STYLE_DISPLAY_NONE) { michael@0: NS_ASSERTION(undisplayed->mContent, michael@0: "Must have undisplayed content"); michael@0: mChangeList->AppendChange(nullptr, undisplayed->mContent, michael@0: NS_STYLE_HINT_FRAMECHANGE); michael@0: // The node should be removed from the undisplayed map when michael@0: // we reframe it. michael@0: } else { michael@0: // update the undisplayed node with the new context michael@0: undisplayed->mStyle = undisplayedContext; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: ElementRestyler::RestyleBeforePseudo() michael@0: { michael@0: // Make sure not to do this for pseudo-frames or frames that michael@0: // can't have generated content. michael@0: if (!mFrame->StyleContext()->GetPseudo() && michael@0: ((mFrame->GetStateBits() & NS_FRAME_MAY_HAVE_GENERATED_CONTENT) || michael@0: // Our content insertion frame might have gotten flagged michael@0: (mFrame->GetContentInsertionFrame()->GetStateBits() & michael@0: NS_FRAME_MAY_HAVE_GENERATED_CONTENT))) { michael@0: // Check for a new :before pseudo and an existing :before michael@0: // frame, but only if the frame is the first continuation. michael@0: nsIFrame* prevContinuation = mFrame->GetPrevContinuation(); michael@0: if (!prevContinuation) { michael@0: // Checking for a :before frame is cheaper than getting the michael@0: // :before style context. michael@0: if (!nsLayoutUtils::GetBeforeFrame(mFrame) && michael@0: nsLayoutUtils::HasPseudoStyle(mFrame->GetContent(), michael@0: mFrame->StyleContext(), michael@0: nsCSSPseudoElements::ePseudo_before, michael@0: mPresContext)) { michael@0: // Have to create the new :before frame michael@0: NS_UpdateHint(mHintsHandled, nsChangeHint_ReconstructFrame); michael@0: mChangeList->AppendChange(mFrame, mContent, michael@0: nsChangeHint_ReconstructFrame); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * aFrame is the last continuation or block-in-inline sibling that this michael@0: * ElementRestyler is restyling. michael@0: */ michael@0: void michael@0: ElementRestyler::RestyleAfterPseudo(nsIFrame* aFrame) michael@0: { michael@0: // Make sure not to do this for pseudo-frames or frames that michael@0: // can't have generated content. michael@0: if (!aFrame->StyleContext()->GetPseudo() && michael@0: ((aFrame->GetStateBits() & NS_FRAME_MAY_HAVE_GENERATED_CONTENT) || michael@0: // Our content insertion frame might have gotten flagged michael@0: (aFrame->GetContentInsertionFrame()->GetStateBits() & michael@0: NS_FRAME_MAY_HAVE_GENERATED_CONTENT))) { michael@0: // Check for new :after content, but only if the frame is the michael@0: // last continuation. michael@0: nsIFrame* nextContinuation = aFrame->GetNextContinuation(); michael@0: michael@0: if (!nextContinuation) { michael@0: // Getting the :after frame is more expensive than getting the pseudo michael@0: // context, so get the pseudo context first. michael@0: if (nsLayoutUtils::HasPseudoStyle(aFrame->GetContent(), michael@0: aFrame->StyleContext(), michael@0: nsCSSPseudoElements::ePseudo_after, michael@0: mPresContext) && michael@0: !nsLayoutUtils::GetAfterFrame(aFrame)) { michael@0: // have to create the new :after frame michael@0: NS_UpdateHint(mHintsHandled, nsChangeHint_ReconstructFrame); michael@0: mChangeList->AppendChange(aFrame, mContent, michael@0: nsChangeHint_ReconstructFrame); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: ElementRestyler::InitializeAccessibilityNotifications() michael@0: { michael@0: #ifdef ACCESSIBILITY michael@0: // Notify a11y for primary frame only if it's a root frame of visibility michael@0: // changes or its parent frame was hidden while it stays visible and michael@0: // it is not inside a {ib} split or is the first frame of {ib} split. michael@0: if (nsIPresShell::IsAccessibilityActive() && michael@0: !mFrame->GetPrevContinuation() && michael@0: !mFrame->FrameIsNonFirstInIBSplit()) { michael@0: if (mDesiredA11yNotifications == eSendAllNotifications) { michael@0: bool isFrameVisible = mFrame->StyleVisibility()->IsVisible(); michael@0: if (isFrameVisible != mWasFrameVisible) { michael@0: if (isFrameVisible) { michael@0: // Notify a11y the element (perhaps with its children) was shown. michael@0: // We don't fall into this case if this element gets or stays shown michael@0: // while its parent becomes hidden. michael@0: mKidsDesiredA11yNotifications = eSkipNotifications; michael@0: mOurA11yNotification = eNotifyShown; michael@0: } else { michael@0: // The element is being hidden; its children may stay visible, or michael@0: // become visible after being hidden previously. If we'll find michael@0: // visible children then we should notify a11y about that as if michael@0: // they were inserted into tree. Notify a11y this element was michael@0: // hidden. michael@0: mKidsDesiredA11yNotifications = eNotifyIfShown; michael@0: mOurA11yNotification = eNotifyHidden; michael@0: } michael@0: } michael@0: } else if (mDesiredA11yNotifications == eNotifyIfShown && michael@0: mFrame->StyleVisibility()->IsVisible()) { michael@0: // Notify a11y that element stayed visible while its parent was michael@0: // hidden. michael@0: mVisibleKidsOfHiddenElement.AppendElement(mFrame->GetContent()); michael@0: mKidsDesiredA11yNotifications = eSkipNotifications; michael@0: } michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: void michael@0: ElementRestyler::RestyleContentChildren(nsIFrame* aParent, michael@0: nsRestyleHint aChildRestyleHint) michael@0: { michael@0: nsIFrame::ChildListIterator lists(aParent); michael@0: TreeMatchContext::AutoAncestorPusher ancestorPusher(mTreeMatchContext); michael@0: if (!lists.IsDone()) { michael@0: ancestorPusher.PushAncestorAndStyleScope(mContent); michael@0: } michael@0: for (; !lists.IsDone(); lists.Next()) { michael@0: nsFrameList::Enumerator childFrames(lists.CurrentList()); michael@0: for (; !childFrames.AtEnd(); childFrames.Next()) { michael@0: nsIFrame* child = childFrames.get(); michael@0: // Out-of-flows are reached through their placeholders. Continuations michael@0: // and block-in-inline splits are reached through those chains. michael@0: if (!(child->GetStateBits() & NS_FRAME_OUT_OF_FLOW) && michael@0: !GetPrevContinuationWithSameStyle(child)) { michael@0: // Get the parent of the child frame's content and check if it michael@0: // is a XBL children element. Push the children element as an michael@0: // ancestor here because it does not have a frame and would not michael@0: // otherwise be pushed as an ancestor. michael@0: michael@0: // Check if the frame has a content because |child| may be a michael@0: // nsPageFrame that does not have a content. michael@0: nsIContent* parent = child->GetContent() ? child->GetContent()->GetParent() : nullptr; michael@0: TreeMatchContext::AutoAncestorPusher insertionPointPusher(mTreeMatchContext); michael@0: if (parent && nsContentUtils::IsContentInsertionPoint(parent)) { michael@0: insertionPointPusher.PushAncestorAndStyleScope(parent); michael@0: } michael@0: michael@0: // only do frames that are in flow michael@0: if (nsGkAtoms::placeholderFrame == child->GetType()) { // placeholder michael@0: // get out of flow frame and recur there michael@0: nsIFrame* outOfFlowFrame = michael@0: nsPlaceholderFrame::GetRealFrameForPlaceholder(child); michael@0: NS_ASSERTION(outOfFlowFrame, "no out-of-flow frame"); michael@0: NS_ASSERTION(outOfFlowFrame != mResolvedChild, michael@0: "out-of-flow frame not a true descendant"); michael@0: michael@0: // |nsFrame::GetParentStyleContextFrame| checks being out michael@0: // of flow so that this works correctly. michael@0: do { michael@0: if (GetPrevContinuationWithSameStyle(outOfFlowFrame)) { michael@0: // Later continuations are likely restyled as a result of michael@0: // the restyling of the previous continuation. michael@0: // (Currently that's always true, but it's likely to michael@0: // change if we implement overflow:fragments or similar.) michael@0: continue; michael@0: } michael@0: ElementRestyler oofRestyler(*this, outOfFlowFrame, michael@0: FOR_OUT_OF_FLOW_CHILD); michael@0: oofRestyler.Restyle(aChildRestyleHint); michael@0: } while ((outOfFlowFrame = outOfFlowFrame->GetNextContinuation())); michael@0: michael@0: // reresolve placeholder's context under the same parent michael@0: // as the out-of-flow frame michael@0: ElementRestyler phRestyler(*this, child, 0); michael@0: phRestyler.Restyle(aChildRestyleHint); michael@0: } michael@0: else { // regular child frame michael@0: if (child != mResolvedChild) { michael@0: ElementRestyler childRestyler(*this, child, 0); michael@0: childRestyler.Restyle(aChildRestyleHint); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: // XXX need to do overflow frames??? michael@0: } michael@0: michael@0: void michael@0: ElementRestyler::SendAccessibilityNotifications() michael@0: { michael@0: #ifdef ACCESSIBILITY michael@0: // Send notifications about visibility changes. michael@0: if (mOurA11yNotification == eNotifyShown) { michael@0: nsAccessibilityService* accService = nsIPresShell::AccService(); michael@0: if (accService) { michael@0: nsIPresShell* presShell = mFrame->PresContext()->GetPresShell(); michael@0: nsIContent* content = mFrame->GetContent(); michael@0: michael@0: accService->ContentRangeInserted(presShell, content->GetParent(), michael@0: content, michael@0: content->GetNextSibling()); michael@0: } michael@0: } else if (mOurA11yNotification == eNotifyHidden) { michael@0: nsAccessibilityService* accService = nsIPresShell::AccService(); michael@0: if (accService) { michael@0: nsIPresShell* presShell = mFrame->PresContext()->GetPresShell(); michael@0: nsIContent* content = mFrame->GetContent(); michael@0: accService->ContentRemoved(presShell, content->GetParent(), content); michael@0: michael@0: // Process children staying shown. michael@0: uint32_t visibleContentCount = mVisibleKidsOfHiddenElement.Length(); michael@0: for (uint32_t idx = 0; idx < visibleContentCount; idx++) { michael@0: nsIContent* childContent = mVisibleKidsOfHiddenElement[idx]; michael@0: accService->ContentRangeInserted(presShell, childContent->GetParent(), michael@0: childContent, michael@0: childContent->GetNextSibling()); michael@0: } michael@0: mVisibleKidsOfHiddenElement.Clear(); michael@0: } michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: static inline nsIFrame* michael@0: GetNextBlockInInlineSibling(FramePropertyTable* aPropTable, nsIFrame* aFrame) michael@0: { michael@0: NS_ASSERTION(!aFrame->GetPrevContinuation(), michael@0: "must start with the first continuation"); michael@0: // Might we have ib-split siblings? michael@0: if (!(aFrame->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT)) { michael@0: // nothing more to do here michael@0: return nullptr; michael@0: } michael@0: michael@0: return static_cast michael@0: (aPropTable->Get(aFrame, nsIFrame::IBSplitSibling())); michael@0: } michael@0: michael@0: void michael@0: RestyleManager::ComputeStyleChangeFor(nsIFrame* aFrame, michael@0: nsStyleChangeList* aChangeList, michael@0: nsChangeHint aMinChange, michael@0: RestyleTracker& aRestyleTracker, michael@0: bool aRestyleDescendants) michael@0: { michael@0: PROFILER_LABEL("CSS", "ComputeStyleChangeFor"); michael@0: michael@0: nsIContent *content = aFrame->GetContent(); michael@0: if (aMinChange) { michael@0: aChangeList->AppendChange(aFrame, content, aMinChange); michael@0: } michael@0: michael@0: NS_ASSERTION(!aFrame->GetPrevContinuation(), michael@0: "must start with the first continuation"); michael@0: michael@0: // We want to start with this frame and walk all its next-in-flows, michael@0: // as well as all its ib-split siblings and their next-in-flows, michael@0: // reresolving style on all the frames we encounter in this walk that michael@0: // we didn't reach already. In the normal case, this will mean only michael@0: // restyling the first two block-in-inline splits and no michael@0: // continuations, and skipping everything else. However, when we have michael@0: // a style change targeted at an element inside a context where styles michael@0: // vary between continuations (e.g., a style change on an element that michael@0: // extends from inside a styled ::first-line to outside of that first michael@0: // line), we might restyle more than that. michael@0: michael@0: FramePropertyTable* propTable = mPresContext->PropertyTable(); michael@0: michael@0: TreeMatchContext treeMatchContext(true, michael@0: nsRuleWalker::eRelevantLinkUnvisited, michael@0: mPresContext->Document()); michael@0: nsIContent *parent = content ? content->GetParent() : nullptr; michael@0: Element *parentElement = michael@0: parent && parent->IsElement() ? parent->AsElement() : nullptr; michael@0: treeMatchContext.InitAncestors(parentElement); michael@0: nsTArray visibleKidsOfHiddenElement; michael@0: for (nsIFrame* ibSibling = aFrame; ibSibling; michael@0: ibSibling = GetNextBlockInInlineSibling(propTable, ibSibling)) { michael@0: // Outer loop over ib-split siblings michael@0: for (nsIFrame* cont = ibSibling; cont; cont = cont->GetNextContinuation()) { michael@0: if (GetPrevContinuationWithSameStyle(cont)) { michael@0: // We already handled this element when dealing with its earlier michael@0: // continuation. michael@0: continue; michael@0: } michael@0: michael@0: // Inner loop over next-in-flows of the current frame michael@0: ElementRestyler restyler(mPresContext, cont, aChangeList, michael@0: aMinChange, aRestyleTracker, michael@0: treeMatchContext, michael@0: visibleKidsOfHiddenElement); michael@0: michael@0: restyler.Restyle(aRestyleDescendants ? eRestyle_Subtree : eRestyle_Self); michael@0: michael@0: if (restyler.HintsHandledForFrame() & nsChangeHint_ReconstructFrame) { michael@0: // If it's going to cause a framechange, then don't bother michael@0: // with the continuations or ib-split siblings since they'll be michael@0: // clobbered by the frame reconstruct anyway. michael@0: NS_ASSERTION(!cont->GetPrevContinuation(), michael@0: "continuing frame had more severe impact than first-in-flow"); michael@0: return; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: } // namespace mozilla