/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* base class of all rendering objects */ #include "nsIFrame.h" #include #include #include "AnchorPositioningUtils.h" #include "LayoutLogging.h" #include "PseudoStyleType.h" #include "RubyUtils.h" #include "TextOverflow.h" #include "gfx2DGlue.h" #include "gfxUtils.h" #include "mozilla/AbsoluteContainingBlock.h" #include "mozilla/Attributes.h" #include "mozilla/CaretAssociationHint.h" #include "mozilla/ComputedStyle.h" #include "mozilla/DebugOnly.h" #include "mozilla/DisplayPortUtils.h" #include "mozilla/EventForwards.h" #include "mozilla/FocusModel.h" #include "mozilla/IntegerRange.h" #include "mozilla/Logging.h" #include "mozilla/Maybe.h" #include "mozilla/PresShell.h" #include "mozilla/PresShellInlines.h" #include "mozilla/ReflowInput.h" #include "mozilla/RestyleManager.h" #include "mozilla/ResultExtensions.h" #include "mozilla/SVGIntegrationUtils.h" #include "mozilla/SVGMaskFrame.h" #include "mozilla/SVGObserverUtils.h" #include "mozilla/SVGTextFrame.h" #include "mozilla/SVGUtils.h" #include "mozilla/ScrollContainerFrame.h" #include "mozilla/SelectionMovementUtils.h" #include "mozilla/ServoStyleConsts.h" #include "mozilla/Sprintf.h" #include "mozilla/StaticAnalysisFunctions.h" #include "mozilla/StaticPrefs_dom.h" #include "mozilla/StaticPrefs_layout.h" #include "mozilla/StaticPrefs_print.h" #include "mozilla/StaticPrefs_ui.h" #include "mozilla/TextControlElement.h" #include "mozilla/ToString.h" #include "mozilla/Try.h" #include "mozilla/ViewportFrame.h" #include "mozilla/ViewportUtils.h" #include "mozilla/WritingModes.h" #include "mozilla/dom/AncestorIterator.h" #include "mozilla/dom/CSSAnimation.h" #include "mozilla/dom/CSSTransition.h" #include "mozilla/dom/ContentVisibilityAutoStateChangeEvent.h" #include "mozilla/dom/DocumentInlines.h" #include "mozilla/dom/ElementInlines.h" #include "mozilla/dom/HTMLDetailsElement.h" #include "mozilla/dom/Selection.h" #include "mozilla/dom/Text.h" #include "mozilla/gfx/2D.h" #include "mozilla/gfx/PathHelpers.h" #include "mozilla/intl/BidiEmbeddingLevel.h" #include "nsAnimationManager.h" #include "nsAtom.h" #include "nsBidiPresUtils.h" #include "nsCOMPtr.h" #include "nsCSSFrameConstructor.h" #include "nsCSSProps.h" #include "nsCSSRendering.h" #include "nsCanvasFrame.h" #include "nsContentUtils.h" #include "nsFieldSetFrame.h" #include "nsFlexContainerFrame.h" #include "nsFocusManager.h" #include "nsFrameList.h" #include "nsFrameSelection.h" #include "nsFrameState.h" #include "nsFrameTraversal.h" #include "nsGkAtoms.h" #include "nsGridContainerFrame.h" #include "nsIBaseWindow.h" #include "nsIContent.h" #include "nsIContentInlines.h" #include "nsIPercentBSizeObserver.h" #include "nsImageFrame.h" #include "nsInlineFrame.h" #include "nsLayoutUtils.h" #include "nsMenuPopupFrame.h" #include "nsNameSpaceManager.h" #include "nsPlaceholderFrame.h" #include "nsPresContext.h" #include "nsPresContextInlines.h" #include "nsRange.h" #include "nsReadableUtils.h" #include "nsString.h" #include "nsStyleConsts.h" #include "nsStyleStructInlines.h" #include "nsStyleTransformMatrix.h" #include "nsTableWrapperFrame.h" #include "nsTextControlFrame.h" #include "nsXULElement.h" // For triple-click pref #include "RetainedDisplayListBuilder.h" #include "ScrollSnap.h" #include "StickyScrollContainer.h" #include "gfxContext.h" #include "imgIRequest.h" #include "nsBlockFrame.h" #include "nsChangeHint.h" #include "nsContainerFrame.h" #include "nsDisplayList.h" #include "nsError.h" #include "nsFontInflationData.h" #include "nsIFrameInlines.h" #include "nsRegion.h" #include "nsStyleChangeList.h" #include "nsSubDocumentFrame.h" #include "nsViewportInfo.h" #include "nsWindowSizes.h" #ifdef ACCESSIBILITY # include "nsAccessibilityService.h" #endif #if defined(ACCESSIBILITY) && defined(MOZ_ENABLE_SKIA_PDF) # include "mozilla/a11y/PdfStructTreeBuilder.h" #endif #include "ActiveLayerTracker.h" #include "mozilla/AsyncEventDispatcher.h" #include "mozilla/CSSClipPathInstance.h" #include "mozilla/EffectCompositor.h" #include "mozilla/EffectSet.h" #include "mozilla/EventListenerManager.h" #include "mozilla/EventStateManager.h" #include "mozilla/LookAndFeel.h" #include "mozilla/MouseEvents.h" #include "mozilla/Preferences.h" #include "mozilla/ServoStyleSet.h" #include "mozilla/ServoStyleSetInlines.h" #include "mozilla/css/ImageLoader.h" #include "mozilla/dom/HTMLBodyElement.h" #include "mozilla/dom/SVGPathData.h" #include "mozilla/dom/TouchEvent.h" #include "mozilla/gfx/Tools.h" #include "mozilla/layers/WebRenderUserData.h" #include "mozilla/layout/ScrollAnchorContainer.h" #include "nsITheme.h" #include "nsPrintfCString.h" using namespace mozilla; using namespace mozilla::css; using namespace mozilla::dom; using namespace mozilla::gfx; using namespace mozilla::layers; using namespace mozilla::layout; using nsStyleTransformMatrix::TransformReferenceBox; nsIFrame* nsILineIterator::LineInfo::GetLastFrameOnLine() const { if (!mNumFramesOnLine) { return nullptr; // empty line, not illegal } MOZ_ASSERT(mFirstFrameOnLine); nsIFrame* maybeLastFrame = mFirstFrameOnLine; for ([[maybe_unused]] int32_t i : IntegerRange(mNumFramesOnLine - 1)) { maybeLastFrame = maybeLastFrame->GetNextSibling(); if (NS_WARN_IF(!maybeLastFrame)) { return nullptr; } } return maybeLastFrame; } #ifdef HAVE_64BIT_BUILD static_assert(sizeof(nsIFrame) == 120, "nsIFrame should remain small"); #else static_assert(sizeof(void*) == 4, "Odd build config?"); // FIXME(emilio): Investigate why win32 and android-arm32 have bigger sizes (80) // than Linux32 (76). static_assert(sizeof(nsIFrame) <= 80, "nsIFrame should remain small"); #endif const mozilla::LayoutFrameType nsIFrame::sLayoutFrameTypes[kFrameClassCount] = { #define FRAME_ID(class_, type_, ...) mozilla::LayoutFrameType::type_, #define ABSTRACT_FRAME_ID(...) #include "mozilla/FrameIdList.h" #undef FRAME_ID #undef ABSTRACT_FRAME_ID }; const nsIFrame::ClassFlags nsIFrame::sLayoutFrameClassFlags[kFrameClassCount] = { #define FRAME_ID(class_, type_, flags_, ...) flags_, #define ABSTRACT_FRAME_ID(...) #include "mozilla/FrameIdList.h" #undef FRAME_ID #undef ABSTRACT_FRAME_ID }; std::string format_as(nsDirection aDirection) { return aDirection == eDirNext ? "eDirNext" : "eDirPrevious"; } std::ostream& operator<<(std::ostream& aStream, nsDirection aDirection) { return aStream << format_as(aDirection); } struct nsContentAndOffset { nsIContent* mContent = nullptr; int32_t mOffset = 0; }; #include "nsILineIterator.h" #include "prenv.h" FrameDestroyContext::~FrameDestroyContext() { for (auto& content : mozilla::Reversed(mAnonymousContent)) { mPresShell->NativeAnonymousContentWillBeRemoved(content); content->UnbindFromTree(); } } // Formerly the nsIFrameDebug interface std::ostream& operator<<(std::ostream& aStream, const nsReflowStatus& aStatus) { char complete = 'Y'; if (aStatus.IsIncomplete()) { complete = 'N'; } else if (aStatus.IsOverflowIncomplete()) { complete = 'O'; } char brk = 'N'; if (aStatus.IsInlineBreakBefore()) { brk = 'B'; } else if (aStatus.IsInlineBreakAfter()) { brk = 'A'; } aStream << "[" << "Complete=" << complete << "," << "NIF=" << (aStatus.NextInFlowNeedsReflow() ? 'Y' : 'N') << "," << "Break=" << brk << "," << "FirstLetter=" << (aStatus.FirstLetterComplete() ? 'Y' : 'N') << "]"; return aStream; } #ifdef DEBUG /** * Note: the log module is created during library initialization which * means that you cannot perform logging before then. */ mozilla::LazyLogModule nsIFrame::sFrameLogModule("frame"); #endif NS_DECLARE_FRAME_PROPERTY_DELETABLE(AbsoluteContainingBlockProperty, AbsoluteContainingBlock) bool nsIFrame::HasAbsolutelyPositionedChildren() const { const auto* absCB = GetAbsoluteContainingBlock(); return absCB && absCB->HasAbsoluteFrames(); } AbsoluteContainingBlock* nsIFrame::GetAbsoluteContainingBlock() const { if (!IsAbsoluteContainer()) { return nullptr; } AbsoluteContainingBlock* absCB = GetProperty(AbsoluteContainingBlockProperty()); NS_ASSERTION(absCB, "The frame is marked as an abspos container but doesn't have " "the property"); return absCB; } void nsIFrame::MarkAsAbsoluteContainingBlock() { MOZ_ASSERT(HasAnyStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN)); NS_ASSERTION(!GetProperty(AbsoluteContainingBlockProperty()), "Already has an abs-pos containing block property?"); NS_ASSERTION(!HasAnyStateBits(NS_FRAME_HAS_ABSPOS_CHILDREN), "Already has NS_FRAME_HAS_ABSPOS_CHILDREN state bit?"); AddStateBits(NS_FRAME_HAS_ABSPOS_CHILDREN); SetProperty(AbsoluteContainingBlockProperty(), new AbsoluteContainingBlock()); } void nsIFrame::MarkAsNotAbsoluteContainingBlock() { NS_ASSERTION(!HasAbsolutelyPositionedChildren(), "Think of the children!"); NS_ASSERTION(GetProperty(AbsoluteContainingBlockProperty()), "Should have an abs-pos containing block property"); NS_ASSERTION(HasAnyStateBits(NS_FRAME_HAS_ABSPOS_CHILDREN), "Should have NS_FRAME_HAS_ABSPOS_CHILDREN state bit"); MOZ_ASSERT(HasAnyStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN)); RemoveStateBits(NS_FRAME_HAS_ABSPOS_CHILDREN); RemoveProperty(AbsoluteContainingBlockProperty()); } bool nsIFrame::CheckAndClearPaintedState() { bool result = HasAnyStateBits(NS_FRAME_PAINTED_THEBES); RemoveStateBits(NS_FRAME_PAINTED_THEBES); for (const auto& childList : ChildLists()) { for (nsIFrame* child : childList.mList) { if (child->CheckAndClearPaintedState()) { result = true; } } } return result; } nsIFrame* nsIFrame::FindLineContainer() const { MOZ_ASSERT(IsLineParticipant()); nsIFrame* parent = GetParent(); while (parent && (parent->IsLineParticipant() || parent->CanContinueTextRun())) { parent = parent->GetParent(); } return parent; } bool nsIFrame::CheckAndClearDisplayListState() { bool result = BuiltDisplayList(); SetBuiltDisplayList(false); for (const auto& childList : ChildLists()) { for (nsIFrame* child : childList.mList) { if (child->CheckAndClearDisplayListState()) { result = true; } } } return result; } bool nsIFrame::IsVisibleConsideringAncestors(uint32_t aFlags) const { if (!StyleVisibility()->IsVisible() || HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { return false; } if (PresShell()->IsUnderHiddenEmbedderElement()) { return false; } const nsIFrame* frame = this; while (frame) { // Checking mMozSubtreeHiddenOnlyVisually is relatively slow because it // involves loading more memory. It's only allowed in chrome sheets so let's // only support it in the parent process so we can mostly optimize this out // in content processes. if (XRE_IsParentProcess()) { if (const nsMenuPopupFrame* popup = do_QueryFrame(frame); popup && !popup->IsOpen()) { return false; } if (frame->StyleUIReset()->mMozSubtreeHiddenOnlyVisually) { return false; } } // This method is used to determine if a frame is focusable, because it's // called by nsIFrame::IsFocusable. `content-visibility: auto` should not // force this frame to be unfocusable, so we only take into account // `content-visibility: hidden` here. if (this != frame && frame->HidesContent(IncludeContentVisibility::Hidden)) { return false; } if (nsIFrame* parent = frame->GetParent()) { frame = parent; } else { parent = nsLayoutUtils::GetCrossDocParentFrameInProcess(frame); if (!parent) { break; } if ((aFlags & nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY) == 0 && parent->PresContext()->IsChrome() && !frame->PresContext()->IsChrome()) { break; } frame = parent; } } return true; } void nsIFrame::FindCloserFrameForSelection( const nsPoint& aPoint, FrameWithDistance* aCurrentBestFrame) { if (nsLayoutUtils::PointIsCloserToRect(aPoint, mRect, aCurrentBestFrame->mXDistance, aCurrentBestFrame->mYDistance)) { aCurrentBestFrame->mFrame = this; } } void nsIFrame::ElementStateChanged(mozilla::dom::ElementState aStates) {} void WeakFrame::Clear(mozilla::PresShell* aPresShell) { if (aPresShell) { aPresShell->RemoveWeakFrame(this); } mFrame = nullptr; } AutoWeakFrame::AutoWeakFrame(const WeakFrame& aOther) : mPrev(nullptr), mFrame(nullptr) { Init(aOther.GetFrame()); } void AutoWeakFrame::Clear(mozilla::PresShell* aPresShell) { if (aPresShell) { aPresShell->RemoveAutoWeakFrame(this); } mFrame = nullptr; mPrev = nullptr; } AutoWeakFrame::~AutoWeakFrame() { Clear(mFrame ? mFrame->PresContext()->GetPresShell() : nullptr); } void AutoWeakFrame::Init(nsIFrame* aFrame) { Clear(mFrame ? mFrame->PresContext()->GetPresShell() : nullptr); mFrame = aFrame; if (mFrame) { mozilla::PresShell* presShell = mFrame->PresContext()->GetPresShell(); NS_WARNING_ASSERTION(presShell, "Null PresShell in AutoWeakFrame!"); if (presShell) { presShell->AddAutoWeakFrame(this); } else { mFrame = nullptr; } } } void WeakFrame::Init(nsIFrame* aFrame) { Clear(mFrame ? mFrame->PresContext()->GetPresShell() : nullptr); mFrame = aFrame; if (mFrame) { mozilla::PresShell* presShell = mFrame->PresContext()->GetPresShell(); MOZ_ASSERT(presShell, "Null PresShell in WeakFrame!"); if (presShell) { presShell->AddWeakFrame(this); } else { mFrame = nullptr; } } } nsIFrame* NS_NewEmptyFrame(PresShell* aPresShell, ComputedStyle* aStyle) { return new (aPresShell) nsIFrame(aStyle, aPresShell->GetPresContext()); } nsIFrame::~nsIFrame() { MOZ_COUNT_DTOR(nsIFrame); MOZ_ASSERT(GetVisibility() != Visibility::ApproximatelyVisible, "Visible nsFrame is being destroyed"); } NS_IMPL_FRAMEARENA_HELPERS(nsIFrame) // Dummy operator delete. Will never be called, but must be defined // to satisfy some C++ ABIs. void nsIFrame::operator delete(void*, size_t) { MOZ_CRASH("nsIFrame::operator delete should never be called"); } NS_QUERYFRAME_HEAD(nsIFrame) NS_QUERYFRAME_ENTRY(nsIFrame) NS_QUERYFRAME_TAIL_INHERITANCE_ROOT ///////////////////////////////////////////////////////////////////////////// // nsIFrame static bool IsFontSizeInflationContainer(nsIFrame* aFrame, const nsStyleDisplay* aStyleDisplay) { /* * Font size inflation is built around the idea that we're inflating * the fonts for a pan-and-zoom UI so that when the user scales up a * block or other container to fill the width of the device, the fonts * will be readable. To do this, we need to pick what counts as a * container. * * From a code perspective, the only hard requirement is that frames * that are line participants (nsIFrame::IsLineParticipant) are never * containers, since line layout assumes that the inflation is consistent * within a line. * * This is not an imposition, since we obviously want a bunch of text * (possibly with inline elements) flowing within a block to count the * block (or higher) as its container. * * We also want form controls, including the text in the anonymous * content inside of them, to match each other and the text next to * them, so they and their anonymous content should also not be a * container. * * However, because we can't reliably compute sizes across XUL during * reflow, any XUL frame with a XUL parent is always a container. * * There are contexts where it would be nice if some blocks didn't * count as a container, so that, for example, an indented quotation * didn't end up with a smaller font size. However, it's hard to * distinguish these situations where we really do want the indented * thing to count as a container, so we don't try, and blocks are * always containers. */ // The root frame should always be an inflation container. if (!aFrame->GetParent()) { return true; } nsIContent* content = aFrame->GetContent(); if (content && content->IsInNativeAnonymousSubtree()) { // Native anonymous content shouldn't be a font inflation root, // except for the custom content container. return content == aFrame->PresContext()->Document()->GetCustomContentContainer(); } LayoutFrameType frameType = aFrame->Type(); bool isInline = aFrame->GetDisplay().IsInlineFlow() || RubyUtils::IsRubyBox(frameType) || (aStyleDisplay->IsFloatingStyle() && frameType == LayoutFrameType::Letter) || // Given multiple frames for the same node, only the // outer one should be considered a container. // (Important, e.g., for nsSelectsAreaFrame.) (aFrame->GetParent()->GetContent() == content) || (content && // Form controls shouldn't become inflation containers. (content->IsAnyOfHTMLElements(nsGkAtoms::option, nsGkAtoms::optgroup, nsGkAtoms::select, nsGkAtoms::input, nsGkAtoms::button, nsGkAtoms::textarea))); NS_ASSERTION(!aFrame->IsLineParticipant() || isInline || // br frames and mathml frames report being line // participants even when their position or display is // set aFrame->IsBrFrame() || aFrame->IsMathMLFrame(), "line participants must not be containers"); return !isInline; } static void MaybeScheduleReflowSVGNonDisplayText(nsIFrame* aFrame) { if (!aFrame->IsInSVGTextSubtree()) { return; } // We need to ensure that any non-display SVGTextFrames get reflowed when a // child text frame gets new style. Thus we need to schedule a reflow in // |DidSetComputedStyle|. We also need to call it from |DestroyFrom|, // because otherwise we won't get notified when style changes to // "display:none". SVGTextFrame* svgTextFrame = static_cast( nsLayoutUtils::GetClosestFrameOfType(aFrame, LayoutFrameType::SVGText)); nsIFrame* anonBlock = svgTextFrame->PrincipalChildList().FirstChild(); // Note that we must check NS_FRAME_FIRST_REFLOW on our SVGTextFrame's // anonymous block frame rather than our aFrame, since NS_FRAME_FIRST_REFLOW // may be set on us if we're a new frame that has been inserted after the // document's first reflow. (In which case this DidSetComputedStyle call may // be happening under frame construction under a Reflow() call.) if (!anonBlock || anonBlock->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) { return; } if (!svgTextFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY) || svgTextFrame->HasAnyStateBits(NS_STATE_SVG_TEXT_IN_REFLOW)) { return; } svgTextFrame->ScheduleReflowSVGNonDisplayText( IntrinsicDirty::FrameAncestorsAndDescendants); } bool nsIFrame::IsReplaced() const { if (HasAnyClassFlag(ClassFlags::Replaced)) { return true; } if (!Style()->IsAnonBox() && mContent->IsHTMLElement(nsGkAtoms::button)) { // Button always behaves as a replaced element. return true; } return false; } bool nsIFrame::IsAtomicInline() const { // See: https://drafts.csswg.org/css-display-4/#atomic-inline return IsInlineOutside() && (IsReplaced() || !StyleDisplay()->IsInlineInsideStyle()); } bool nsIFrame::ShouldPropagateRepaintsToRoot() const { if (!IsPrimaryFrame()) { // special case for table frames because style images are associated to the // table frame, but the table wrapper frame is the primary frame if (IsTableFrame()) { MOZ_ASSERT(GetParent() && GetParent()->IsTableWrapperFrame()); return GetParent()->ShouldPropagateRepaintsToRoot(); } return false; } nsIContent* content = GetContent(); Document* document = content->OwnerDoc(); return content == document->GetRootElement() || content == document->GetBodyElement(); } bool nsIFrame::IsRenderedLegend() const { if (auto* parent = GetParent(); parent && parent->IsFieldSetFrame()) { return static_cast(parent)->GetLegend() == this; } return false; } void nsIFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, nsIFrame* aPrevInFlow) { MOZ_ASSERT(nsQueryFrame::FrameIID(mClass) == GetFrameId()); MOZ_ASSERT(!mContent, "Double-initing a frame?"); mContent = aContent; mParent = aParent; MOZ_ASSERT(!mParent || PresShell() == mParent->PresShell()); if (aPrevInFlow) { mWritingMode = aPrevInFlow->GetWritingMode(); // Copy some state bits from prev-in-flow (the bits that should apply // throughout a continuation chain). The bits are sorted according to their // order in nsFrameStateBits.h. // clang-format off AddStateBits(aPrevInFlow->GetStateBits() & (NS_FRAME_GENERATED_CONTENT | NS_FRAME_OUT_OF_FLOW | NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN | NS_FRAME_PART_OF_IBSPLIT | NS_FRAME_MAY_BE_TRANSFORMED | NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR)); // clang-format on // Copy other bits in nsIFrame from prev-in-flow. mHasColumnSpanSiblings = aPrevInFlow->HasColumnSpanSiblings(); // If our prev-in-flow is an absolute containing block, we must be one, too. if (aPrevInFlow->IsAbsoluteContainer()) { MOZ_ASSERT(HasAnyStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN), "We should've carried this bit from our prev-in-flow!"); MarkAsAbsoluteContainingBlock(); } } else { PresContext()->ConstructedFrame(); } if (GetParent()) { if (MOZ_UNLIKELY(mContent == PresContext()->Document()->GetRootElement() && mContent == GetParent()->GetContent())) { // Our content is the root element and we have the same content as our // parent. That is, we are the internal anonymous frame of the root // element. Copy the used mWritingMode from our parent because // mDocElementContainingBlock gets its mWritingMode from . mWritingMode = GetParent()->GetWritingMode(); } // Copy some state bits from our parent (the bits that should apply // recursively throughout a subtree). The bits are sorted according to their // order in nsFrameStateBits.h. // clang-format off AddStateBits(GetParent()->GetStateBits() & (NS_FRAME_GENERATED_CONTENT | NS_FRAME_IS_SVG_TEXT | NS_FRAME_IN_POPUP | NS_FRAME_IS_NONDISPLAY)); // clang-format on if (HasAnyStateBits(NS_FRAME_IN_POPUP) && TrackingVisibility()) { // Assume all frames in popups are visible. IncApproximateVisibleCount(); } } if (aPrevInFlow) { mMayHaveOpacityAnimation = aPrevInFlow->MayHaveOpacityAnimation(); mMayHaveTransformAnimation = aPrevInFlow->MayHaveTransformAnimation(); } else if (mContent) { // It's fine to fetch the EffectSet for the style frame here because in the // following code we take care of the case where animations may target // a different frame. EffectSet* effectSet = EffectSet::GetForStyleFrame(this); if (effectSet) { mMayHaveOpacityAnimation = effectSet->MayHaveOpacityAnimation(); if (effectSet->MayHaveTransformAnimation()) { // If we are the inner table frame for display:table content, then // transform animations should go on our parent frame (the table wrapper // frame). // // We do this when initializing the child frame (table inner frame), // because when initializng the table wrapper frame, we don't yet have // access to its children so we can't tell if we have transform // animations or not. if (SupportsCSSTransforms()) { mMayHaveTransformAnimation = true; AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED); } else if (aParent && nsLayoutUtils::GetStyleFrame(aParent) == this) { MOZ_ASSERT( aParent->SupportsCSSTransforms(), "Style frames that don't support transforms should have parents" " that do"); aParent->mMayHaveTransformAnimation = true; aParent->AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED); } } } } const nsStyleDisplay* disp = StyleDisplay(); if (disp->HasTransform(this)) { // If 'transform' dynamically changes, RestyleManager takes care of // updating this bit. AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED); } if (nsLayoutUtils::FontSizeInflationEnabled(PresContext()) || !GetParent() #ifdef DEBUG // We have assertions that check inflation invariants even when // font size inflation is not enabled. || true #endif ) { if (IsFontSizeInflationContainer(this, disp)) { AddStateBits(NS_FRAME_FONT_INFLATION_CONTAINER); if (!GetParent() || // I'd use NS_FRAME_OUT_OF_FLOW, but it's not set yet. disp->IsFloating(this) || disp->IsAbsolutelyPositioned(this) || GetParent()->IsFlexContainerFrame() || GetParent()->IsGridContainerFrame()) { AddStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT); } } NS_ASSERTION( GetParent() || HasAnyStateBits(NS_FRAME_FONT_INFLATION_CONTAINER), "root frame should always be a container"); } if (TrackingVisibility() && PresShell()->AssumeAllFramesVisible()) { IncApproximateVisibleCount(); } DidSetComputedStyle(nullptr); // For a newly created frame, we need to update this frame's visibility state. // Usually we update the state when the frame is restyled and has a // VisibilityChange change hint but we don't generate any change hints for // newly created frames. // Note: We don't need to do this for placeholders since placeholders have // different styles so that the styles don't have visibility:hidden even if // the parent has visibility:hidden style. We also don't need to update the // state when creating continuations because its visibility is the same as its // prev-in-flow, and the animation code cares only primary frames. if (!IsPlaceholderFrame() && !aPrevInFlow) { UpdateVisibleDescendantsState(); } if (!aPrevInFlow && HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { // We aren't going to get a reflow, so nothing else will call // InvalidateRenderingObservers, we have to do it here. SVGObserverUtils::InvalidateRenderingObservers(this); } } void nsIFrame::InitPrimaryFrame() { MOZ_ASSERT(IsPrimaryFrame()); HandlePrimaryFrameStyleChange(nullptr); } // If `aFrame` is a generated content container (::before/::after/::marker/...) // whose content changed only in the text of its string items, rewrite the // existing generated text nodes in place instead of reconstructing the frame. // See nsStyleContent::CanUpdateGeneratedContentText and its usage. static void UpdateGeneratedContentTextIfNeeded( nsIFrame* aFrame, const ComputedStyle* aOldComputedStyle) { if (!aFrame->IsGeneratedContentFrame()) { return; } // Update once per element, not per continuation. if (aFrame->GetPrevContinuation()) { return; } nsIContent* content = aFrame->GetContent(); if (!content || !content->IsRootOfNativeAnonymousSubtree()) { return; } const auto* oldStyle = aOldComputedStyle->StyleContent(); const auto* newStyle = aFrame->StyleContent(); if (oldStyle->mContent == newStyle->mContent || !nsStyleContent::CanUpdateGeneratedContentText(*oldStyle, *newStyle)) { return; } auto oldItems = oldStyle->NonAltContentItems(); auto newItems = newStyle->NonAltContentItems(); MOZ_ASSERT(oldItems.Length() == newItems.Length(), "CanUpdateGeneratedContentText should guarantee equal lengths"); nsIContent* child = content->GetFirstChild(); for (size_t i = 0; i < newItems.Length(); i++) { const auto& item = newItems[i]; const bool isString = item.IsString(); if (isString && item.AsString().AsString().IsEmpty()) { // Empty strings have no associated content node. continue; } MOZ_ASSERT(child, "Mismatch between content items and generated content nodes?"); if (isString && item != oldItems[i]) { MOZ_ASSERT(child->IsText(), "A non-empty string item should map to a text node"); child->AsText()->SetText( NS_ConvertUTF8toUTF16(item.AsString().AsString()), /* aNotify = */ true); } child = child->GetNextSibling(); } MOZ_ASSERT(!child, "More generated content than content items."); } void nsIFrame::HandlePrimaryFrameStyleChange(ComputedStyle* aOldStyle) { const nsStyleDisplay* disp = StyleDisplay(); const nsStyleDisplay* oldDisp = aOldStyle ? aOldStyle->StyleDisplay() : nullptr; const bool wasQueryContainer = oldDisp && oldDisp->IsQueryContainer(); const bool isQueryContainer = disp->IsQueryContainer(); if (wasQueryContainer != isQueryContainer) { auto* pc = PresContext(); if (isQueryContainer) { pc->RegisterContainerQueryFrame(this); } else { pc->UnregisterContainerQueryFrame(this); } } const bool wasReferringToAnchor = aOldStyle && oldDisp->IsAbsolutelyPositionedStyle() && aOldStyle->HasAnchorPosReference(); const bool isReferringToAnchor = HasAnchorPosReference(); if (wasReferringToAnchor && !isReferringToAnchor) { PresShell()->RemoveAnchorPosPositioned(this); RemoveProperty(NormalPositionProperty()); } else if (!wasReferringToAnchor && isReferringToAnchor) { PresShell()->AddAnchorPosPositioned(this); } bool handleAnchorPosAnchorNameChange = oldDisp ? oldDisp->mAnchorName != disp->mAnchorName : disp->HasAnchorName(); if (handleAnchorPosAnchorNameChange && !HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { // TODO: Add invalidation. // TODO: Only remove/add the necessary names below. if (oldDisp && oldDisp->HasAnchorName()) { for (const auto& name : oldDisp->mAnchorName.AsSpan()) { PresShell()->RemoveAnchorPosAnchor(name.AsAtom(), this); } } for (const auto& name : disp->mAnchorName.AsSpan()) { PresShell()->AddAnchorPosAnchor(name.AsAtom(), this); } } // According to the Anchor Positioning spec, // https://drafts.csswg.org/css-anchor-position-1/#last-successful-position-option: // // If el has a last successful position option remove its last successful // position option if any of the following are true: // 1. Its computed position value has changed, its containing block has // changed, or it no longer generates a box. // 2. Its computed value for any longhand of position-try has changed. // 3. Its computed value for any @position-try property has changed. // 4. Any of the @position-try rules referenced by it have been added, // removed, or mutated. // // Case 1 will cause a reframe, so does not need to be handled here. // Cases 2 and 3 are handled here. // TODO: case 4, see bug 1987960. if (aOldStyle && HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) && HasProperty(LastSuccessfulPositionFallback())) { const auto* pos = StylePosition(); const auto* oldPos = aOldStyle->StylePosition(); if (pos->mPositionTryFallbacks != oldPos->mPositionTryFallbacks || pos->mPositionTryOrder != oldPos->mPositionTryOrder || pos->mOffset != oldPos->mOffset || pos->mAlignSelf != oldPos->mAlignSelf || pos->mJustifySelf != oldPos->mJustifySelf || pos->mPositionAnchor != oldPos->mPositionAnchor || pos->mPositionArea != oldPos->mPositionArea || pos->mMinWidth != oldPos->mMinWidth || pos->mMinHeight != oldPos->mMinHeight || pos->mMaxWidth != oldPos->mMaxWidth || pos->mMaxHeight != oldPos->mMaxHeight || pos->mWidth != oldPos->mWidth || pos->mHeight != oldPos->mHeight || StyleMargin()->mMargin != aOldStyle->StyleMargin()->mMargin) { RemoveProperty(LastSuccessfulPositionFallback()); } } const auto cv = disp->ContentVisibility(*this); const auto oldCv = oldDisp ? oldDisp->ContentVisibility(*this) : StyleContentVisibility::Visible; if (!oldDisp || cv != oldCv) { // 'content-visibility' has changed (or this is a new frame being // initialized). Inform PresShell so it can update its bookkeeping // about frames with particular content-visibility values: // * Inform it of the new value (for the values that it cares about): if (cv == StyleContentVisibility::Hidden) { PresShell()->IncrementContentVisibilityHiddenCount(); } else if (cv == StyleContentVisibility::Auto) { PresShell()->RegisterContentVisibilityAutoFrame(this); } // * Inform it of the old value (for the values that it cares about): if (oldCv == StyleContentVisibility::Hidden) { PresShell()->DecrementContentVisibilityHiddenCount(); } else if (oldCv == StyleContentVisibility::Auto) { if (auto* element = Element::FromNodeOrNull(GetContent())) { element->ClearContentRelevancy(); } PresShell()->UnregisterContentVisibilityAutoFrame(this); } PresContext()->SetNeedsToUpdateHiddenByContentVisibilityForAnimations(); } HandleLastRememberedSize(); bool handleStickyChange = oldDisp ? (disp->mPosition != oldDisp->mPosition && (disp->mPosition == StylePositionProperty::Sticky || oldDisp->mPosition == StylePositionProperty::Sticky)) : disp->mPosition == StylePositionProperty::Sticky; if (handleStickyChange && !HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { if (auto* ssc = StickyScrollContainer::GetOrCreateForFrame(this)) { if (disp->mPosition == StylePositionProperty::Sticky) { ssc->AddFrame(this); } else { ssc->RemoveFrame(this); } } } } void nsIFrame::Destroy(DestroyContext& aContext) { NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(), "destroy called on frame while scripts not blocked"); NS_ASSERTION(!GetNextSibling() && !GetPrevSibling(), "Frames should be removed before destruction."); MOZ_ASSERT(!HasAbsolutelyPositionedChildren()); MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT), "NS_FRAME_PART_OF_IBSPLIT set on non-nsContainerFrame?"); MaybeScheduleReflowSVGNonDisplayText(this); SVGObserverUtils::InvalidateDirectRenderingObservers( this, SVGObserverUtils::InvalidationFlag::FrameBeingDestroyed); const auto* disp = StyleDisplay(); if (disp->mPosition == StylePositionProperty::Sticky) { if (auto* ssc = StickyScrollContainer::GetOrCreateForFrame(this)) { ssc->RemoveFrame(this); } } if (HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) { if (nsPlaceholderFrame* placeholder = GetPlaceholderFrame()) { placeholder->SetOutOfFlowFrame(nullptr); } } nsPresContext* pc = PresContext(); mozilla::PresShell* ps = pc->GetPresShell(); if (IsPrimaryFrame()) { if (disp->IsQueryContainer()) { pc->UnregisterContainerQueryFrame(this); } auto contentVisibility = disp->ContentVisibility(*this); if (contentVisibility == StyleContentVisibility::Hidden) { ps->DecrementContentVisibilityHiddenCount(); } else if (contentVisibility == StyleContentVisibility::Auto) { ps->UnregisterContentVisibilityAutoFrame(this); } // This needs to happen before we clear our Properties() table. ActiveLayerTracker::TransferActivityToContent(this, mContent); } ScrollAnchorContainer* anchor = nullptr; if (IsScrollAnchor(&anchor)) { anchor->InvalidateAnchor(); } if (HasCSSAnimations() || HasCSSTransitions() || // It's fine to look up the style frame here since if we're destroying the // frames for display:table content we should be destroying both wrapper // and inner frame. EffectSet::GetForStyleFrame(this)) { // If no new frame for this element is created by the end of the // restyling process, stop animations and transitions for this frame RestyleManager::AnimationsWithDestroyedFrame* adf = pc->RestyleManager()->GetAnimationsWithDestroyedFrame(); // AnimationsWithDestroyedFrame only lives during the restyling process. if (adf) { adf->Put(mContent, mComputedStyle); } } if (HasAnchorPosName()) { for (const auto& name : disp->mAnchorName.AsSpan()) { PresShell()->RemoveAnchorPosAnchor(name.AsAtom(), this); } } if (HasAnchorPosReference()) { ps->RemoveAnchorPosPositioned(this); } // Disable visibility tracking. Note that we have to do this before we clear // frame properties and lose track of whether we were previously visible. // XXX(seth): It'd be ideal to assert that we're already marked nonvisible // here, but it's unfortunately tricky to guarantee in the face of things like // frame reconstruction induced by style changes. DisableVisibilityTracking(); // Ensure that we're not in the approximately visible list anymore. ps->RemoveFrameFromApproximatelyVisibleList(this); ps->NotifyDestroyingFrame(this); if (HasAnyStateBits(NS_FRAME_EXTERNAL_REFERENCE)) { ps->ClearFrameRefs(this); } // Make sure that our deleted frame can't be returned from GetPrimaryFrame() if (IsPrimaryFrame()) { mContent->SetPrimaryFrame(nullptr); // Pass the root of a generated content subtree (e.g. ::after/::before) to // aPostDestroyData to unbind it after frame destruction is done. if (HasAnyStateBits(NS_FRAME_GENERATED_CONTENT) && mContent->IsRootOfNativeAnonymousSubtree()) { aContext.AddAnonymousContent(mContent.forget()); } } // Remove all properties attached to the frame, to ensure any property // destructors that need the frame pointer are handled properly. RemoveAllProperties(); // Must retrieve the object ID before calling destructors, so the // vtable is still valid. // // Note to future tweakers: having the method that returns the // object size call the destructor will not avoid an indirect call; // the compiler cannot devirtualize the call to the destructor even // if it's from a method defined in the same class. nsQueryFrame::FrameIID id = GetFrameId(); this->~nsIFrame(); #ifdef DEBUG { nsIFrame* rootFrame = ps->GetRootFrame(); MOZ_ASSERT(rootFrame); if (this != rootFrame) { auto* builder = nsLayoutUtils::GetRetainedDisplayListBuilder(rootFrame); auto* data = builder ? builder->Data() : nullptr; const bool inData = data && (data->IsModified(this) || data->HasProps(this)); if (inData) { DL_LOG(LogLevel::Warning, "Frame %p found in retained data", this); } MOZ_ASSERT(!inData, "Deleted frame in retained data!"); } } #endif // Now that we're totally cleaned out, we need to add ourselves to // the presshell's recycler. ps->FreeFrame(id, this); } std::pair nsIFrame::GetOffsets() const { return std::make_pair(0, 0); } static void CompareLayers( const nsStyleImageLayers* aFirstLayers, const nsStyleImageLayers* aSecondLayers, const std::function& aCallback) { NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, (*aFirstLayers)) { const auto& image = aFirstLayers->mLayers[i].mImage; if (!image.IsImageRequestType() || !image.IsResolved()) { continue; } // aCallback is called when the style image in aFirstLayers is thought to // be different with the corresponded one in aSecondLayers if (!aSecondLayers || i >= aSecondLayers->mImageCount || (!aSecondLayers->mLayers[i].mImage.IsResolved() || image.GetImageRequest() != aSecondLayers->mLayers[i].mImage.GetImageRequest())) { if (imgRequestProxy* req = image.GetImageRequest()) { aCallback(req); } } } } static void AddAndRemoveImageAssociations( ImageLoader& aImageLoader, nsIFrame* aFrame, const nsStyleImageLayers* aOldLayers, const nsStyleImageLayers* aNewLayers) { // If the old context had a background-image image, or mask-image image, // and new context does not have the same image, clear the image load // notifier (which keeps the image loading, if it still is) for the frame. // We want to do this conservatively because some frames paint their // backgrounds from some other frame's style data, and we don't want // to clear those notifiers unless we have to. (They'll be reset // when we paint, although we could miss a notification in that // interval.) if (aOldLayers && aFrame->HasImageRequest()) { CompareLayers(aOldLayers, aNewLayers, [&](imgRequestProxy* aReq) { aImageLoader.DisassociateRequestFromFrame(aReq, aFrame); }); } CompareLayers(aNewLayers, aOldLayers, [&](imgRequestProxy* aReq) { aImageLoader.AssociateRequestToFrame(aReq, aFrame); }); } void nsIFrame::AddDisplayItem(nsDisplayItem* aItem) { MOZ_DIAGNOSTIC_ASSERT(!mDisplayItems.Contains(aItem)); mDisplayItems.AppendElement(aItem); #ifdef ACCESSIBILITY if (nsAccessibilityService* accService = GetAccService()) { accService->NotifyOfPossibleBoundsChange(PresShell(), mContent); } #endif } bool nsIFrame::RemoveDisplayItem(nsDisplayItem* aItem) { return mDisplayItems.RemoveElement(aItem); } bool nsIFrame::HasDisplayItems() { return !mDisplayItems.IsEmpty(); } bool nsIFrame::HasDisplayItem(nsDisplayItem* aItem) { return mDisplayItems.Contains(aItem); } bool nsIFrame::HasDisplayItem(uint32_t aKey) { for (nsDisplayItem* i : mDisplayItems) { if (i->GetPerFrameKey() == aKey) { return true; } } return false; } template static void DiscardDisplayItems(nsIFrame* aFrame, Condition aCondition) { for (nsDisplayItem* i : aFrame->DisplayItems()) { // Only discard items that are invalidated by this frame, as we're only // guaranteed to rebuild those items. Table background items are created by // the relevant table part, but have the cell frame as the primary frame, // and we don't want to remove them if this is the cell. if (aCondition(i) && i->FrameForInvalidation() == aFrame) { i->SetCantBeReused(); } } } static void DiscardOldItems(nsIFrame* aFrame) { DiscardDisplayItems(aFrame, [](nsDisplayItem* aItem) { return aItem->IsOldItem(); }); } void nsIFrame::RemoveDisplayItemDataForDeletion() { // Destroying a WebRenderUserDataTable can cause destruction of other objects // which can remove frame properties in their destructor. If we delete a frame // property it runs the destructor of the stored object in the middle of // updating the frame property table, so if the destruction of that object // causes another update to the frame property table it would leave the frame // property table in an inconsistent state. So we remove it from the table and // then destroy it. (bug 1530657) WebRenderUserDataTable* userDataTable = TakeProperty(WebRenderUserDataProperty::Key()); if (userDataTable) { for (const auto& data : userDataTable->Values()) { data->RemoveFromTable(); } delete userDataTable; } auto* builder = nsLayoutUtils::GetRetainedDisplayListBuilder(this); if (!builder) { MOZ_ASSERT(DisplayItems().IsEmpty()); MOZ_ASSERT(!IsFrameModified()); return; } for (nsDisplayItem* i : DisplayItems()) { if (i->GetDependentFrame() == this && !i->HasDeletedFrame()) { i->Frame()->MarkNeedsDisplayItemRebuild(); } i->RemoveFrame(this); } DisplayItems().Clear(); nsAutoString name; #ifdef DEBUG_FRAME_DUMP if (DL_LOG_TEST(LogLevel::Debug)) { GetFrameName(name); } #endif DL_LOGV("Removing display item data for frame %p (%s)", this, NS_ConvertUTF16toUTF8(name).get()); builder->Data()->Remove(this); } void nsIFrame::MarkNeedsDisplayItemRebuild() { if (!nsLayoutUtils::AreRetainedDisplayListsEnabled() || IsFrameModified() || HasAnyStateBits(NS_FRAME_IN_POPUP)) { // Skip frames that are already marked modified. return; } if (Type() == LayoutFrameType::Placeholder) { nsIFrame* oof = static_cast(this)->GetOutOfFlowFrame(); if (oof) { oof->MarkNeedsDisplayItemRebuild(); } // Do not mark placeholder frames modified. return; } #ifdef ACCESSIBILITY if (nsAccessibilityService* accService = GetAccService()) { accService->NotifyOfPossibleBoundsChange(PresShell(), mContent); } #endif nsIFrame* rootFrame = PresShell()->GetRootFrame(); if (rootFrame->IsFrameModified()) { // The whole frame tree is modified. return; } auto* builder = nsLayoutUtils::GetRetainedDisplayListBuilder(this); if (!builder) { MOZ_ASSERT(DisplayItems().IsEmpty()); return; } RetainedDisplayListData* data = builder->Data(); MOZ_ASSERT(data); if (data->AtModifiedFrameLimit()) { // This marks the whole frame tree modified. // See |RetainedDisplayListBuilder::ShouldBuildPartial()|. data->AddModifiedFrame(rootFrame); return; } nsAutoString name; #ifdef DEBUG_FRAME_DUMP if (DL_LOG_TEST(LogLevel::Debug)) { GetFrameName(name); } #endif DL_LOGV("RDL - Rebuilding display items for frame %p (%s)", this, NS_ConvertUTF16toUTF8(name).get()); data->AddModifiedFrame(this); MOZ_ASSERT( PresContext()->LayoutPhaseCount(nsLayoutPhase::DisplayListBuilding) == 0); // Hopefully this is cheap, but we could use a frame state bit to note // the presence of dependencies to speed it up. for (nsDisplayItem* i : DisplayItems()) { if (i->HasDeletedFrame() || i->Frame() == this) { // Ignore the items with deleted frames, and the items with |this| as // the primary frame. continue; } if (i->GetDependentFrame() == this) { // For items with |this| as a dependent frame, mark the primary frame // for rebuild. i->Frame()->MarkNeedsDisplayItemRebuild(); } } } // Subclass hook for style post processing /* virtual */ void nsIFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) { if (aOldComputedStyle) { #ifdef ACCESSIBILITY // Don't notify for reconstructed frames here, since the frame is still // being constructed at this point and so LocalAccessible::GetFrame() will // return null. Style changes for reconstructed frames are handled in // DocAccessible::PruneOrInsertSubtree. if (nsAccessibilityService* accService = GetAccService()) { accService->NotifyOfComputedStyleChange(PresShell(), mContent); } #endif UpdateGeneratedContentTextIfNeeded(this, aOldComputedStyle); } MaybeScheduleReflowSVGNonDisplayText(this); Document* doc = PresContext()->Document(); ImageLoader& loader = doc->EnsureStyleImageLoader(); // Continuing text frame doesn't initialize its continuation pointer before // reaching here for the first time, so we have to exclude text frames. This // doesn't affect correctness because text can't match selectors. // // FIXME(emilio): We should consider fixing that. // // TODO(emilio): Can we avoid doing some / all of the image stuff when // isNonTextFirstContinuation is false? We should consider doing this just for // primary frames and pseudos, but the first-line reparenting code makes it // all bad, should get around to bug 1465474 eventually :( const bool isNonText = !IsTextFrame(); if (isNonText) { mComputedStyle->StartImageLoads(*doc, aOldComputedStyle); } const bool isRootElementStyle = Style()->IsRootElementStyle(); if (isRootElementStyle) { PresShell()->SetNeedsWindowPropertiesSync(); } const nsStyleImageLayers* oldLayers = aOldComputedStyle ? &aOldComputedStyle->StyleBackground()->mImage : nullptr; const nsStyleImageLayers* newLayers = &StyleBackground()->mImage; AddAndRemoveImageAssociations(loader, this, oldLayers, newLayers); oldLayers = aOldComputedStyle ? &aOldComputedStyle->StyleSVGReset()->mMask : nullptr; newLayers = &StyleSVGReset()->mMask; AddAndRemoveImageAssociations(loader, this, oldLayers, newLayers); const nsStyleDisplay* disp = StyleDisplay(); if (aOldComputedStyle) { // Detect style changes that should trigger a scroll anchor adjustment // suppression. // https://drafts.csswg.org/css-scroll-anchoring/#suppression-triggers bool needScrollAnchorSuppression = false; const nsStyleMargin* oldMargin = aOldComputedStyle->StyleMargin(); if (!oldMargin->MarginEquals(*StyleMargin())) { needScrollAnchorSuppression = true; } const nsStylePadding* oldPadding = aOldComputedStyle->StylePadding(); if (oldPadding->mPadding != StylePadding()->mPadding) { SetHasPaddingChange(true); needScrollAnchorSuppression = true; } const nsStyleDisplay* oldDisp = aOldComputedStyle->StyleDisplay(); if (oldDisp->mOverflowAnchor != disp->mOverflowAnchor) { if (auto* container = ScrollAnchorContainer::FindFor(this)) { container->InvalidateAnchor(); } if (ScrollContainerFrame* scrollContainerFrame = do_QueryFrame(this)) { scrollContainerFrame->Anchor()->InvalidateAnchor(); } } if (mInScrollAnchorChain) { const nsStylePosition* pos = StylePosition(); const nsStylePosition* oldPos = aOldComputedStyle->StylePosition(); if (!needScrollAnchorSuppression && (oldPos->mOffset != pos->mOffset || oldPos->mWidth != pos->mWidth || oldPos->mMinWidth != pos->mMinWidth || oldPos->mMaxWidth != pos->mMaxWidth || oldPos->mHeight != pos->mHeight || oldPos->mMinHeight != pos->mMinHeight || oldPos->mMaxHeight != pos->mMaxHeight || oldDisp->mPosition != disp->mPosition || oldDisp->mTransform != disp->mTransform)) { needScrollAnchorSuppression = true; } if (needScrollAnchorSuppression && StaticPrefs::layout_css_scroll_anchoring_suppressions_enabled()) { ScrollAnchorContainer::FindFor(this)->SuppressAdjustments(); } } if (disp->mPosition != oldDisp->mPosition) { if (!disp->IsRelativelyOrStickyPositionedStyle() && oldDisp->IsRelativelyOrStickyPositionedStyle()) { RemoveProperty(NormalPositionProperty()); } } if (disp->mScrollSnapAlign != oldDisp->mScrollSnapAlign) { ScrollSnapUtils::PostPendingResnapFor(this); } if (isRootElementStyle && disp->mScrollSnapType != oldDisp->mScrollSnapType) { if (ScrollContainerFrame* sf = PresShell()->GetRootScrollContainerFrame()) { sf->PostPendingResnap(); } } if (StyleUIReset()->mMozSubtreeHiddenOnlyVisually && !aOldComputedStyle->StyleUIReset()->mMozSubtreeHiddenOnlyVisually) { PresShell::ClearMouseCapture(this); } } imgIRequest* oldBorderImage = aOldComputedStyle ? aOldComputedStyle->StyleBorder()->GetBorderImageRequest() : nullptr; imgIRequest* newBorderImage = StyleBorder()->GetBorderImageRequest(); // FIXME (Bug 759996): The following is no longer true. // For border-images, we can't be as conservative (we need to set the // new loaders if there has been any change) since the CalcDifference // call depended on the result of GetComputedBorder() and that result // depends on whether the image has loaded, start the image load now // so that we'll get notified when it completes loading and can do a // restyle. Otherwise, the image might finish loading from the // network before we start listening to its notifications, and then // we'll never know that it's finished loading. Likewise, we want to // do this for freshly-created frames to prevent a similar race if the // image loads between reflow (which can depend on whether the image // is loaded) and paint. We also don't really care about any callers who try // to paint borders with a different style, because they won't have the // correct size for the border either. if (oldBorderImage != newBorderImage) { // stop and restart the image loading/notification if (oldBorderImage && HasImageRequest()) { loader.DisassociateRequestFromFrame(oldBorderImage, this); } if (newBorderImage) { loader.AssociateRequestToFrame(newBorderImage, this); } } auto GetShapeImageRequest = [](const ComputedStyle* aStyle) -> imgIRequest* { if (!aStyle) { return nullptr; } auto& shape = aStyle->StyleDisplay()->mShapeOutside; if (!shape.IsImage()) { return nullptr; } return shape.AsImage().GetImageRequest(); }; imgIRequest* oldShapeImage = GetShapeImageRequest(aOldComputedStyle); imgIRequest* newShapeImage = GetShapeImageRequest(Style()); if (oldShapeImage != newShapeImage) { if (oldShapeImage && HasImageRequest()) { loader.DisassociateRequestFromFrame(oldShapeImage, this); } if (newShapeImage) { loader.AssociateRequestToFrame( newShapeImage, this, ImageLoader::Flags:: RequiresReflowOnFirstFrameCompleteAndLoadEventBlocking); } } // SVGObserverUtils::GetEffectProperties() asserts that we only invoke it with // the first continuation so we need to check that in advance. const bool isNonTextFirstContinuation = isNonText && !GetPrevContinuation(); if (isNonTextFirstContinuation) { // Kick off loading of external SVG resources referenced from properties if // any. This currently includes filter, clip-path, and mask. SVGObserverUtils::InitiateResourceDocLoads(this); } // If the page contains markup that overrides text direction, and // does not contain any characters that would activate the Unicode // bidi algorithm, we need to call |SetBidiEnabled| on the pres // context before reflow starts. See bug 115921. if (StyleVisibility()->mDirection == StyleDirection::Rtl) { PresContext()->SetBidiEnabled(); } // The following part is for caching offset-path:path(). We cache the // flatten gfx path, so we don't have to rebuild and re-flattern it at // each cycle if we have animations on offset-* with a fixed offset-path. const StyleOffsetPath* oldPath = aOldComputedStyle ? &aOldComputedStyle->StyleDisplay()->mOffsetPath : nullptr; const StyleOffsetPath& newPath = StyleDisplay()->mOffsetPath; if (!oldPath || *oldPath != newPath) { // FIXME: Bug 1837042. Cache all basic shapes. if (newPath.IsPath()) { RefPtr builder = MotionPathUtils::GetPathBuilder(); RefPtr path = MotionPathUtils::BuildSVGPath(newPath.AsSVGPathData(), builder); if (path) { // The newPath could be path('') (i.e. empty path), so its gfx path // could be nullptr, and so we only set property for a non-empty path. SetProperty(nsIFrame::OffsetPathCache(), path.forget().take()); } else { // May have an old cached path, so we have to delete it. RemoveProperty(nsIFrame::OffsetPathCache()); } } else if (oldPath) { RemoveProperty(nsIFrame::OffsetPathCache()); } } if (IsPrimaryFrame()) { MOZ_ASSERT(aOldComputedStyle); HandlePrimaryFrameStyleChange(aOldComputedStyle); } RemoveStateBits(NS_FRAME_SIMPLE_DISPLAYLIST); mMayHaveRoundedCorners = true; } void nsIFrame::HandleLastRememberedSize() { MOZ_ASSERT(IsPrimaryFrame()); auto* element = Element::FromNodeOrNull(mContent); if (!element) { return; } const WritingMode wm = GetWritingMode(); const nsStylePosition* stylePos = StylePosition(); bool canRememberBSize = stylePos->ContainIntrinsicBSize(wm).HasAuto(); bool canRememberISize = stylePos->ContainIntrinsicISize(wm).HasAuto(); if (!canRememberBSize) { element->RemoveLastRememberedBSize(); } if (!canRememberISize) { element->RemoveLastRememberedISize(); } if ((canRememberBSize || canRememberISize) && !HidesContent()) { bool isNonReplacedInline = IsLineParticipant() && !IsReplaced(); if (!isNonReplacedInline) { PresContext()->Document()->ObserveForLastRememberedSize(*element); return; } } PresContext()->Document()->UnobserveForLastRememberedSize(*element); } #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED void nsIFrame::AssertNewStyleIsSane(ComputedStyle& aNewStyle) { MOZ_DIAGNOSTIC_ASSERT( aNewStyle.GetPseudoType() == mComputedStyle->GetPseudoType() || // ::first-line continuations are weird, this should probably be fixed via // bug 1465474. (mComputedStyle->GetPseudoType() == PseudoStyleType::FirstLine && aNewStyle.GetPseudoType() == PseudoStyleType::MozLineFrame) || // ::first-letter continuations are broken, in particular floating ones, // see bug 1490281. The construction code tries to fix this up after the // fact, then restyling undoes it... (mComputedStyle->GetPseudoType() == PseudoStyleType::MozText && aNewStyle.GetPseudoType() == PseudoStyleType::MozFirstLetterContinuation) || (mComputedStyle->GetPseudoType() == PseudoStyleType::MozFirstLetterContinuation && aNewStyle.GetPseudoType() == PseudoStyleType::MozText)); } #endif /* virtual */ nsMargin nsIFrame::GetUsedMargin() const { nsMargin margin; if (((mState & NS_FRAME_FIRST_REFLOW) && !(mState & NS_FRAME_IN_REFLOW)) || IsInSVGTextSubtree()) { return margin; } if (nsMargin* m = GetProperty(UsedMarginProperty())) { margin = *m; } else if (!StyleMargin()->GetMargin(margin)) { // If we get here, our caller probably shouldn't be calling us... NS_ERROR( "Returning bogus 0-sized margin, because this margin " "depends on layout & isn't cached!"); } return margin; } /* virtual */ nsMargin nsIFrame::GetUsedBorder() const { if (((mState & NS_FRAME_FIRST_REFLOW) && !(mState & NS_FRAME_IN_REFLOW)) || IsInSVGTextSubtree()) { return {}; } const nsStyleDisplay* disp = StyleDisplay(); if (IsThemed(disp)) { // Theme methods don't use const-ness. auto* mutable_this = const_cast(this); nsPresContext* pc = PresContext(); LayoutDeviceIntMargin widgetBorder = pc->Theme()->GetWidgetBorder( pc->DeviceContext(), mutable_this, disp->EffectiveAppearance()); return LayoutDevicePixel::ToAppUnits(widgetBorder, pc->AppUnitsPerDevPixel()); } return StyleBorder()->GetComputedBorder(); } /* virtual */ nsMargin nsIFrame::GetUsedPadding() const { nsMargin padding; if (((mState & NS_FRAME_FIRST_REFLOW) && !(mState & NS_FRAME_IN_REFLOW)) || IsInSVGTextSubtree()) { return padding; } const nsStyleDisplay* disp = StyleDisplay(); if (IsThemed(disp)) { // Theme methods don't use const-ness. nsIFrame* mutable_this = const_cast(this); nsPresContext* pc = PresContext(); LayoutDeviceIntMargin widgetPadding; if (pc->Theme()->GetWidgetPadding(pc->DeviceContext(), mutable_this, disp->EffectiveAppearance(), &widgetPadding)) { return LayoutDevicePixel::ToAppUnits(widgetPadding, pc->AppUnitsPerDevPixel()); } } if (nsMargin* p = GetProperty(UsedPaddingProperty())) { padding = *p; } else if (!StylePadding()->GetPadding(padding)) { // If we get here, our caller probably shouldn't be calling us... NS_ERROR( "Returning bogus 0-sized padding, because this padding " "depends on layout & isn't cached!"); } return padding; } nsIFrame::Sides nsIFrame::GetSkipSides() const { if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak == StyleBoxDecorationBreak::Clone) && !HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) { return Sides(); } // Convert the logical skip sides to physical sides using the frame's // writing mode WritingMode writingMode = GetWritingMode(); LogicalSides logicalSkip = GetLogicalSkipSides(); Sides skip; if (logicalSkip.BStart()) { if (writingMode.IsVertical()) { skip |= writingMode.IsVerticalLR() ? SideBits::eLeft : SideBits::eRight; } else { skip |= SideBits::eTop; } } if (logicalSkip.BEnd()) { if (writingMode.IsVertical()) { skip |= writingMode.IsVerticalLR() ? SideBits::eRight : SideBits::eLeft; } else { skip |= SideBits::eBottom; } } if (logicalSkip.IStart()) { if (writingMode.IsVertical()) { skip |= writingMode.IsInlineReversed() ? SideBits::eBottom : SideBits::eTop; } else { skip |= writingMode.IsBidiLTR() ? SideBits::eLeft : SideBits::eRight; } } if (logicalSkip.IEnd()) { if (writingMode.IsVertical()) { skip |= writingMode.IsInlineReversed() ? SideBits::eTop : SideBits::eBottom; } else { skip |= writingMode.IsBidiLTR() ? SideBits::eRight : SideBits::eLeft; } } return skip; } nsRect nsIFrame::GetPaddingRectRelativeToSelf() const { nsMargin border = GetUsedBorder().ApplySkipSides(GetSkipSides()); nsRect r(0, 0, mRect.width, mRect.height); r.Deflate(border); return r; } nsRect nsIFrame::GetPaddingRect() const { return GetPaddingRectRelativeToSelf() + GetPosition(); } WritingMode nsIFrame::WritingModeForLine(WritingMode aSelfWM, nsIFrame* aSubFrame) const { MOZ_ASSERT(aSelfWM == GetWritingMode()); WritingMode writingMode = aSelfWM; if (StyleTextReset()->mUnicodeBidi == StyleUnicodeBidi::Plaintext) { mozilla::intl::BidiEmbeddingLevel frameLevel = nsBidiPresUtils::GetFrameBaseLevel(aSubFrame); writingMode.SetDirectionFromBidiLevel(frameLevel); } return writingMode; } nsRect nsIFrame::GetMarginRect() const { return GetMarginRectRelativeToSelf() + GetPosition(); } nsRect nsIFrame::GetMarginRectRelativeToSelf() const { nsMargin m = GetUsedMargin().ApplySkipSides(GetSkipSides()); nsRect r(0, 0, mRect.width, mRect.height); r.Inflate(m); return r; } bool nsIFrame::IsTransformed() const { if (!HasAnyStateBits(NS_FRAME_MAY_BE_TRANSFORMED)) { MOZ_ASSERT(!IsCSSTransformed()); MOZ_ASSERT(!GetParentSVGTransforms()); return false; } return IsCSSTransformed() || GetParentSVGTransforms(); } bool nsIFrame::IsCSSTransformed() const { return HasAnyStateBits(NS_FRAME_MAY_BE_TRANSFORMED) && (StyleDisplay()->HasTransform(this) || HasAnimationOfTransform()); } bool nsIFrame::HasAnimationOfTransform() const { if (!MayHaveTransformAnimation()) { MOZ_ASSERT(!IsPrimaryFrame() || !SupportsCSSTransforms() || !nsLayoutUtils::HasAnimationOfTransformAndMotionPath(this)); return false; } return IsPrimaryFrame() && SupportsCSSTransforms() && nsLayoutUtils::HasAnimationOfTransformAndMotionPath(this); } bool nsIFrame::ChildrenHavePerspective( const nsStyleDisplay* aStyleDisplay) const { MOZ_ASSERT(aStyleDisplay == StyleDisplay()); return aStyleDisplay->HasPerspective(this); } bool nsIFrame::HasAnimationOfOpacity(EffectSet* aEffectSet) const { return ((nsLayoutUtils::IsPrimaryStyleFrame(this) || nsLayoutUtils::FirstContinuationOrIBSplitSibling(this) ->IsPrimaryFrame()) && nsLayoutUtils::HasAnimationOfPropertySet( this, nsCSSPropertyIDSet::OpacityProperties(), aEffectSet)); } bool nsIFrame::HasOpacityInternal(float aThreshold, const nsStyleDisplay* aStyleDisplay, const nsStyleEffects* aStyleEffects, EffectSet* aEffectSet) const { MOZ_ASSERT(0.0 <= aThreshold && aThreshold <= 1.0, "Invalid argument"); if (aStyleEffects->mOpacity < aThreshold || aStyleDisplay->mWillChange.bits & StyleWillChangeBits::OPACITY) { return true; } if (!mMayHaveOpacityAnimation) { return false; } return HasAnimationOfOpacity(aEffectSet); } bool nsIFrame::DoGetParentSVGTransforms(gfx::Matrix*) const { return false; } bool nsIFrame::Extend3DContext(const nsStyleDisplay* aStyleDisplay, const nsStyleEffects* aStyleEffects, mozilla::EffectSet* aEffectSetForOpacity) const { if (!HasAnyStateBits(NS_FRAME_MAY_BE_TRANSFORMED)) { return false; } const nsStyleDisplay* disp = StyleDisplayWithOptionalParam(aStyleDisplay); if (disp->mTransformStyle != StyleTransformStyle::Preserve3d || !SupportsCSSTransforms()) { return false; } // If we're all scroll frame, then all descendants will be clipped, so we // can't preserve 3d. if (IsScrollContainerFrame()) { return false; } const nsStyleEffects* effects = StyleEffectsWithOptionalParam(aStyleEffects); if (HasOpacity(disp, effects, aEffectSetForOpacity)) { return false; } return ShouldApplyOverflowClipping(disp).isEmpty() && !GetClipPropClipRect(disp, effects, GetSize()) && !SVGIntegrationUtils::UsingEffectsForFrame(this) && !effects->HasMixBlendMode() && !ForcesStackingContextForViewTransition() && disp->mIsolation != StyleIsolation::Isolate; } bool nsIFrame::Combines3DTransformWithAncestors() const { // Check these first as they are faster then both calls below and are we are // likely to hit the early return (backface hidden is uncommon and // GetReferenceFrame is a hot caller of this which only calls this if // IsCSSTransformed is false). if (!IsCSSTransformed() && !BackfaceIsHidden()) { return false; } nsIFrame* parent = GetClosestFlattenedTreeAncestorPrimaryFrame(); return parent && parent->Extend3DContext(); } bool nsIFrame::In3DContextAndBackfaceIsHidden() const { // While both tests fail most of the time, test BackfaceIsHidden() // first since it's likely to fail faster. return BackfaceIsHidden() && Combines3DTransformWithAncestors(); } bool nsIFrame::HasPerspective() const { if (!IsCSSTransformed()) { return false; } nsIFrame* parent = GetClosestFlattenedTreeAncestorPrimaryFrame(); if (!parent) { return false; } return parent->ChildrenHavePerspective(); } nsRect nsIFrame::GetContentRectRelativeToSelf() const { nsMargin bp = GetUsedBorderAndPadding().ApplySkipSides(GetSkipSides()); nsRect r(0, 0, mRect.width, mRect.height); r.Deflate(bp); return r; } nsRect nsIFrame::GetContentRect() const { return GetContentRectRelativeToSelf() + GetPosition(); } bool nsIFrame::ComputeBorderRadii(const BorderRadius& aBorderRadius, const CornerShapeRect& aCornerShape, const nsSize& aFrameSize, const nsSize& aBorderArea, Sides aSkipSides, nsRectCornerRadii& aRadii) { // Percentages are relative to whichever side they're on. for (const auto i : mozilla::AllPhysicalHalfCorners()) { const LengthPercentage& c = aBorderRadius.Get(i); nscoord axis = HalfCornerIsX(i) ? aFrameSize.width : aFrameSize.height; aRadii[i] = std::max(0, c.Resolve(axis)); } aRadii.mShapeK[mozilla::eCornerTopLeft] = aCornerShape.top_left.k; aRadii.mShapeK[mozilla::eCornerTopRight] = aCornerShape.top_right.k; aRadii.mShapeK[mozilla::eCornerBottomLeft] = aCornerShape.bottom_left.k; aRadii.mShapeK[mozilla::eCornerBottomRight] = aCornerShape.bottom_right.k; bool isTopLeftSquare = std::isinf(aCornerShape.top_left.k) && (aCornerShape.top_left.k > 0.0f); bool isTopRightSquare = std::isinf(aCornerShape.top_right.k) && (aCornerShape.top_right.k > 0.0f); bool isBottomLeftSquare = std::isinf(aCornerShape.bottom_left.k) && (aCornerShape.bottom_left.k > 0.0f); bool isBottomRightSquare = std::isinf(aCornerShape.bottom_right.k) && (aCornerShape.bottom_right.k > 0.0f); if (aSkipSides.Intersects(SideBits::eTop | SideBits::eLeft) || isTopLeftSquare) { aRadii.TopLeft() = {}; } if (aSkipSides.Intersects(SideBits::eTop | SideBits::eRight) || isTopRightSquare) { aRadii.TopRight() = {}; } if (aSkipSides.Intersects(SideBits::eBottom | SideBits::eLeft) || isBottomLeftSquare) { aRadii.BottomLeft() = {}; } if (aSkipSides.Intersects(SideBits::eBottom | SideBits::eRight) || isBottomRightSquare) { aRadii.BottomRight() = {}; } // css3-background specifies this algorithm for reducing // corner radii when they are too big. bool haveRadius = false; double ratio = 1.0f; for (const auto side : mozilla::AllPhysicalSides()) { auto hc1 = SideToHalfCorner(side, false, true); auto hc2 = SideToHalfCorner(side, true, true); nscoord length = SideIsVertical(side) ? aBorderArea.height : aBorderArea.width; nscoord sum = aRadii[hc1] + aRadii[hc2]; if (sum) { haveRadius = true; // avoid floating point division in the normal case if (length < sum) { ratio = std::min(ratio, double(length) / sum); } } } if (ratio < 1.0) { for (const auto corner : mozilla::AllPhysicalHalfCorners()) { aRadii[corner] *= ratio; } } return haveRadius; } static inline bool RadiiAreDefinitelyZero(const BorderRadius& aBorderRadius) { for (const auto corner : mozilla::AllPhysicalHalfCorners()) { if (!aBorderRadius.Get(corner).IsDefinitelyZero()) { return false; } } return true; } /* virtual */ bool nsIFrame::GetBorderRadii(const nsSize& aFrameSize, const nsSize& aBorderArea, Sides aSkipSides, nsRectCornerRadii& aRadii) const { if (!mMayHaveRoundedCorners) { return false; } if (IsThemed()) { // When we're themed, the native theme code draws the border and // background, and therefore it doesn't make sense to tell other // code that's interested in border-radius that we have any radii. // // In an ideal world, we might have a way for the them to tell us an // border radius, but since we don't, we're better off assuming // zero. return false; } const auto& radii = StyleBorder()->mBorderRadius; const auto& cornerShape = StyleBorder()->mCornerShape; const bool hasRadii = ComputeBorderRadii(radii, cornerShape, aFrameSize, aBorderArea, aSkipSides, aRadii); if (!hasRadii) { // TODO(emilio): Maybe we can just remove this bit and do the // IsDefinitelyZero check unconditionally. That should still avoid most of // the work, though maybe not the cache miss of going through the style and // the border struct. const_cast(this)->mMayHaveRoundedCorners = !RadiiAreDefinitelyZero(radii); } return hasRadii; } bool nsIFrame::GetBorderRadii(nsRectCornerRadii& aRadii) const { nsSize sz = GetSize(); return GetBorderRadii(sz, sz, GetSkipSides(), aRadii); } bool nsIFrame::GetMarginBoxBorderRadii(nsRectCornerRadii& aRadii) const { if (!GetBorderRadii(aRadii)) { return false; } aRadii.AdjustOutwards(GetUsedMargin()); return true; } bool nsIFrame::GetPaddingBoxBorderRadii(nsRectCornerRadii& aRadii) const { if (!GetBorderRadii(aRadii)) { return false; } aRadii.AdjustInwards(GetUsedBorder()); return true; } bool nsIFrame::GetContentBoxBorderRadii(nsRectCornerRadii& aRadii) const { if (!GetBorderRadii(aRadii)) { return false; } aRadii.AdjustInwards(GetUsedBorderAndPadding()); return true; } bool nsIFrame::GetShapeBoxBorderRadii(nsRectCornerRadii& aRadii) const { using Tag = StyleShapeOutside::Tag; auto& shapeOutside = StyleDisplay()->mShapeOutside; auto box = StyleShapeBox::MarginBox; switch (shapeOutside.tag) { case Tag::Image: case Tag::None: return false; case Tag::Box: box = shapeOutside.AsBox(); break; case Tag::Shape: box = shapeOutside.AsShape()._1; break; } switch (box) { case StyleShapeBox::ContentBox: return GetContentBoxBorderRadii(aRadii); case StyleShapeBox::PaddingBox: return GetPaddingBoxBorderRadii(aRadii); case StyleShapeBox::BorderBox: return GetBorderRadii(aRadii); case StyleShapeBox::MarginBox: return GetMarginBoxBorderRadii(aRadii); default: MOZ_ASSERT_UNREACHABLE("Unexpected box value"); return false; } } nscoord nsIFrame::OneEmInAppUnits() const { return StyleFont() ->mFont.size.ScaledBy(nsLayoutUtils::FontSizeInflationFor(this)) .ToAppUnits(); } RubyMetrics nsIFrame::RubyMetrics(float aRubyMetricsFactor) const { RefPtr fm = nsLayoutUtils::GetInflatedFontMetricsForFrame(this); return mozilla::RubyMetrics{ nscoord(NS_round(fm->TrimmedAscent() * aRubyMetricsFactor)), nscoord(NS_round(fm->TrimmedDescent() * aRubyMetricsFactor))}; } nscoord nsIFrame::SynthesizeFallbackBaseline( WritingMode aWM, BaselineSharingGroup aBaselineGroup) const { NS_ASSERTION(!IsSubtreeDirty(), "frame must not be dirty"); return Baseline::SynthesizeBOffsetFromMarginBox(this, aWM, aBaselineGroup); } nscoord nsIFrame::GetLogicalBaseline(WritingMode aWM) const { return GetLogicalBaseline(aWM, GetDefaultBaselineSharingGroup(), BaselineExportContext::LineLayout); } nscoord nsIFrame::GetLogicalBaseline( WritingMode aWM, BaselineSharingGroup aBaselineGroup, BaselineExportContext aExportContext) const { const auto result = GetNaturalBaselineBOffset(aWM, aBaselineGroup, aExportContext) .valueOrFrom([this, aWM, aBaselineGroup]() { return SynthesizeFallbackBaseline(aWM, aBaselineGroup); }); if (aBaselineGroup == BaselineSharingGroup::Last) { return BSize(aWM) - result; } return result; } const nsFrameList& nsIFrame::GetChildList(ChildListID aListID) const { if (IsAbsoluteContainer()) { if (aListID == FrameChildListID::Absolute) { return GetAbsoluteContainingBlock()->GetChildList(); } if (aListID == FrameChildListID::PushedAbsolute) { return GetAbsoluteContainingBlock()->GetPushedChildList(); } } return nsFrameList::EmptyList(); } void nsIFrame::GetChildLists(nsTArray* aLists) const { if (const auto* absCB = GetAbsoluteContainingBlock()) { const nsFrameList& absoluteList = absCB->GetChildList(); absoluteList.AppendIfNonempty(aLists, FrameChildListID::Absolute); const nsFrameList& pushedAbsoluteList = absCB->GetPushedChildList(); pushedAbsoluteList.AppendIfNonempty(aLists, FrameChildListID::PushedAbsolute); } } AutoTArray nsIFrame::CrossDocChildLists() { AutoTArray childLists; nsSubDocumentFrame* subdocumentFrame = do_QueryFrame(this); if (subdocumentFrame) { // Descend into the subdocument nsIFrame* root = subdocumentFrame->GetSubdocumentRootFrame(); if (root) { childLists.EmplaceBack( nsFrameList(root, nsLayoutUtils::GetLastSibling(root)), FrameChildListID::Principal); } } GetChildLists(&childLists); return childLists; } nsIFrame::CaretBlockAxisMetrics nsIFrame::GetCaretBlockAxisMetrics( mozilla::WritingMode aWM, const nsFontMetrics& aFM) const { // Note(dshin): Ultimately, this does something highly similar (But still // different) to `nsLayoutUtils::GetFirstLinePosition`. const auto baseline = GetCaretBaseline(); nscoord ascent = 0, descent = 0; ascent = aFM.MaxAscent(); descent = aFM.MaxDescent(); const nscoord height = ascent + descent; if (aWM.IsVertical() && aWM.IsLineInverted()) { return CaretBlockAxisMetrics{.mOffset = baseline - descent, .mExtent = height}; } return CaretBlockAxisMetrics{.mOffset = baseline - ascent, .mExtent = height}; } nscoord nsIFrame::GetFontMetricsDerivedCaretBaseline() const { float inflation = nsLayoutUtils::FontSizeInflationFor(this); RefPtr fm = nsLayoutUtils::GetFontMetricsForFrame(this, inflation); const WritingMode wm = GetWritingMode(); const nscoord lineHeight = ReflowInput::CalcLineHeight( *Style(), PresContext(), GetContent(), inflation); return nsLayoutUtils::GetCenteredFontBaseline(fm, lineHeight, wm.IsLineInverted()) + GetLogicalUsedBorderAndPadding(wm).BStart(wm); } const nsAtom* nsIFrame::ComputePageValue(const nsAtom* aAutoValue) const { const nsAtom* value = aAutoValue ? aAutoValue : nsGkAtoms::_empty; const nsIFrame* frame = this; // Find what CSS page name value this frame's subtree has, if any. // Starting with this frame, check if a page name other than auto is present, // and record it if so. Then, if the current frame is a container frame, find // the first non-placeholder child and repeat. // This will find the most deeply nested first in-flow child of this frame's // subtree, and return its page name (with auto resolved if applicable, and // subtrees with no page-names returning the empty atom rather than null). do { if (const nsAtom* maybePageName = frame->GetStylePageName()) { value = maybePageName; } // Get the next frame to read from. const nsIFrame* firstNonPlaceholderFrame = nullptr; // If this is a container frame, inspect its in-flow children. if (const nsContainerFrame* containerFrame = do_QueryFrame(frame)) { for (const nsIFrame* childFrame : containerFrame->PrincipalChildList()) { if (!childFrame->IsPlaceholderFrame()) { firstNonPlaceholderFrame = childFrame; break; } } } frame = firstNonPlaceholderFrame; } while (frame); return value; } Visibility nsIFrame::GetVisibility() const { if (!HasAnyStateBits(NS_FRAME_VISIBILITY_IS_TRACKED)) { return Visibility::Untracked; } bool isSet = false; uint32_t visibleCount = GetProperty(VisibilityStateProperty(), &isSet); MOZ_ASSERT(isSet, "Should have a VisibilityStateProperty value " "if NS_FRAME_VISIBILITY_IS_TRACKED is set"); return visibleCount > 0 ? Visibility::ApproximatelyVisible : Visibility::ApproximatelyNonVisible; } void nsIFrame::UpdateVisibilitySynchronously() { mozilla::PresShell* presShell = PresShell(); if (!presShell) { return; } if (presShell->AssumeAllFramesVisible()) { presShell->EnsureFrameInApproximatelyVisibleList(this); return; } bool visible = StyleVisibility()->IsVisible(); nsIFrame* f = GetParent(); nsRect rect = GetRectRelativeToSelf(); nsIFrame* rectFrame = this; while (f && visible) { if (ScrollContainerFrame* sf = do_QueryFrame(f)) { nsRect transformedRect = nsLayoutUtils::TransformFrameRectToAncestor(rectFrame, rect, f); if (!sf->IsRectNearlyVisible(transformedRect)) { visible = false; break; } // In this code we're trying to synchronously update *approximate* // visibility. (In the future we may update precise visibility here as // well, which is why the method name does not contain 'approximate'.) The // IsRectNearlyVisible() check above tells us that the rect we're checking // is approximately visible within the scrollframe, but we still need to // ensure that, even if it was scrolled into view, it'd be visible when we // consider the rest of the document. To do that, we move transformedRect // to be contained in the scrollport as best we can (it might not fit) to // pretend that it was scrolled into view. rect = transformedRect.MoveInsideAndClamp(sf->GetScrollPortRect()); rectFrame = f; } nsIFrame* parent = f->GetParent(); if (!parent) { parent = nsLayoutUtils::GetCrossDocParentFrameInProcess(f); if (parent && parent->PresContext()->IsChrome()) { break; } } f = parent; } if (visible) { presShell->EnsureFrameInApproximatelyVisibleList(this); } else { presShell->RemoveFrameFromApproximatelyVisibleList(this); } } void nsIFrame::EnableVisibilityTracking() { if (HasAnyStateBits(NS_FRAME_VISIBILITY_IS_TRACKED)) { return; // Nothing to do. } MOZ_ASSERT(!HasProperty(VisibilityStateProperty()), "Shouldn't have a VisibilityStateProperty value " "if NS_FRAME_VISIBILITY_IS_TRACKED is not set"); // Add the state bit so we know to track visibility for this frame, and // initialize the frame property. AddStateBits(NS_FRAME_VISIBILITY_IS_TRACKED); SetProperty(VisibilityStateProperty(), 0); mozilla::PresShell* presShell = PresShell(); if (!presShell) { return; } // Schedule a visibility update. This method will virtually always be called // when layout has changed anyway, so it's very unlikely that any additional // visibility updates will be triggered by this, but this way we guarantee // that if this frame is currently visible we'll eventually find out. presShell->ScheduleApproximateFrameVisibilityUpdateSoon(); } void nsIFrame::DisableVisibilityTracking() { if (!HasAnyStateBits(NS_FRAME_VISIBILITY_IS_TRACKED)) { return; // Nothing to do. } bool isSet = false; uint32_t visibleCount = TakeProperty(VisibilityStateProperty(), &isSet); MOZ_ASSERT(isSet, "Should have a VisibilityStateProperty value " "if NS_FRAME_VISIBILITY_IS_TRACKED is set"); RemoveStateBits(NS_FRAME_VISIBILITY_IS_TRACKED); if (visibleCount == 0) { return; // We were nonvisible. } // We were visible, so send an OnVisibilityChange() notification. OnVisibilityChange(Visibility::ApproximatelyNonVisible); } void nsIFrame::DecApproximateVisibleCount( const Maybe& aNonvisibleAction /* = Nothing() */) { MOZ_ASSERT(HasAnyStateBits(NS_FRAME_VISIBILITY_IS_TRACKED)); bool isSet = false; uint32_t visibleCount = GetProperty(VisibilityStateProperty(), &isSet); MOZ_ASSERT(isSet, "Should have a VisibilityStateProperty value " "if NS_FRAME_VISIBILITY_IS_TRACKED is set"); MOZ_ASSERT(visibleCount > 0, "Frame is already nonvisible and we're " "decrementing its visible count?"); visibleCount--; SetProperty(VisibilityStateProperty(), visibleCount); if (visibleCount > 0) { return; } // We just became nonvisible, so send an OnVisibilityChange() notification. OnVisibilityChange(Visibility::ApproximatelyNonVisible, aNonvisibleAction); } void nsIFrame::IncApproximateVisibleCount() { MOZ_ASSERT(HasAnyStateBits(NS_FRAME_VISIBILITY_IS_TRACKED)); bool isSet = false; uint32_t visibleCount = GetProperty(VisibilityStateProperty(), &isSet); MOZ_ASSERT(isSet, "Should have a VisibilityStateProperty value " "if NS_FRAME_VISIBILITY_IS_TRACKED is set"); visibleCount++; SetProperty(VisibilityStateProperty(), visibleCount); if (visibleCount > 1) { return; } // We just became visible, so send an OnVisibilityChange() notification. OnVisibilityChange(Visibility::ApproximatelyVisible); } void nsIFrame::OnVisibilityChange(Visibility aNewVisibility, const Maybe& aNonvisibleAction /* = Nothing() */) { // XXX(seth): In bug 1218990 we'll implement visibility tracking for CSS // images here. } static nsIFrame* GetActiveSelectionFrame(nsPresContext* aPresContext, nsIFrame* aFrame) { nsIContent* capturingContent = PresShell::GetCapturingContent(); if (capturingContent) { nsIFrame* activeFrame = aPresContext->GetPrimaryFrameFor(capturingContent); return activeFrame ? activeFrame : aFrame; } return aFrame; } bool nsIFrame::ShouldHandleSelectionMovementEvents(ForSelectionStart aType) { if (GetDisplaySelection() == nsISelectionController::SELECTION_OFF) { return false; } if (UsedUserSelect(this, aType) == StyleUserSelect::None) { // Check whether style allows selection. return false; } if (IsScrollbarFrame() || IsHTMLCanvasFrame()) { // Scrollbars and canvas don't move selection with the mouse. return false; } return true; } static Element* FindElementAncestorForMozSelection(nsIContent* aContent) { NS_ENSURE_TRUE(aContent, nullptr); while (aContent && aContent->IsInNativeAnonymousSubtree()) { aContent = aContent->GetClosestNativeAnonymousSubtreeRootParentOrHost(); } NS_ASSERTION(aContent, "aContent isn't in non-anonymous tree?"); return aContent ? aContent->GetAsElementOrParentElement() : nullptr; } already_AddRefed nsIFrame::ComputeSelectionStyle( int16_t aSelectionStatus) const { // Just bail out if not a selection-status that ::selection applies to. if (aSelectionStatus != nsISelectionController::SELECTION_ON && aSelectionStatus != nsISelectionController::SELECTION_DISABLED) { return nullptr; } Element* element = FindElementAncestorForMozSelection(GetContent()); if (!element) { return nullptr; } nsIFrame* primaryFrame = element->GetPrimaryFrame(); ComputedStyle* parentStyle = primaryFrame ? primaryFrame->Style() : Style(); RefPtr pseudoStyle = PresContext()->StyleSet()->ProbePseudoElementStyle( *element, PseudoStyleType::Selection, nullptr, parentStyle); if (!pseudoStyle) { return nullptr; } // When in high-contrast mode, the style system ends up ignoring the color // declarations, which means that the ::selection style becomes the inherited // color, and default background. That's no good. // When force-color-adjust is set to none allow using the color styles, // as they will not be replaced. if (PresContext()->ForcingColors() && pseudoStyle->StyleText()->mForcedColorAdjust != StyleForcedColorAdjust::None) { return nullptr; } return do_AddRef(pseudoStyle); } already_AddRefed nsIFrame::ComputeHighlightSelectionStyle( nsAtom* aHighlightName) { Element* element = FindElementAncestorForMozSelection(GetContent()); if (!element) { return nullptr; } nsIFrame* primaryFrame = element->GetPrimaryFrame(); ComputedStyle* parentStyle = primaryFrame ? primaryFrame->Style() : Style(); return PresContext()->StyleSet()->ProbePseudoElementStyle( *element, PseudoStyleType::Highlight, aHighlightName, parentStyle); } already_AddRefed nsIFrame::ComputeTargetTextStyle() const { const Element* element = FindElementAncestorForMozSelection(GetContent()); if (!element) { return nullptr; } nsIFrame* primaryFrame = element->GetPrimaryFrame(); ComputedStyle* parentStyle = primaryFrame ? primaryFrame->Style() : Style(); RefPtr pseudoStyle = PresContext()->StyleSet()->ProbePseudoElementStyle( *element, PseudoStyleType::TargetText, nullptr, parentStyle); if (!pseudoStyle) { return nullptr; } if (PresContext()->ForcingColors() && pseudoStyle->StyleText()->mForcedColorAdjust != StyleForcedColorAdjust::None) { return nullptr; } return pseudoStyle.forget(); } nsTextControlFrame* nsIFrame::GetContainingTextControlFrame() const { for (const nsIFrame* cur = this; cur; cur = cur->GetParent()) { if (const nsTextControlFrame* tc = do_QueryFrame(cur)) { return const_cast(tc); } if (cur->Style()->IsAnonBox()) { // Anon boxes could belong to the /