/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
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 (constauto& childList : ChildLists()) { for (nsIFrame* child : childList.mList) { if (child->CheckAndClearPaintedState()) {
result = true;
}
}
} return result;
}
if (PresShell()->IsUnderHiddenEmbedderElement()) { returnfalse;
}
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()) { returnfalse;
} if (frame->StyleUIReset()->mMozSubtreeHiddenOnlyVisually) { returnfalse;
}
}
// 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)) { returnfalse;
}
if (nsIFrame* parent = frame->GetParent()) {
frame = parent;
} else {
parent = nsLayoutUtils::GetCrossDocParentFrameInProcess(frame); if (!parent) { break;
}
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::operatordelete(void*, size_t) {
MOZ_CRASH("nsIFrame::operator delete should never be called");
}
// The root frame should always be an inflation container. if (!aFrame->GetParent()) { returntrue;
}
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;
}
staticvoid 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<SVGTextFrame*>(
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;
}
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();
}
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 <body>.
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();
} elseif (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);
} elseif (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);
}
}
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 (constauto& name : oldDisp->mAnchorName.AsSpan()) {
PresShell()->RemoveAnchorPosAnchor(name.AsAtom(), this);
}
} for (constauto& 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())) { constauto* pos = StylePosition(); constauto* 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());
}
}
constauto cv = disp->ContentVisibility(*this); if (!oldDisp || oldDisp->ContentVisibility(*this) != cv) { if (cv == StyleContentVisibility::Auto) {
PresShell()->RegisterContentVisibilityAutoFrame(this);
} else { if (auto* element = Element::FromNodeOrNull(GetContent())) {
element->ClearContentRelevancy();
}
PresShell()->UnregisterContentVisibilityAutoFrame(this);
}
PresContext()->SetNeedsToUpdateHiddenByContentVisibilityForAnimations();
}
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?");
constauto* 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);
} if (disp->ContentVisibility(*this) == 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 (constauto& 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();
// 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);
}
}
}
}
staticvoid 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);
});
}
bool nsIFrame::HasDisplayItem(uint32_t aKey) { for (nsDisplayItem* i : mDisplayItems) { if (i->GetPerFrameKey() == aKey) { returntrue;
}
} returnfalse;
}
template <typename Condition> staticvoid 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();
}
}
}
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 (constauto& data : userDataTable->Values()) {
data->RemoveFromTable();
} delete userDataTable;
}
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<nsPlaceholderFrame*>(this)->GetOutOfFlowFrame(); if (oof) {
oof->MarkNeedsDisplayItemRebuild();
} // Do not mark placeholder frames modified. return;
}
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
// 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) { #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 (aOldComputedStyle) { if (nsAccessibilityService* accService = GetAccService()) {
accService->NotifyOfComputedStyleChange(PresShell(), mContent);
}
} #endif
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 :( constbool isNonText = !IsTextFrame(); if (isNonText) {
mComputedStyle->StartImageLoads(*doc, aOldComputedStyle);
}
constbool isRootElementStyle = Style()->IsRootElementStyle(); if (isRootElementStyle) {
PresShell()->SetNeedsWindowPropertiesSync();
}
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();
};
// SVGObserverUtils::GetEffectProperties() asserts that we only invoke it with // the first continuation so we need to check that in advance. constbool 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<gfx::PathBuilder> builder = MotionPathUtils::GetPathBuilder();
RefPtr<gfx::Path> 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());
}
} elseif (oldPath) {
RemoveProperty(nsIFrame::OffsetPathCache());
}
}
if (IsPrimaryFrame()) {
MOZ_ASSERT(aOldComputedStyle);
HandlePrimaryFrameStyleChange(aOldComputedStyle);
}
#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
if (nsMargin* m = GetProperty(UsedMarginProperty())) {
margin = *m;
} elseif (!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;
}
const nsStyleDisplay* disp = StyleDisplay(); if (IsThemed(disp)) { // Theme methods don't use const-ness.
nsIFrame* mutable_this = const_cast<nsIFrame*>(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;
} elseif (!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;
}
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()) { returnfalse;
}
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();
}
// css3-background specifies this algorithm for reducing // corner radii when they are too big. bool haveRadius = false; double ratio = 1.0f; for (constauto 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 (constauto corner : mozilla::AllPhysicalHalfCorners()) {
aRadii[corner] *= ratio;
}
}
return haveRadius;
}
staticinlinebool RadiiAreDefinitelyZero(const BorderRadius& aBorderRadius) { for (constauto corner : mozilla::AllPhysicalHalfCorners()) { if (!aBorderRadius.Get(corner).IsDefinitelyZero()) { returnfalse;
}
} returntrue;
}
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. returnfalse;
}
constauto& radii = StyleBorder()->mBorderRadius; constauto& cornerShape = StyleBorder()->mCornerShape; constbool 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<nsIFrame*>(this)->mMayHaveRoundedCorners =
!RadiiAreDefinitelyZero(radii);
} return hasRadii;
}
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;
}
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.
}
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);
}
already_AddRefed<ComputedStyle> 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<ComputedStyle> 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);
}
nsTextControlFrame* nsIFrame::GetContainingTextControlFrame() const { for (const nsIFrame* cur = this; cur; cur = cur->GetParent()) { if (const nsTextControlFrame* tc = do_QueryFrame(cur)) { returnconst_cast<nsTextControlFrame*>(tc);
} if (cur->Style()->IsAnonBox()) { // Anon boxes could belong to the <input> / <textarea> itself. continue;
} auto* content = cur->GetContent(); if (!content || !content->IsInNativeAnonymousSubtree()) { // All content inside text controls is anonymous. break;
}
} return nullptr;
}
bool nsIFrame::CanBeDynamicReflowRoot() const { constauto& display = *StyleDisplay(); if (IsLineParticipant() || display.mDisplay.IsRuby() ||
display.IsInnerTableStyle() ||
display.DisplayInside() == StyleDisplayInside::Table) { // We have a display type where 'width' and 'height' don't actually set the // width or height (i.e., the size depends on content).
MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_DYNAMIC_REFLOW_ROOT), "should not have dynamic reflow root bit"); returnfalse;
}
// In general, frames that have contain:layout+size can be reflow roots. // (One exception: table-wrapper frames don't work well as reflow roots, // because their inner-table ReflowInput init path tries to reuse & deref // the wrapper's containing block's reflow input, which may be null if we // initiate reflow from the table-wrapper itself.) // // Changes to `contain` force frame reconstructions, so we used to use // NS_FRAME_REFLOW_ROOT, this bit could be set for the whole lifetime of // this frame. But after the support of `content-visibility: auto` which // is with contain layout + size when it's not relevant to user, and only // with contain layout when it is relevant. The frame does not reconstruct // when the relevancy changes. So we use NS_FRAME_DYNAMIC_REFLOW_ROOT instead. // // We place it above the pref check on purpose, to make sure it works for // containment even with the pref disabled. if (display.IsContainLayout() && GetContainSizeAxes().IsBoth()) { returntrue;
}
if (!StaticPrefs::layout_dynamic_reflow_roots_enabled()) { returnfalse;
}
// We can't serve as a dynamic reflow root if our used 'width' and 'height' // might be influenced by content. // // FIXME: For display:block, we should probably optimize inline-size: auto. // FIXME: Other flex and grid cases? constauto& pos = *StylePosition(); constauto anchorResolutionParams = AnchorPosResolutionParams::From(this); constauto width = pos.GetWidth(anchorResolutionParams); constauto height = pos.GetHeight(anchorResolutionParams); if (!width->IsLengthPercentage() || width->HasPercent() ||
!height->IsLengthPercentage() || height->HasPercent() ||
IsIntrinsicKeyword(*pos.GetMinWidth(anchorResolutionParams)) ||
IsIntrinsicKeyword(*pos.GetMaxWidth(anchorResolutionParams)) ||
IsIntrinsicKeyword(*pos.GetMinHeight(anchorResolutionParams)) ||
IsIntrinsicKeyword(*pos.GetMaxHeight(anchorResolutionParams)) ||
((pos.GetMinWidth(anchorResolutionParams)->IsAuto() ||
pos.GetMinHeight(anchorResolutionParams)->IsAuto()) &&
IsFlexOrGridItem())) { returnfalse;
}
// If our flex-basis is 'auto', it'll defer to 'width' (or 'height') which // we've already checked. Otherwise, it preempts them, so we need to // perform the same "could-this-value-be-influenced-by-content" checks that // we performed for 'width' and 'height' above. if (IsFlexItem()) { constauto& flexBasis = pos.mFlexBasis; if (!flexBasis.IsAuto()) { if (!flexBasis.IsSize() || !flexBasis.AsSize().IsLengthPercentage() ||
flexBasis.AsSize().HasPercent()) { returnfalse;
}
}
}
if (!IsFixedPosContainingBlock()) { // We can't treat this frame as a reflow root, since dynamic changes // to absolutely-positioned frames inside of it require that we // reflow the placeholder before we reflow the absolutely positioned // frame. // FIXME: Alternatively, we could sort the reflow roots in // PresShell::ProcessReflowCommands by depth in the tree, from // deepest to least deep. However, for performance (FIXME) we // should really be sorting them in the opposite order! returnfalse;
}
// If we participate in a container's block reflow context, or margins // can collapse through us, we can't be a dynamic reflow root. // (NS_BLOCK_BFC is block specific bit, check first as an optimization, it's // okay because we also check that it is a block frame.) if (!HasAnyStateBits(NS_BLOCK_BFC) && IsBlockFrameOrSubclass()) { returnfalse;
}
// Subgrids are never reflow roots, but 'contain:layout/paint' prevents // creating a subgrid in the first place. if (pos.mGridTemplateColumns.IsSubgrid() ||
pos.mGridTemplateRows.IsSubgrid()) { // NOTE: we could check that 'display' of our parent's primary frame is // '[inline-]grid' here but that's probably not worth it in practice. if (!display.IsContainLayout() && !display.IsContainPaint()) { returnfalse;
}
}
// If we are split, we can't be a dynamic reflow root. Our reflow status may // change after reflow, and our parent is responsible to create or delete our // next-in-flow. if (GetPrevContinuation() || GetNextContinuation()) { returnfalse;
}
void nsIFrame::DisplayOutlineUnconditional(nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists) { // Per https://drafts.csswg.org/css-tables-3/#global-style-overrides: // "All css properties of table-column and table-column-group boxes are // ignored, except when explicitly specified by this specification." // CSS outlines fall into this category, so we skip them on these boxes.
MOZ_ASSERT(!IsTableColGroupFrame() && !IsTableColFrame()); constauto& outline = *StyleOutline();
if (!outline.ShouldPaintOutline()) { return;
}
// Outlines are painted by the table wrapper frame. if (IsTableFrame()) { return;
}
if (HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT) &&
ScrollableOverflowRect().IsEmpty()) { // Skip parts of IB-splits with an empty overflow rect, see bug 434301. // We may still want to fix some of the overflow area calculations over in // that bug. return;
}
// We don't display outline-style: auto on themed frames that have their own // focus indicators. if (outline.mOutlineStyle.IsAuto()) { auto* disp = StyleDisplay(); if (IsThemed(disp) && PresContext()->Theme()->ThemeDrawsFocusForWidget( this, disp->EffectiveAppearance())) { return;
}
}
void nsIFrame::DisplayInsetBoxShadowUnconditional(
nsDisplayListBuilder* aBuilder, nsDisplayList* aList) { // XXXbz should box-shadow for rows/rowgroups/columns/colgroups get painted // just because we're visible? Or should it depend on the cell visibility // when we're not the whole table? constauto* effects = StyleEffects(); if (effects->HasBoxShadowWithInset(true)) {
aList->AppendNewToTop<nsDisplayBoxShadowInner>(aBuilder, this);
}
}
void nsIFrame::DisplayOutsetBoxShadowUnconditional(
nsDisplayListBuilder* aBuilder, nsDisplayList* aList) { // XXXbz should box-shadow for rows/rowgroups/columns/colgroups get painted // just because we're visible? Or should it depend on the cell visibility // when we're not the whole table? constauto* effects = StyleEffects(); if (effects->HasBoxShadowWithInset(false)) {
aList->AppendNewToTop<nsDisplayBoxShadowOuter>(aBuilder, this);
}
}
auto nsIFrame::ComputeShouldPaintBackground() const -> ShouldPaintBackground {
nsPresContext* pc = PresContext();
ShouldPaintBackground settings{pc->GetBackgroundColorDraw(),
pc->GetBackgroundImageDraw()}; if (settings.mColor && settings.mImage) { return settings;
}
if (StyleVisibility()->mPrintColorAdjust == StylePrintColorAdjust::Exact) { return {true, true};
}
return settings;
}
bool nsIFrame::DisplayBackgroundUnconditional(nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists) { if (aBuilder->IsForEventDelivery() && !aBuilder->HitTestIsForVisibility()) { // For hit-testing, we generally just need a light-weight data structure // like nsDisplayEventReceiver. But if the hit-testing is for visibility, // then we need to know the opaque region in order to determine whether to // stop or not.
aLists.BorderBackground()->AppendNewToTop<nsDisplayEventReceiver>(aBuilder, this); returnfalse;
}
if (result == AppendedBackgroundType::None) {
aBuilder->BuildCompositorHitTestInfoIfNeeded(this,
aLists.BorderBackground());
}
return result == AppendedBackgroundType::ThemedBackground;
}
void nsIFrame::DisplayBorderBackgroundOutline(nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists) { // The visibility check belongs here since child elements have the // opportunity to override the visibility property and display even if // their parent is hidden. if (!IsVisibleForPainting()) { return;
}
// If there's a themed background, we should not create a border item. // It won't be rendered. // Don't paint borders for tables here, since they paint them in a different // order. if (!bgIsThemed && StyleBorder()->HasBorder() && !IsTableFrame()) {
aLists.BorderBackground()->AppendNewToTop<nsDisplayBorder>(aBuilder, this);
}
DisplayOutlineUnconditional(aBuilder, aLists);
}
inlinestaticbool IsSVGContentWithCSSClip(const nsIFrame* aFrame) { // The CSS spec says that the 'clip' property only applies to absolutely // positioned elements, whereas the SVG spec says that it applies to SVG // elements regardless of the value of the 'position' property. Here we obey // the CSS spec for outer-<svg> (since that's what we generally do), but // obey the SVG spec for other SVG elements to which 'clip' applies. return aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT) &&
aFrame->GetContent()->IsAnyOfSVGElements(nsGkAtoms::svg,
nsGkAtoms::foreignObject);
}
auto& clipRect = aEffects->mClip.AsRect();
nsRect rect = clipRect.ToLayoutRect(); if (MOZ_LIKELY(StyleBorder()->mBoxDecorationBreak ==
StyleBoxDecorationBreak::Slice)) { // The clip applies to the joined boxes so it's relative the first // continuation.
nscoord y = 0; for (nsIFrame* f = GetPrevContinuation(); f; f = f->GetPrevContinuation()) {
y += f->GetRect().height;
}
rect.MoveBy(nsPoint(0, -y));
}
static Sides ToSkipSides(PhysicalAxes aClipAxes) {
SideBits result{}; if (!aClipAxes.contains(PhysicalAxis::Vertical)) {
result |= SideBits::eTop;
result |= SideBits::eBottom;
} if (!aClipAxes.contains(PhysicalAxis::Horizontal)) {
result |= SideBits::eLeft;
result |= SideBits::eRight;
} return Sides(result);
}
bool nsIFrame::ComputeOverflowClipRectRelativeToSelf( const PhysicalAxes aClipAxes, nsRect& aOutRect,
nsRectCornerRadii& aOutRadii) const { // Only 'clip' is handled here (and 'hidden' for table frames, and any // non-'visible' value for blocks in a paginated context). // We allow 'clip' to apply to any kind of frame. This is required by // comboboxes which make their display text (an inline frame) have clipping.
MOZ_ASSERT(!aClipAxes.isEmpty());
MOZ_ASSERT(ShouldApplyOverflowClipping(StyleDisplay()) == aClipAxes); auto boxMargin = OverflowClipMargin(aClipAxes, /* aAllowNegative = */ true);
boxMargin.ApplySkipSides(GetSkipSides() | ToSkipSides(aClipAxes));
aOutRect = nsRect(nsPoint(), GetSize());
aOutRect.Inflate(boxMargin); if (MOZ_UNLIKELY(!aClipAxes.contains(PhysicalAxis::Horizontal))) { // NOTE(mats) We shouldn't be clipping at all in this dimension really, // but clipping in just one axis isn't supported by our GFX APIs so we // clip to our visual overflow rect instead.
nsRect o = InkOverflowRectRelativeToSelf();
aOutRect.x = o.x;
aOutRect.width = o.width;
} if (MOZ_UNLIKELY(!aClipAxes.contains(PhysicalAxis::Vertical))) { // See the note above.
nsRect o = InkOverflowRectRelativeToSelf();
aOutRect.y = o.y;
aOutRect.height = o.height;
} if (!GetBorderRadii(aOutRadii)) { returnfalse;
}
aOutRadii.AdjustOutwards(boxMargin); returntrue;
}
nsMargin nsIFrame::OverflowClipMargin(PhysicalAxes aClipAxes, bool aAllowNegative) const {
nsMargin result; if (aClipAxes.isEmpty()) { return result;
} constauto& margin = StyleMargin()->mOverflowClipMargin; if (!aAllowNegative && margin.offset.IsZero()) { return result;
} switch (margin.visual_box) { case StyleOverflowClipMarginBox::BorderBox: break; case StyleOverflowClipMarginBox::PaddingBox:
result = -GetUsedBorder(); break; case StyleOverflowClipMarginBox::ContentBox:
result = -GetUsedBorderAndPadding(); break;
} if (!margin.offset.IsZero()) {
nscoord marginAu = margin.offset.ToAppUnits();
result += nsMargin(marginAu, marginAu, marginAu, marginAu);
} if (!aAllowNegative) {
result.EnsureAtLeast(nsMargin());
} return result;
}
do { if (node->IsNodeApzAware()) { returntrue;
}
nsIContent* shadowRoot = node->GetShadowRoot(); if (shadowRoot && shadowRoot->IsNodeApzAware()) { returntrue;
}
// Even if the node owning aFrame doesn't have apz-aware event listeners // itself, its shadow root or display: contents ancestors (which have no // frames) might, so we need to account for them too.
} while ((node = node->GetFlattenedTreeParent()) && node->IsElement() &&
node->AsElement()->IsDisplayContents());
if (IsFrameOrAncestorApzAware(aFrame)) {
aBuilder->SetAncestorHasApzAwareEventHandler(true);
}
}
staticvoid UpdateCurrentHitTestInfo(nsDisplayListBuilder* aBuilder,
nsIFrame* aFrame) { if (!aBuilder->BuildCompositorHitTestInfo()) { // Compositor hit test info is not used. return;
}
CheckForApzAwareEventHandlers(aBuilder, aFrame);
const CompositorHitTestInfo info =
aFrame->GetCompositorHitTestInfoWithoutPointerEvents(aBuilder);
aBuilder->SetInheritedCompositorHitTestInfo(info);
}
staticbool ItemParticipatesIn3DContext(nsIFrame* aAncestor,
nsDisplayItem* aItem) { auto type = aItem->GetType(); constbool isContainer = type == DisplayItemType::TYPE_WRAP_LIST ||
type == DisplayItemType::TYPE_CONTAINER;
if (isContainer && aItem->GetChildren()->Length() == 1) { // If the wraplist has only one child item, use the type of that item.
type = aItem->GetChildren()->GetBottom()->GetType();
}
if (type != DisplayItemType::TYPE_TRANSFORM &&
type != DisplayItemType::TYPE_PERSPECTIVE) { returnfalse;
}
nsIFrame* transformFrame = aItem->Frame(); if (aAncestor->GetContent() == transformFrame->GetContent()) { returntrue;
} return FrameParticipatesIn3DContext(aAncestor, transformFrame);
}
staticvoid WrapSeparatorTransform(nsDisplayListBuilder* aBuilder,
nsIFrame* aFrame,
nsDisplayList* aNonParticipants,
nsDisplayList* aParticipants, int aIndex,
nsDisplayItem** aSeparator) { if (aNonParticipants->IsEmpty()) { return;
}
// Try to compute a clip rect to bound the contents of the mask item // that will be built for |aMaskedFrame|. If we're not able to compute // one, return an empty Maybe. // The returned clip rect, if there is one, is relative to |aMaskedFrame|. static Maybe<nsRect> ComputeClipForMaskItem(
nsDisplayListBuilder* aBuilder, nsIFrame* aMaskedFrame, const SVGUtils::MaskUsage& aMaskUsage) { const nsStyleSVGReset* svgReset = aMaskedFrame->StyleSVGReset();
// Use an infinite dirty rect to pass into nsCSSRendering:: // GetImageLayerClip() because we don't have an actual dirty rect to // pass in. This is fine because the only time GetImageLayerClip() will // not intersect the incoming dirty rect with something is in the "NoClip" // case, and we handle that specially.
nsRect dirtyRect(nscoord_MIN / 2, nscoord_MIN / 2, nscoord_MAX,
nscoord_MAX);
// Round the clip out. In FrameLayerBuilder we round clips to nearest // pixels, and if we have a really thin clip here, that can cause the // clip to become empty if we didn't round out here. // The rounding happens in coordinates that are relative to the reference // frame, which matches what FrameLayerBuilder does.
combinedClip->RoundOut();
// Convert to app units.
nsRect result =
nsLayoutUtils::RoundGfxRectToAppRect(*combinedClip, devPixelRatio);
// The resulting clip is relative to the reference frame, but the caller // expects it to be relative to the masked frame, so adjust it.
result -= toReferenceFrame; return Some(result);
} return Nothing();
}
// Do not need to build the display list of the captured frames for event // delivery (i.e. to determine if this frame is under the mouse position). // // Per spec, hit-testing is skipped because the element’s DOM location does // not correspond to where its contents are rendered, so we could just skip // the building of the display list for these frames, and then these APIs, // e.g. elementFromPoint(), will skip these frames as well. // https://drafts.csswg.org/css-view-transitions-1/#view-transition-stacking-layer if (capturedByViewTransition && aBuilder->IsForEventDelivery()) { return;
}
// For preserves3d, use the dirty rect already installed on the // builder, since aDirtyRect maybe distorted for transforms along // the chain.
nsRect visibleRect = aBuilder->GetVisibleRect();
nsRect dirtyRect = aBuilder->GetDirtyRect();
// We build an opacity item if it's not going to be drawn by SVG content. // We could in principle skip creating an nsDisplayOpacity item if // nsDisplayOpacity::NeedsActiveLayer returns false and usingSVGEffects is // true (the nsDisplayFilter/nsDisplayMasksAndClipPaths could handle the // opacity). Since SVG has perf issues where we sometimes spend a lot of // time creating display list items that might be helpful. We'd need to // restore our mechanism to do that (changed in bug 1482403), and we'd // need to invalidate the frame if the value that would be return from // NeedsActiveLayer was to change, which we don't currently do. constbool useOpacity =
HasVisualOpacity(disp, effects, effectSetForOpacity) &&
!SVGUtils::CanOptimizeOpacity(this);
UniquePtr<nsDisplayListBuilder::AutoPreserves3DContext>
autoPreserves3DContext; if (extend3DContext && !combines3DTransformWithAncestors) { // Start a new preserves3d context to keep informations on // nsDisplayListBuilder.
autoPreserves3DContext =
MakeUnique<nsDisplayListBuilder::AutoPreserves3DContext>(aBuilder); // Save dirty rect on the builder to avoid being distorted for // multiple transforms along the chain.
aBuilder->SavePreserves3DRect();
// We rebuild everything within preserve-3d and don't try // to retain, so override the dirty rect now. if (aBuilder->IsRetainingDisplayList()) {
dirtyRect = visibleRect;
aBuilder->SetDisablePartialUpdates(true);
}
}
switch (prerenderInfo.mDecision) { case nsDisplayTransform::PrerenderDecision::Full: case nsDisplayTransform::PrerenderDecision::Partial:
dirtyRect = visibleRect; break; case nsDisplayTransform::PrerenderDecision::No: { // If we didn't prerender an animated frame in a preserve-3d context, // then we want disable async animations for the rest of the preserve-3d // (especially ancestors). if ((extend3DContext || combines3DTransformWithAncestors) &&
prerenderInfo.mHasAnimations) {
aBuilder->SavePreserves3DAllowAsyncAnimation(false);
}
// If we're in preserve-3d then grab the dirty rect that was given to // the root and transform using the combined transform. if (combines3DTransformWithAncestors) {
visibleRect = dirtyRect = aBuilder->GetPreserves3DRect();
}
constfloat appPerDev = PresContext()->AppUnitsPerDevPixel();
uint32_t flags = nsDisplayTransform::kTransformRectFlags &
~nsDisplayTransform::OFFSET_BY_ORIGIN; if (!hasPerspective) {
flags &= ~nsDisplayTransform::INCLUDE_PERSPECTIVE;
} if (!combines3DTransformWithAncestors) {
flags &= ~nsDisplayTransform::INCLUDE_PRESERVE3D_ANCESTORS;
} auto transform = nsDisplayTransform::GetResultingTransformMatrix( this, nsPoint(), appPerDev, flags);
nsRect untransformedDirtyRect; if (nsDisplayTransform::UntransformRect(dirtyRect, overflow, transform,
appPerDev,
&untransformedDirtyRect)) {
dirtyRect = untransformedDirtyRect;
nsDisplayTransform::UntransformRect(visibleRect, overflow, transform,
appPerDev, &visibleRect);
} else { // This should only happen if the transform is singular, in which case // nothing is visible anyway
dirtyRect.SetEmpty();
visibleRect.SetEmpty();
}
}
}
inTransform = true;
} elseif (IsFixedPosContainingBlock()) { // Restict the building area to the overflow rect for these frames, since // RetainedDisplayListBuilder uses it to know if the size of the stacking // context changed.
visibleRect.IntersectRect(visibleRect, InkOverflowRect());
dirtyRect.IntersectRect(dirtyRect, InkOverflowRect());
}
bool hasOverrideDirtyRect = false; // If we're doing a partial build, we're not invalid and we're capable // of having an override building rect (stacking context and fixed pos // containing block), then we should assume we have one. // Either we have an explicit one, or nothing in our subtree changed and // we have an implicit empty rect. // // These conditions should match |CanStoreDisplayListBuildingRect()| in // RetainedDisplayListBuilder.cpp if (!aBuilder->IsReusingStackingContextItems() &&
aBuilder->IsPartialUpdate() && !aBuilder->InInvalidSubtree() &&
!IsFrameModified() && IsFixedPosContainingBlock() &&
!GetPrevContinuation() && !GetNextContinuation()) {
dirtyRect = nsRect(); if (HasOverrideDirtyRegion()) {
nsDisplayListBuilder::DisplayListBuildingData* data =
GetProperty(nsDisplayListBuilder::DisplayListBuildingRect()); if (data) {
dirtyRect = data->mDirtyRect.Intersect(visibleRect);
hasOverrideDirtyRect = true;
}
}
}
if (capturedByViewTransition) { // Captured view transition elements must have their frames built regardless // of onscreen visibility so they can be snapshotted, since the snapshot can // itself be in view. We set visibleRect and dirtyRect to ensure the frame // and its descendants are painted.
visibleRect = InkOverflowRectRelativeToSelf();
dirtyRect = InkOverflowRectRelativeToSelf();
}
// Depending on the effects that are applied to this frame, we can create // multiple container display items and wrap them around our contents. // This enum lists all the potential container display items, in the order // outside to inside. enumclass ContainerItemType : uint8_t {
None = 0,
FixedPosition,
OwnLayerForTransformWithRoundedClip,
Perspective,
Transform,
Filter,
ViewTransitionCapture,
};
// NOTE(emilio): The order of these RAII objects is quite subtle.
nsDisplayListBuilder::AutoEnterViewTransitionCapture
inViewTransitionCaptureSetter(aBuilder, capturedByViewTransition);
RefPtr<const ActiveScrolledRoot> stickyASR = nullptr;
nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter asrSetter(aBuilder); if (aBuilder->IsInViewTransitionCapture()) { // View transition contents shouldn't scroll along our ASR. They get // "pulled out" of the rendering (or when they don't, you can't scroll // anyways).
asrSetter.SetCurrentActiveScrolledRoot(nullptr);
}
DisplayListClipState::AutoSaveRestore stickyItemClipState(aBuilder); if (useStickyPosition) {
StickyScrollContainer* stickyScrollContainer =
StickyScrollContainer::GetOrCreateForFrame(this); if (stickyScrollContainer && aBuilder->IsPaintingToWindow()) { if (!aBuilder->IsInViewTransitionCapture() &&
stickyScrollContainer->ScrollContainer()
->IsMaybeAsynchronouslyScrolled()) {
shouldFlattenStickyItem = false;
}
stickyScrollContainer->SetShouldFlatten(shouldFlattenStickyItem);
}
auto cssClip = GetClipPropClipRect(disp, effects, GetSize()); auto ApplyClipProp = [&](DisplayListClipState::AutoSaveRestore& aClipState) { if (!cssClip) { return;
}
nsPoint offset = aBuilder->GetCurrentFrameOffsetToReferenceFrame();
aBuilder->IntersectDirtyRect(*cssClip);
aBuilder->IntersectVisibleRect(*cssClip);
aClipState.ClipContentDescendants(*cssClip + offset);
};
// The CSS clip property is effectively inside the transform, but outside the // filters. So if we're not transformed we can apply it just here for // simplicity, instead of on each of the places that handle clipCapturedBy.
DisplayListClipState::AutoSaveRestore untransformedCssClip(aBuilder); if (!isTransformed) {
ApplyClipProp(untransformedCssClip);
}
// If there is a current clip, then depending on the container items we // create, different things can happen to it. Some container items simply // propagate the clip to their children and aren't clipped themselves. // But other container items, especially those that establish a different // geometry for their contents (e.g. transforms), capture the clip on // themselves and unset the clip for their contents. If we create more than // one of those container items, the clip will be captured on the outermost // one and the inner container items will be unclipped.
ContainerItemType clipCapturedBy = ContainerItemType::None; if (capturedByViewTransition) {
clipCapturedBy = isTransformed ? ContainerItemType::Transform
: ContainerItemType::ViewTransitionCapture;
} elseif (useFixedPosition) {
clipCapturedBy = ContainerItemType::FixedPosition;
} elseif (isTransformed) { const DisplayItemClipChain* currentClip =
aBuilder->ClipState().GetCurrentCombinedClipChain(aBuilder); if ((hasPerspective || extend3DContext) &&
(currentClip && currentClip->HasRoundedCorners())) { // If we're creating an nsDisplayTransform item that is going to combine // its transform with its children (preserve-3d or perspective), then we // can't have an intermediate surface. Mask layers force an intermediate // surface, so if we're going to need both then create a separate // wrapping layer for the mask.
clipCapturedBy = ContainerItemType::OwnLayerForTransformWithRoundedClip;
} elseif (hasPerspective) {
clipCapturedBy = ContainerItemType::Perspective;
} else {
clipCapturedBy = ContainerItemType::Transform;
}
} elseif (usingFilter) {
clipCapturedBy = ContainerItemType::Filter;
}
DisplayListClipState::AutoSaveRestore clipState(aBuilder); if (clipCapturedBy != ContainerItemType::None) {
clipState.Clear();
}
DisplayListClipState::AutoSaveRestore transformedCssClip(aBuilder); if (isTransformed) { // FIXME(emilio, bug 1525159): In the case we have a both a transform _and_ // filters, this clips the input to the filters as well, which is not // correct (clipping by the `clip` property is supposed to happen after // applying the filter effects, per [1]. // // This is not a regression though, since we used to do that anyway before // bug 1514384, and even without the transform we get it wrong. // // [1]: https://drafts.fxtf.org/css-masking/#placement
ApplyClipProp(transformedCssClip);
}
DisplayListClipState::AutoSaveRestore stickyItemNestedClipState(aBuilder); if (useStickyPosition && !shouldFlattenStickyItem) {
stickyItemNestedClipState.MaybeRemoveDisplayportClip();
}
// If we have a mask, compute a clip to bound the masked content. // This is necessary in case the content moves with an ancestor // ASR of the mask. // Don't do this if we also have a filter, because then the clip // would be applied before the filter, violating // https://www.w3.org/TR/filter-effects-1/#placement. // Filters are a containing block for fixed and absolute descendants, // so the masked content cannot move with an ancestor ASR. if (usingMask && !usingFilter) {
clipForMask = ComputeClipForMaskItem(aBuilder, this, maskUsage); if (clipForMask) {
aBuilder->IntersectDirtyRect(*clipForMask);
aBuilder->IntersectVisibleRect(*clipForMask);
nestedClipState.ClipContentDescendants(
*clipForMask + aBuilder->GetCurrentFrameOffsetToReferenceFrame());
}
}
// extend3DContext also guarantees that applyAbsPosClipping and // usingSVGEffects are false We only modify the preserve-3d rect if we are // the top of a preserve-3d heirarchy if (extend3DContext) { // Mark these first so MarkAbsoluteFramesForDisplayList knows if we are // going to be forced to descend into frames.
aBuilder->MarkPreserve3DFramesForDisplayList(this);
}
aBuilder->AdjustWindowDraggingRegion(this);
MarkAbsoluteFramesForDisplayList(aBuilder);
aBuilder->Check();
BuildDisplayList(aBuilder, set); // mBuiltDisplayList is observed only by painting-related tests (reftests // and paint-regression mochitests via nsIDOMWindowUtils), so record the // build only for painting display lists, not hit-testing, frame // visibility, or glyph-mask builds. if (aBuilder->IsForPainting()) {
SetBuiltDisplayList(true);
}
aBuilder->Check();
aBuilder->DisplayCaret(this, set.Outlines());
// Blend modes are a real pain for retained display lists. We build a blend // container item if the built list contains any blend mode items within // the current stacking context. This can change without an invalidation // to the stacking context frame, or the blend mode frame (e.g. by moving // an intermediate frame). // When we gain/remove a blend container item, we need to mark this frame // as invalid and have the full display list for merging to track // the change correctly. // It seems really hard to track this in advance, as the bookkeeping // required to note which stacking contexts have blend descendants // is complex and likely to be buggy. // Instead we're doing the sad thing, detecting it afterwards, and just // repeating display list building if it changed. // We have to repeat building for the entire display list (or at least // the outer stacking context), since we need to mark this frame as invalid // to remove any existing content that isn't wrapped in the blend container, // and then we need to build content infront/behind the blend container // to get correct positioning during merging. if (aBuilder->ContainsBlendMode() && aBuilder->IsRetainingDisplayList()) { if (aBuilder->IsPartialUpdate()) {
aBuilder->SetPartialBuildFailed(true);
} else {
aBuilder->SetDisablePartialUpdates(true);
}
}
}
if (aBuilder->IsBackgroundOnly()) {
set.BlockBorderBackgrounds()->DeleteAll(aBuilder);
set.Floats()->DeleteAll(aBuilder);
set.Content()->DeleteAll(aBuilder);
set.PositionedDescendants()->DeleteAll(aBuilder);
set.Outlines()->DeleteAll(aBuilder);
}
if (hasOverrideDirtyRect &&
StaticPrefs::layout_display_list_show_rebuild_area()) {
nsDisplaySolidColor* color = MakeDisplayItem<nsDisplaySolidColor>(
aBuilder, this,
dirtyRect + aBuilder->GetCurrentFrameOffsetToReferenceFrame(),
NS_RGBA(255, 0, 0, 64)); if (color) {
color->SetOverrideZIndex(INT32_MAX);
set.PositionedDescendants()->AppendToTop(color);
}
}
// Get the ASR to use for the container items that we create here. const ActiveScrolledRoot* containerItemASR = contASRTracker.GetContainerASR();
bool createdContainer = false; const StackingContextBits localIsolationReasons = [&] { auto reasons = StackingContextBits::None; if (!GetParent()) { // We don't need to isolate the root frame. return reasons;
} // Elements with a view-transition name also form a backdrop-root. Same for // masks / clip-path. // See https://www.w3.org/TR/css-view-transitions-1/#named-and-transitioning // and https://github.com/w3c/csswg-drafts/issues/11772 constbool hasViewTransitionName =
style.StyleUIReset()->HasViewTransitionName() &&
!style.IsRootElementStyle(); if ((disp->mWillChange.bits & StyleWillChangeBits::BACKDROP_ROOT) ||
hasViewTransitionName || usingMask) {
reasons |= StackingContextBits::ContainsBackdropFilter;
} if (!combines3DTransformWithAncestors) {
reasons |= StackingContextBits::MayContainNonIsolated3DTransform;
} return reasons;
}();
// If adding both a nsDisplayBlendContainer and a nsDisplayBlendMode to the // same list, the nsDisplayBlendContainer should be added first. This only // happens when the element creating this stacking context has mix-blend-mode // and also contains a child which has mix-blend-mode. // The nsDisplayBlendContainer must be added to the list first, so it does not // isolate the containing element blending as well. if (aBuilder->ContainsBlendMode()) {
resultList.AppendToTop(nsDisplayBlendContainer::CreateForMixBlendMode(
aBuilder, this, &resultList, containerItemASR,
nsDisplayItem::ContainerASRType::AncestorOfContained));
createdContainer = true;
MarkAsIsolated();
}
// NOTE: When changing this condition make sure to tweak ScrollContainerFrame // as well. constbool usingBackdropFilter = effects->HasBackdropFilters() &&
IsVisibleForPainting() &&
!style.IsRootElementStyle(); if (usingBackdropFilter) {
stackingContextTracker.AddToParent(
StackingContextBits::ContainsBackdropFilter);
nsRect backdropRect =
GetRectRelativeToSelf() + aBuilder->ToReferenceFrame(this);
resultList.AppendNewToTop<nsDisplayBackdropFilters>(
aBuilder, this, &resultList, backdropRect, this);
createdContainer = true;
MarkAsIsolated();
}
// If there are any SVG effects, wrap the list up in an SVG effects item // (which also handles CSS group opacity). Note that we create an SVG effects // item even if resultList is empty, since a filter can produce graphical // output even if the element being filtered wouldn't otherwise do so. if (usingSVGEffects) {
MOZ_ASSERT(usingFilter || usingMask, "Beside filter & mask/clip-path, what else effect do we have?");
if (clipCapturedBy == ContainerItemType::Filter) {
clipState.Restore();
} // Revert to the post-filter dirty rect.
aBuilder->SetVisibleRect(visibleRectOutsideSVGEffects);
// Skip all filter effects while generating glyph mask. if (usingFilter && !aBuilder->IsForGenerateGlyphMask()) { /* List now emptied, so add the new list to the top. */
resultList.AppendNewToTop<nsDisplayFilters>(aBuilder, this, &resultList, this, usingBackdropFilter);
createdContainer = true;
}
if (usingMask) { // The mask should move with aBuilder->CurrentActiveScrolledRoot(), so // that's the ASR we prefer to use for the mask item. However, we can // only do this if the mask if clipped with respect to that ASR, because // an item always needs to have finite bounds with respect to its ASR. // If we weren't able to compute a clip for the mask, we fall back to // using containerItemASR, which is the lowest common ancestor clip of // the mask's contents. That's not entirely correct, but it satisfies // the base requirement of the ASR system (that items have finite bounds // wrt. their ASR). const ActiveScrolledRoot* maskASR =
clipForMask.isSome() ? aBuilder->CurrentActiveScrolledRoot()
: containerItemASR; /* List now emptied, so add the new list to the top. */
resultList.AppendNewToTop<nsDisplayMasksAndClipPaths>(
aBuilder, this, &resultList, maskASR,
clipForMask.isSome()
? nsDisplayItem::ContainerASRType::Constant
: nsDisplayItem::ContainerASRType::AncestorOfContained,
usingBackdropFilter, ShouldForceIsolation());
createdContainer = true;
}
// TODO(miko): We could probably create a wraplist here and avoid creating // it later in |BuildDisplayListForChild()|.
createdContainer = false;
// Also add the hoisted scroll info items. We need those for APZ scrolling // because nsDisplayMasksAndClipPaths items can't build active layers.
aBuilder->ExitSVGEffectsContents();
resultList.AppendToTop(&hoistedScrollInfoItemsStorage);
}
// If the list is non-empty and there is CSS group opacity without SVG // effects, wrap it up in an opacity item. if (useOpacity) { constbool needsActiveOpacityLayer =
nsDisplayOpacity::NeedsActiveLayer(this);
resultList.AppendNewToTop<nsDisplayOpacity>(
aBuilder, this, &resultList, containerItemASR,
nsDisplayItem::ContainerASRType::AncestorOfContained,
opacityItemForEventsOnly, needsActiveOpacityLayer, usingBackdropFilter,
ShouldForceIsolation());
createdContainer = true;
}
// We build nsDisplayViewTransitionCapture here, and then use // nsDisplayTransform to wrap it, to make sure we create the correct transform // for the captured element and its descendants. This is necessary to make // sure our captured element doesn't become blurry when using scale() // transform. // // So the display list looks like this: // nsDisplayTransform // For the captured element if it is transformed // VTCapture // For the captured element // ... // Other display items // For the descendants of the captured element // ... // ... // // We intentionally use nsDisplayTransform to wrap the VTCapture (so it's // different from opacity display item) because nsDisplayTransform may push a // reference frame which creates a new coordinate system in WR. So it's just // like a separator between the VTCapture the outside, if it is transformed. if (capturedByViewTransition) {
resultList.AppendNewToTop<nsDisplayViewTransitionCapture>(
aBuilder, this, &resultList, nullptr, false);
createdContainer = true;
MarkAsIsolated(); // We don't want the capture to be clipped, so we do this _after_ building // the wrapping item. if (clipCapturedBy == ContainerItemType::ViewTransitionCapture) {
clipState.Restore();
}
}
// If we're going to apply a transformation and don't have preserve-3d set, // wrap everything in an nsDisplayTransform. If there's nothing in the list, // don't add anything. // // For the preserve-3d case we want to individually wrap every child in the // list with a separate nsDisplayTransform instead. When the child is already // an nsDisplayTransform, we can skip this step, as the computed transform // will already include our own. // // We also traverse into sublists created by nsDisplayWrapList, so that we // find all the correct children. // // We still need to create nsDisplayTransform to wrap the VT capture element // to make sure WR renders it properly if it is transformed as well. if (isTransformed) { if (extend3DContext) { // Install dummy nsDisplayTransform as a leaf containing // descendants not participating this 3D rendering context.
nsDisplayList nonparticipants(aBuilder);
nsDisplayList participants(aBuilder); int index = 1;
nsDisplayItem* separator = nullptr;
// TODO: This can be simplified: |participants| is just |resultList|. for (nsDisplayItem* item : resultList.TakeItems()) { if (ItemParticipatesIn3DContext(this, item) &&
!item->GetClip().HasClip()) { // The frame of this item participates the same 3D context.
WrapSeparatorTransform(aBuilder, this, &nonparticipants,
&participants, index++, &separator);
participants.AppendToTop(item);
} else { // The frame of the item doesn't participate the current // context, or has no transform. // // For items participating but not transformed, they are add // to nonparticipants to get a separator layer for handling // clips, if there is, on an intermediate surface. // \see ContainerLayer::DefaultComputeEffectiveTransforms().
nonparticipants.AppendToTop(item);
}
}
WrapSeparatorTransform(aBuilder, this, &nonparticipants, &participants,
index++, &separator);
if (separator) {
createdContainer = true;
MarkAsIsolated();
}
resultList.AppendToTop(&participants);
}
transformedCssClip.Restore(); if (clipCapturedBy == ContainerItemType::Transform) { // Restore clip state now so nsDisplayTransform is clipped properly.
clipState.Restore();
} // Revert to the dirtyrect coming in from the parent, without our transform // taken into account.
aBuilder->SetVisibleRect(visibleRectOutsideTransform);
if (this != aBuilder->RootReferenceFrame()) { // Revert to the outer reference frame and offset because all display // items we create from now on are outside the transform.
nsPoint toOuterReferenceFrame; const nsIFrame* outerReferenceFrame =
aBuilder->FindReferenceFrameFor(GetParent(), &toOuterReferenceFrame);
toOuterReferenceFrame += GetPosition();
// We would like to block async animations for ancestors of ones not // prerendered in the preserve-3d tree. Now that we've finished processing // all descendants, update allowAsyncAnimation to take their prerender // state into account // FIXME: We don't block async animations for previous siblings because // their prerender decisions have been made. We may have to figure out a // better way to rollback their prerender decisions. // Alternatively we could not block animations for later siblings, and only // block them for ancestors of a blocked one. if ((extend3DContext || combines3DTransformWithAncestors) &&
prerenderInfo.CanUseAsyncAnimations() &&
!aBuilder->GetPreserves3DAllowAsyncAnimation()) { // aBuilder->GetPreserves3DAllowAsyncAnimation() means the inner or // previous silbing frames are allowed/disallowed for async animations.
prerenderInfo.mDecision = nsDisplayTransform::PrerenderDecision::No;
}
// TODO(emilio): Ideally should also isolate when the transform is // potentially animated (prerenderInfo.mHasAnimations), but that causes a // lot of fuzz on Windows due to text antialiasing. constbool hasMaybe3dTransform =
hasPerspective || !transformItem->GetTransform().Is2D(); if (hasMaybe3dTransform) {
stackingContextTracker.AddToParent(
StackingContextBits::MayContainNonIsolated3DTransform);
}
} if (clipCapturedBy ==
ContainerItemType::OwnLayerForTransformWithRoundedClip) {
clipState.Restore();
resultList.AppendNewToTopWithIndex<nsDisplayOwnLayer>(
aBuilder, this, /* aIndex = */ nsDisplayOwnLayer::OwnLayerForTransformWithRoundedClip,
&resultList, aBuilder->CurrentActiveScrolledRoot(),
nsDisplayItem::ContainerASRType::Constant,
nsDisplayOwnLayerFlags::None, ScrollbarData{}, /* aForceActive = */ false, false);
createdContainer = true;
}
}
// If we have sticky positioning, wrap it in a sticky position item. if (useFixedPosition && !capturedByViewTransition) { if (clipCapturedBy == ContainerItemType::FixedPosition) {
clipState.Restore();
} // The ASR for the fixed item should be the ASR of our containing block, // which has been set as the builder's current ASR, unless this frame is // invisible and we hadn't saved display item data for it. In that case, // we need to take the containerItemASR since we might have fixed children. // For WebRender, we want to the know what |containerItemASR| is for the // case where the fixed-pos item is not a "real" fixed-pos item (e.g. it's // nested inside a scrolling transform), so we stash that on the display // item as well. const ActiveScrolledRoot* fixedASR = ActiveScrolledRoot::PickAncestor(
containerItemASR, aBuilder->CurrentActiveScrolledRoot()); const ActiveScrolledRoot* scrollTargetASR =
containerItemASR ? containerItemASR->GetNearestScrollASR() : nullptr;
resultList.AppendNewToTop<nsDisplayFixedPosition>(
aBuilder, this, &resultList, fixedASR,
nsDisplayItem::ContainerASRType::AncestorOfContained, scrollTargetASR,
ShouldForceIsolation());
createdContainer = true;
} elseif (useStickyPosition && !capturedByViewTransition) { // For position:sticky, the clip needs to be applied both to the sticky // container item and to the contents. The container item needs the clip // because a scrolled clip needs to move independently from the sticky // contents, and the contents need the clip so that they have finite // clipped bounds with respect to the container item's ASR. The latter is // a little tricky in the case where the sticky item has both fixed and // non-fixed descendants, because that means that the sticky container // item's ASR is the ASR of the fixed descendant. // For WebRender display list building, though, we still want to know the // the ASR that the sticky container item would normally have, so we stash // that on the display item as the "container ASR" (i.e. the normal ASR of // the container item, excluding the special behaviour induced by fixed // descendants). const ActiveScrolledRoot* stickyItemASR = ActiveScrolledRoot::PickAncestor(
containerItemASR, aBuilder->CurrentActiveScrolledRoot());
// If the sticky element is inside a filter, annotate the scroll frame that // scrolls the filter as having out-of-flow content inside a filter (this // inhibits paint skipping). if (aBuilder->GetFilterASR() && aBuilder->GetFilterASR() == stickyItemASR) {
aBuilder->GetFilterASR()
->GetNearestScrollASR()
->ScrollFrame()
->SetHasOutOfFlowContentInsideFilter();
}
}
// If there's blending, wrap up the list in a blend-mode item. Note that // opacity can be applied before blending as the blend color is not affected // by foreground opacity (only background alpha). if (effects->mMixBlendMode != StyleBlend::Normal) {
stackingContextTracker.AddToParent(
StackingContextBits::ContainsMixBlendMode);
resultList.AppendNewToTop<nsDisplayBlendMode>(
aBuilder, this, &resultList, effects->mMixBlendMode, containerItemASR,
nsDisplayItem::ContainerASRType::AncestorOfContained, false);
createdContainer = true;
MarkAsIsolated();
}
// Mark the outermost display item as reusable. These display items and // their chidren can be reused during the next paint if no ancestor or // descendant frames have been modified. if (!container->IsReusedItem()) {
container->SetReusable();
}
aList->AppendToTop(container);
createdContainer = true;
} else {
aList->AppendToTop(&resultList);
}
if (aCreatedContainerItem) {
*aCreatedContainerItem = createdContainer;
}
}
// We need a wrap list if there are multiple items, or if the single // item has a different frame. This can change in a partial build depending // on which items we build, so we need to ensure that we don't transition // to/from a wrap list without invalidating correctly. bool needsWrapList =
aList->Length() > 1 || item->Frame() != aFrame || item->GetChildren();
// If we have an explicit container item (that can't change without an // invalidation) or we're doing a full build and don't need a wrap list, then // we can skip adding one. if (aBuiltContainerItem || (!aBuilder->IsPartialUpdate() && !needsWrapList)) {
MOZ_ASSERT(aList->Length() == 1);
aList->Clear(); return item;
}
// If we're doing a partial build and we didn't need a wrap list // previously then we can try to work from there. if (aBuilder->IsPartialUpdate() &&
!aFrame->HasDisplayItem(uint32_t(DisplayItemType::TYPE_CONTAINER))) { // If we now need a wrap list, we must previously have had no display items // or a single one belonging to this frame. Mark the item itself as // discarded so that RetainedDisplayListBuilder uses the ones we just built. // We don't want to mark the frame as modified as that would invalidate // positioned descendants that might be outside of this list, and might not // have been rebuilt this time. if (needsWrapList) {
DiscardOldItems(aFrame);
} else {
MOZ_ASSERT(aList->Length() == 1);
aList->Clear(); return item;
}
}
// The last case we could try to handle is when we previously had a wrap list, // but no longer need it. Unfortunately we can't differentiate this case from // a partial build where other children exist but we just didn't build them // this time. // TODO:RetainedDisplayListBuilder's merge phase has the full list and // could strip them out.
// If the child is a scrollframe that we want to ignore, then we need // to descend into it because its scrolled child may intersect the dirty // area even if the scrollframe itself doesn't. if (aChild == aBuilder->GetIgnoreScrollFrame()) { returntrue;
}
// There are cases where the "ignore scroll frame" on the builder is not set // correctly, and so we additionally want to catch cases where the child is // a root scrollframe and we are ignoring scrolling on the viewport. if (aChild == aBuilder->GetPresShellIgnoreScrollFrame()) { returntrue;
}
nsRect overflow = aChild->InkOverflowRect();
// On mobile, there may be a dynamic toolbar. The root content document's // root scroll frame's ink overflow rect does not include the toolbar // height, but if the toolbar is hidden, we still want to be able to target // content underneath the toolbar, so expand the overflow rect here to // allow display list building to descend into the scroll frame. if (aBuilder->IsForEventDelivery() &&
aChild == aChild->PresShell()->GetRootScrollContainerFrame() &&
aChild->PresContext()->IsRootContentDocumentCrossProcess() &&
aChild->PresContext()->HasDynamicToolbar()) {
overflow.SizeTo(nsLayoutUtils::ExpandHeightForDynamicToolbar(
aChild->PresContext(), overflow.Size()));
}
if (aDirty.Intersects(overflow)) { returntrue;
}
if (aChild->ForceDescendIntoIfVisible() && aVisible.Intersects(overflow)) { returntrue;
}
if (aChild->IsTablePart()) { // Relative positioning and transforms can cause table parts to move, but we // will still paint the backgrounds for their ancestor parts under them at // their 'normal' position. That means that we must consider the overflow // rects at both positions.
// We convert the overflow rect into the nsTableFrame's coordinate // space, applying the normal position offset at each step. Then we // compare that against the builder's cached dirty rect in table // coordinate space. const nsIFrame* f = aChild;
nsRect normalPositionOverflowRelativeToTable = overflow;
while (f->IsTablePart()) {
normalPositionOverflowRelativeToTable += f->GetNormalPosition();
f = f->GetParent();
}
void nsIFrame::BuildDisplayListForSimpleChild(nsDisplayListBuilder* aBuilder,
nsIFrame* aChild, const nsDisplayListSet& aLists) { // This is the shortcut for frames been handled along the common // path, the most common one of THE COMMON CASE mentioned later.
MOZ_ASSERT(aChild->Type() != LayoutFrameType::Placeholder);
MOZ_ASSERT(!aBuilder->GetSelectedFramesOnly() &&
!aBuilder->GetIncludeAllOutOfFlows(), "It should be held for painting to window");
MOZ_ASSERT(aChild->HasAnyStateBits(NS_FRAME_SIMPLE_DISPLAYLIST));
// Child cannot be transformed since it is not a stacking context.
nsDisplayListBuilder::AutoBuildingDisplayList buildingForChild(
aBuilder, aChild, visible, dirty, false);
staticbool ShouldSkipFrame(nsDisplayListBuilder* aBuilder, const nsIFrame* aFrame) { // If painting is restricted to just the background of the top level frame, // then we have nothing to do here. if (aBuilder->IsBackgroundOnly()) { returntrue;
} if (aBuilder->IsForGenerateGlyphMask()) { if ((aFrame->IsLeaf() && !aFrame->IsTextFrame()) ||
aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) { // Only in-flow text frames are painted for background-clip: text mask // generation. returntrue;
}
} if (aBuilder->GetSelectedFramesOnly() && aFrame->IsLeaf() &&
!aFrame->IsSelected()) { returntrue;
} staticconst nsFrameState skipFlags = NS_FRAME_TOO_DEEP_IN_FRAME_TREE |
NS_FRAME_IS_NONDISPLAY |
NS_FRAME_POSITION_VISIBILITY_HIDDEN; if (aFrame->HasAnyStateBits(skipFlags)) { returntrue;
} // 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. return XRE_IsParentProcess() &&
aFrame->StyleUIReset()->mMozSubtreeHiddenOnlyVisually;
}
#ifdefined(ACCESSIBILITY) && defined(MOZ_ENABLE_SKIA_PDF) // Bug 2025119: If this is inlined in nsIFrame::BuildDisplayListForChild on // Win32, we end up with crashes when there is deep recursion due to the // increased stack size caused by the additional variables here. Work around // this by having this code in its own function and ensuring it is never inlined // on Win32. # ifdefined(XP_WIN) && !defined(_WIN64)
MOZ_NEVER_INLINE # endif staticvoid MaybeAddAccId(nsIFrame* aChildOrOutOfFlow,
nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists) { auto [bcId, accId] = a11y::PdfStructTreeBuilder::GetAccId(aChildOrOutOfFlow); if (!bcId) { return;
} // When generating tagged PDF, associate this content with the correct // node in the structure tree. auto* item = MakeDisplayItem<nsDisplayAccessibleId>(
aBuilder, aChildOrOutOfFlow, bcId, accId);
aLists.Content()->AppendToTop(item);
} #endif
// If we're generating a display list for printing, include Link items for // frames that correspond to HTML link elements so that we can have active // links in saved PDF output. Note that the state of "within a link" is // set on the display-list builder, such that all descendants of the link // element will generate display-list links. // TODO: we should be able to optimize this so as to avoid creating links // for the same destination that entirely overlap each other, which adds // nothing useful to the final PDF.
Maybe<nsDisplayListBuilder::Linkifier> linkifier; if (aBuilder->IsForPrinting()) { if (StaticPrefs::print_save_as_pdf_links_enabled()) {
linkifier.emplace(aBuilder, childOrOutOfFlow, aLists.Content());
linkifier->MaybeAppendLink(aBuilder, childOrOutOfFlow);
} #ifdefined(ACCESSIBILITY) && defined(MOZ_ENABLE_SKIA_PDF)
MaybeAddAccId(childOrOutOfFlow, aBuilder, aLists); #endif
}
constbool isPaintingToWindow = aBuilder->IsPaintingToWindow(); constbool doingShortcut =
isPaintingToWindow &&
child->HasAnyStateBits(NS_FRAME_SIMPLE_DISPLAYLIST) && // Animations may change the stacking context state. // ShouldApplyOverflowClipping is affected by the parent style, which does // not invalidate the NS_FRAME_SIMPLE_DISPLAYLIST bit.
!(!overflowClipAxes.isEmpty() || child->MayHaveTransformAnimation() ||
child->MayHaveOpacityAnimation());
if (StaticPrefs::layout_css_scroll_anchoring_highlight()) { if (child->FirstContinuation()->IsScrollAnchor()) {
nsRect bounds = child->GetContentRectRelativeToSelf() +
aBuilder->ToReferenceFrame(child);
nsDisplaySolidColor* color = MakeDisplayItem<nsDisplaySolidColor>(
aBuilder, child, bounds, NS_RGBA(255, 0, 255, 64)); if (color) {
color->SetOverrideZIndex(INT32_MAX);
aLists.PositionedDescendants()->AppendToTop(color);
}
}
}
if (doingShortcut) {
BuildDisplayListForSimpleChild(aBuilder, child, aLists); return;
}
nsDisplayListBuilder::OutOfFlowDisplayData* savedOutOfFlowData = nullptr; if (placeholder) { if (placeholder->HasAnyStateBits(PLACEHOLDER_FOR_TOPLAYER)) { // If the out-of-flow frame is in the top layer, the viewport frame // will paint it. Skip it here. Note that, only out-of-flow frames // with this property should be skipped, because non-HTML elements // may stop their children from being out-of-flow. Those frames // should still be handled in the normal in-flow path. return;
}
child = childOrOutOfFlow;
// If 'child' is a pushed out-of-flow then it's owned by a block that's not // an ancestor of the placeholder, and it will be painted by that block and // should not be painted through the placeholder. Also recheck // NS_FRAME_TOO_DEEP_IN_FRAME_TREE and NS_FRAME_IS_NONDISPLAY. staticconst nsFrameState skipFlags =
(NS_FRAME_IS_PUSHED_OUT_OF_FLOW | NS_FRAME_TOO_DEEP_IN_FRAME_TREE |
NS_FRAME_IS_NONDISPLAY); if (child->HasAnyStateBits(skipFlags) || nsLayoutUtils::IsPopup(child)) { return;
}
// For a transformed absolute containing block, if the abspos's placeholder // is in a different continuation's subtree, the abspos was already built // directly by DisplayAbsoluteFramesNotBuiltByPlaceholder() on its // containing block. Exclude inline frames because IB-split (bug 489100) // can cause the placeholder to live in a later IB-split sibling. if (parent->IsTransformed() && !parent->IsInlineFrame() &&
child->IsAbsolutelyPositioned() &&
!nsLayoutUtils::IsProperAncestorFrame(parent, placeholder)) { return;
}
if (aBuilder->GetIncludeAllOutOfFlows()) {
visible = child->InkOverflowRect();
dirty = child->InkOverflowRect();
} elseif (savedOutOfFlowData) {
visible =
savedOutOfFlowData->GetVisibleRectForFrame(aBuilder, child, &dirty);
} else { // The out-of-flow frame did not intersect the dirty area. We may still // need to traverse into it, since it may contain placeholders we need // to enter to reach other out-of-flow frames that are visible.
visible.SetEmpty();
dirty.SetEmpty();
}
}
NS_ASSERTION(!child->IsPlaceholderFrame(), "Should have dealt with placeholders already");
// This flag is raised if the control flow strays off the common path. // The common path is the most common one of THE COMMON CASE mentioned later. bool awayFromCommonPath = !isPaintingToWindow;
// true if this is a real or pseudo stacking context bool pseudoStackingContext =
aFlags.contains(DisplayChildFlag::ForcePseudoStackingContext);
if (!pseudoStackingContext && !isSVG &&
aFlags.contains(DisplayChildFlag::Inline) &&
!child->IsLineParticipant()) { // child is a non-inline frame in an inline context, i.e., // it acts like inline-block or inline-table. Therefore it is a // pseudo-stacking-context.
pseudoStackingContext = true;
}
// Since we're now sure that we're adding this frame to the display list // (which means we're painting it, modulo occlusion), mark it as visible // within the displayport. if (isPaintingToWindow && child->TrackingVisibility() &&
child->IsVisibleForPainting()) {
child->PresShell()->EnsureFrameInApproximatelyVisibleList(child);
awayFromCommonPath = true;
}
// Child is composited if it's transformed, partially transparent, or has // SVG effects or a blend mode.. const nsStyleDisplay* disp = child->StyleDisplay(); const nsStyleEffects* effects = child->StyleEffects();
if (savedOutOfFlowData) {
aBuilder->SetBuildingInvisibleItems(false);
nsIFrame* scrollsWithAnchor = nullptr; if (aBuilder->IsPaintingToWindow() && // If we are in view transition capture we get a null asr no matter // what, so don't bother checking for async scrolling with a CSS anchor // pos anchor.
!aBuilder->IsInViewTransitionCapture() &&
child->IsAbsolutelyPositioned(disp) && // If there is an active view transition in this document it is tricky // to determine what will be an active scroll frame outside of that // frame's BuildDisplayList, so don't bother to async scroll with an // anchor in that case. Bug 2001861 tracks removing this check.
!PresContext()->Document()->GetActiveViewTransition()) {
scrollsWithAnchor = AnchorPositioningUtils::GetAnchorThatFrameScrollsWith(
child, aBuilder);
if (scrollsWithAnchor && aBuilder->IsRetainingDisplayList()) { if (aBuilder->IsPartialUpdate()) {
aBuilder->SetPartialBuildFailed(true);
} else {
aBuilder->SetDisablePartialUpdates(true);
}
}
}
const ActiveScrolledRoot* asr =
savedOutOfFlowData->mContainingBlockActiveScrolledRoot;
#ifdef DEBUG if (aBuilder->IsPaintingToWindow()) { // Assert that the asr is as expected. if (savedOutOfFlowData->mContainingBlockInViewTransitionCapture) {
MOZ_ASSERT(asr == nullptr);
MOZ_ASSERT(aBuilder->IsInViewTransitionCapture());
} elseif ((asr ? FrameAndASRKind{asr->mFrame, asr->mKind}
: FrameAndASRKind::default_value()) !=
DisplayPortUtils::GetASRAncestorFrame(
{child->GetParent(), ActiveScrolledRoot::ASRKind::Scroll},
aBuilder)) { // A weird case for native anonymous content in the custom content // container when the root is captured by a view transition. This // content is built outside of the view transition capture but the // containing block (the canvas frame) was built inside the capture, so // savedOutOfFlowData is saved as if we are inside the capture while we // are outside it (bug 2002160).
MOZ_ASSERT(asr == nullptr);
MOZ_ASSERT(PresContext()->Document()->GetActiveViewTransition());
MOZ_ASSERT(
child->GetParent()->GetContent()->IsInNativeAnonymousSubtree()); bool inTopLayer = false;
nsIFrame* curr = child->GetParent(); while (curr) { if (curr->StyleDisplay()->mTopLayer == StyleTopLayer::Auto) {
inTopLayer = true; break;
}
curr = curr->GetParent();
}
MOZ_ASSERT(inTopLayer);
}
} #endif
if (scrollsWithAnchor) {
asr = DisplayPortUtils::ActivateDisplayportOnASRAncestors(
scrollsWithAnchor, child->GetParent(), asr, aBuilder);
if (aBuilder->IsInViewTransitionCapture()) { if (!savedOutOfFlowData->mContainingBlockInViewTransitionCapture) {
clipState.Clear();
} else {
clipState.SetClipChainForContainingBlockDescendants(
savedOutOfFlowData->mContainingBlockClipChain);
}
asrSetter.SetCurrentActiveScrolledRoot(nullptr);
} else {
clipState.SetClipChainForContainingBlockDescendants(
savedOutOfFlowData->mContainingBlockClipChain);
asrSetter.SetCurrentActiveScrolledRoot(asr);
asrSetter.SetCurrentScrollParentId(savedOutOfFlowData->mScrollParentId);
}
MOZ_ASSERT(awayFromCommonPath, "It is impossible when savedOutOfFlowData is true");
} elseif (HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO) &&
placeholder) {
NS_ASSERTION(visible.IsEmpty(), "should have empty visible rect"); // Every item we build from now until we descent into an out of flow that // does have saved out of flow data should be invisible. This state gets // restored when AutoBuildingDisplayList gets out of scope.
aBuilder->SetBuildingInvisibleItems(true);
// If we have nested out-of-flow frames and the outer one isn't visible // then we won't have stored clip data for it. We can just clear the clip // instead since we know we won't render anything, and the inner out-of-flow // frame will setup the correct clip for itself.
clipState.SetClipChainForContainingBlockDescendants(nullptr);
}
// Setup clipping for the parent's overflow:clip, // or overflow:hidden on elements that don't support scrolling (and therefore // don't create nsHTML/XULScrollFrame). This clipping needs to not clip // anything directly rendered by the parent, only the rendering of its // children. // Don't use overflowClip to restrict the dirty rect, since some of the // descendants may not be clipped by it. Even if we end up with unnecessary // display items, they'll be pruned during ComputeVisibility. // // FIXME(emilio): Why can't we handle this more similarly to `clip` (on the // parent, rather than on the children)? Would ClipContentDescendants do what // we want? if (!overflowClipAxes.isEmpty()) {
ApplyOverflowClipping(aBuilder, parent, overflowClipAxes, clipState);
awayFromCommonPath = true;
}
child->MarkAbsoluteFramesForDisplayList(aBuilder); if (aBuilder->IsForPainting()) {
child->SetBuiltDisplayList(true);
}
// Some SVG frames might change opacity without invalidating the frame, so // exclude them from the fast-path. if (!awayFromCommonPath && !child->IsSVGFrame()) { // The shortcut is available for the child for next time.
child->AddStateBits(NS_FRAME_SIMPLE_DISPLAYLIST);
}
if (!pseudoStackingContext) { // THIS IS THE COMMON CASE. // Not a pseudo or real stacking context. Do the simple thing and // return early.
aBuilder->AdjustWindowDraggingRegion(child);
aBuilder->Check();
child->BuildDisplayList(aBuilder, aLists);
aBuilder->Check();
aBuilder->DisplayCaret(child, aLists.Outlines()); return;
}
// A pseudo-stacking context (e.g., a positioned element with z-index auto). // We allow positioned descendants of the child to escape to our parent // stacking context's positioned descendant list, because they might be // z-index:non-auto
nsDisplayListCollection pseudoStack(aBuilder);
if (!list.IsEmpty()) { if (isPositioned || isStackingContext) { // Genuine stacking contexts, and positioned pseudo-stacking-contexts, // go in this level.
nsDisplayItem* item = WrapInWrapList(aBuilder, child, &list, wrapListASR,
builtContainerItem); if (isSVG) {
aLists.Content()->AppendToTop(item);
} else {
aLists.PositionedDescendants()->AppendToTop(item);
}
} elseif (!isSVG && disp->IsFloating(child)) {
aLists.Floats()->AppendToTop(
WrapInWrapList(aBuilder, child, &list, wrapListASR));
} else {
aLists.Content()->AppendToTop(&list);
}
} // We delay placing the positioned descendants of positioned frames to here, // because in the absence of z-index this is the correct order for them. // This doesn't affect correctness because the positioned descendants list // is sorted by z-order and content in BuildDisplayListForStackingContext, // but it means that sort routine needs to do less work.
aLists.PositionedDescendants()->AppendToTop(&extraPositionedDescendants);
}
nsIContent* nsIFrame::GetExplicitEventTargetContent( const WidgetEvent* aEvent /* = nullptr */) const { // Return the content as-is if this is not a generated content. if (!IsGeneratedContentFrame()) { return GetContent();
} // If the content is a generated content, it won't handle any events from the // DOM point of view. Therefore, let's return the parent of the generated // content. const nsIFrame* generatedRoot = this; while (true) { auto* parent = nsLayoutUtils::GetParentOrPlaceholderFor(generatedRoot); if (!parent || !parent->IsGeneratedContentFrame()) { break;
}
generatedRoot = parent;
} // Return the non-generated ancestor. return generatedRoot->GetContent()->GetParent();
}
if (target) { auto asyncDispatcher = MakeRefPtr<AsyncEventDispatcher>(
target, aDOMEventName, CanBubble::eYes, ChromeOnlyDispatch::eNo);
DebugOnly<nsresult> rv = asyncDispatcher->PostDOMEvent();
NS_ASSERTION(NS_SUCCEEDED(rv), "AsyncEventDispatcher failed to dispatch");
}
}
nsresult nsIFrame::HandleEvent(nsPresContext* aPresContext,
WidgetGUIEvent* aEvent,
nsEventStatus* aEventStatus) { if (aEvent->mMessage == eMouseMove) { // XXX If the second argument of HandleDrag() is WidgetMouseEvent, // the implementation becomes simpler. return HandleDrag(aPresContext, aEvent, aEventStatus);
}
// When secondary button is down, we need to move selection to make users // possible to paste something at click point quickly. // When middle button is down, we need to just move selection and focus at // the clicked point. Note that even if middle click paste is not enabled, // Chrome moves selection at middle mouse button down. So, we should follow // the behavior for the compatibility. if (aEvent->mMessage == eMouseDown) {
WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent(); if (mouseEvent && (mouseEvent->mButton == MouseButton::eSecondary ||
mouseEvent->mButton == MouseButton::eMiddle)) { if (*aEventStatus == nsEventStatus_eConsumeNoDefault) { return NS_OK;
} return MoveCaretToEventPoint(aPresContext, mouseEvent, aEventStatus);
}
}
// DISPLAY_ALL means we're in an editor. // If already in cell selection mode, // continue selecting with mouse drag or end on mouse up, // or when using shift key to extend block of cells // (Mouse down does normal selection unless Ctrl/Cmd is pressed) bool doTableSelection =
displaySelection == nsISelectionDisplay::DISPLAY_ALL &&
selectingTableCells &&
(aMouseEvent->mMessage == eMouseMove ||
(aMouseEvent->mMessage == eMouseUp &&
aMouseEvent->mButton == MouseButton::ePrimary) ||
aMouseEvent->IsShift());
if (!doTableSelection) { // In Browser, special 'table selection' key must be pressed for table // selection or when just Shift is pressed and we're already in table/cell // selection mode #ifdef XP_MACOSX
doTableSelection = aMouseEvent->IsMeta() ||
(aMouseEvent->IsShift() && selectingTableCells); #else
doTableSelection = aMouseEvent->IsControl() ||
(aMouseEvent->IsShift() && selectingTableCells); #endif
} if (!doTableSelection) { return NS_OK;
}
// Get the cell frame or table frame (or parent) of the current content node
nsIFrame* frame = this; bool foundCell = false; bool foundTable = false;
if (independentSelectionLimiter &&
!independentSelectionLimiter->Contains(GetContent())) { // If our content is not under the limiting node, stop the search right now. return NS_OK;
}
for (; frame; frame = frame->GetParent()) { // Check for a table cell by querying to a known CellFrame interface if (static_cast<nsITableCellLayout*>(do_QueryFrame(frame))) {
foundCell = true; // TODO: If we want to use proximity to top or left border // for row and column selection, this is the place to do it break;
} if (frame->IsTableWrapperFrame()) { // If not a cell, check for table. This will happen when the starting // frame is the table or child of a table, such as a row (we were // in-between cells or in table border). // TODO: How can we select row when along left table edge // or select column when along top edge?
foundTable = true; break;
}
}
if (!foundCell && !foundTable) { // We aren't in a cell or table. return NS_OK;
}
nsIContent* tableOrCellContent = frame->GetContent(); if (!tableOrCellContent) { return NS_OK;
}
if (independentSelectionLimiter &&
!independentSelectionLimiter->Contains(tableOrCellContent)) { return NS_OK;
}
nsCOMPtr<nsIContent> parentContent = tableOrCellContent->GetParent(); if (!parentContent) { return NS_ERROR_FAILURE;
}
const int32_t offset =
parentContent->ComputeIndexOf_Deprecated(tableOrCellContent); // Not likely? if (offset < 0) { return NS_ERROR_FAILURE;
}
// Everything is OK -- set the return values
parentContent.forget(aParentContent);
// Per https://drafts.csswg.org/css-ui-4/#content-selection: // // The used value is the same as the computed value, except: // // 1 - on editable elements where the used value is always 'contain' // regardless of the computed value // 2 - when the computed value is auto, in which case the used value is one // of the other values... // // See https://github.com/w3c/csswg-drafts/issues/3344 to see why we do this // at used-value time instead of at computed-value time.
bool nsIFrame::ShouldPaintNormalSelection() const { if (IsSelectable()) { // NOTE: Ideally, we should return false if display selection is "OFF". // However, here is a hot path at painting. Therefore, it should be checked // before and we shouldn't need to check it. returntrue;
} // If we're not selectable by user, we should paint selection only while the // normal selection is styled as "attention" by "Find in Page" or something. return GetDisplaySelection() == nsISelectionController::SELECTION_ATTENTION;
}
mozilla::PresShell* presShell = aPresContext->GetPresShell(); if (!presShell) { return NS_ERROR_FAILURE;
}
// We often get out of sync state issues with mousedown events that // get interrupted by alerts/dialogs. // Check with the ESM to see if we should process this one
EventStateManager* const esm = aPresContext->EventStateManager(); if (!esm->EventStatusOK(aMouseEvent)) { return NS_OK;
}
if (nsIContent* dragGestureContent = esm->GetTrackingDragGestureContent()) { // When the current tracked dragging gesture is different than this frame, // it means this frame was being dragged, however it got moved/destroyed. So // we should consider the drag is still happening, so return early here. // Additionally, when dragGestureContent is a `Text` and the text frame is // reframed by a preceding event listener, we're the parent element frame. // In that case, we need to treat this as a normal mouse button down. // Therefore, we should compare the inclusive flattened tree ancestor // element of dragGestureContent and our content. constbool isDragGestureContent =
mContent == dragGestureContent ||
mContent ==
dragGestureContent->GetInclusiveFlattenedTreeAncestorElement(); if (!isDragGestureContent) { return NS_OK;
}
}
// When not using `alt`, and clicking on a draggable, but non-editable // element, don't do anything, and let d&d handle the event. // // See bug 48876, bug 388659 and bug 55921 for context here. // // FIXME(emilio): The .Contains(pt) check looks a bit fishy. When would it be // false given we're the event target? If it is needed, why not checking the // actual draggable node rect instead? if (!aMouseEvent->IsAlt() && GetRectRelativeToSelf().Contains(pt)) { for (nsIContent* content = mContent; content;
content = content->GetFlattenedTreeParent()) { if (nsContentUtils::ContentIsDraggable(content) &&
!content->IsEditable()) { return NS_OK;
}
}
}
// If we are in Navigator and the click is in a draggable node, we don't want // to start selection because we don't want to interfere with a potential // drag of said node and steal all its glory. constbool isEditor =
presShell->GetSelectionFlags() == nsISelectionDisplay::DISPLAY_ALL;
// Don't do something if it's middle button down event. constbool isPrimaryButtonDown =
aMouseEvent->mButton == MouseButton::ePrimary;
// Check whether this frame should handle selection events. If not, don't // tell selection the mouse event even occurred. if (!ShouldHandleSelectionMovementEvents()) { return NS_OK;
}
if (isPrimaryButtonDown) { // If the mouse is dragged outside the nearest enclosing scrollable area // while making a selection, the area will be scrolled. To do this, capture // the mouse on the nearest scroll container frame. If there isn't a scroll // container frame, or something else is already capturing the mouse, // there's no reason to capture. if (!PresShell::GetCapturingContent()) {
ScrollContainerFrame* scrollContainerFrame =
nsLayoutUtils::GetNearestScrollContainerFrame( this, nsLayoutUtils::SCROLLABLE_SAME_DOC |
nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN); if (scrollContainerFrame) {
nsIFrame* capturingFrame = scrollContainerFrame;
PresShell::SetCapturingContent(capturingFrame->GetContent(),
CaptureFlags::IgnoreAllowedState);
}
}
}
// XXX This is screwy; it really should use the selection frame, not the // event frame const nsFrameSelection* frameselection = GetConstFrameSelection(); if (!frameselection || frameselection->GetDisplaySelection() ==
nsISelectionController::SELECTION_OFF) { return NS_OK; // nothing to do we cannot affect selection from here
}
#ifdef XP_MACOSX // If Control key is pressed on macOS, it should be treated as right click. // So, don't change selection. if (aMouseEvent->IsControl()) { return NS_OK;
} constbool control = aMouseEvent->IsMeta(); #else constbool control = aMouseEvent->IsControl(); #endif
RefPtr<nsFrameSelection> fc = const_cast<nsFrameSelection*>(frameselection); if (isPrimaryButtonDown && aMouseEvent->mClickCount > 1) { // These methods aren't const but can't actually delete anything, // so no need for AutoWeakFrame.
fc->SetDragState(true); return HandleMultiplePress(aPresContext, aMouseEvent, aEventStatus,
control);
}
if (!offsets.content) { return NS_ERROR_FAILURE;
}
constbool isSecondaryButton =
aMouseEvent->mButton == MouseButton::eSecondary; if (isSecondaryButton &&
!MovingCaretToEventPointAllowedIfSecondaryButtonEvent(
*frameselection, *aMouseEvent, *offsets.content, // When we collapse selection in nsFrameSelection::TakeFocus, // we always collapse selection to the start offset. Therefore, // we can ignore the end offset here. E.g., when an <img> is clicked, // set the primary offset to after it, but the the secondary offset // may be before it, see OffsetsForSingleFrame for the detail.
offsets.StartOffset())) { return NS_OK;
}
if (aMouseEvent->mMessage == eMouseDown &&
aMouseEvent->mButton == MouseButton::eMiddle &&
!offsets.content->IsEditable()) { // However, some users don't like the Chrome compatible behavior of // middle mouse click. They want to keep selection after starting // autoscroll. However, the selection change is important for middle // mouse past. Therefore, we should allow users to take the traditional // behavior back by themselves unless middle click paste is enabled or // autoscrolling is disabled. if (!Preferences::GetBool("middlemouse.paste", false) &&
Preferences::GetBool("general.autoScroll", false) &&
Preferences::GetBool("general.autoscroll.prevent_to_collapse_selection_" "by_middle_mouse_down", false)) { return NS_OK;
}
}
if (isPrimaryButtonDown) { // Let Ctrl/Cmd + left mouse down do table selection instead of drag // initiation.
nsCOMPtr<nsIContent> parentContent;
int32_t contentOffset;
TableSelectionMode target;
nsresult rv = GetDataForTableSelection(
frameselection, presShell, aMouseEvent, getter_AddRefs(parentContent),
&contentOffset, &target); if (NS_SUCCEEDED(rv) && parentContent) {
fc->SetDragState(true); return fc->HandleTableSelection(parentContent, contentOffset, target,
aMouseEvent);
}
}
fc->SetDelayedCaretData(nullptr);
if (isPrimaryButtonDown) { // Check if any part of this frame is selected, and if the user clicked // inside the selected region, and if it's the left button. If so, we delay // starting a new selection since the user may be trying to drag the // selected region to some other app.
// // If there are any details, check to see if the user clicked // within any selected region of the frame. //
for (SelectionDetails* curDetail = details.get(); curDetail;
curDetail = curDetail->mNext.get()) { // // If the user clicked inside a selection, then just // return without doing anything. We will handle placing // the caret later on when the mouse is released. We ignore // the spellcheck, find and url formatting selections. // if (curDetail->mSelectionType != SelectionType::eSpellCheck &&
curDetail->mSelectionType != SelectionType::eFind &&
curDetail->mSelectionType != SelectionType::eURLSecondary &&
curDetail->mSelectionType != SelectionType::eURLStrikeout &&
curDetail->mSelectionType != SelectionType::eHighlight &&
curDetail->mSelectionType != SelectionType::eTargetText &&
curDetail->mStart <= offsets.StartOffset() &&
offsets.EndOffset() <= curDetail->mEnd) {
inSelection = true;
}
}
if (inSelection) {
fc->SetDragState(false);
fc->SetDelayedCaretData(aMouseEvent); return NS_OK;
}
}
fc->SetDragState(true);
}
// Do not touch any nsFrame members after this point without adding // weakFrame checks. const nsFrameSelection::FocusMode focusMode = [&]() { // If "Shift" and "Ctrl" are both pressed, "Shift" is given precedence. This // mimics the old behaviour. constbool isShift =
aMouseEvent->IsShift() && // If Shift + secondary button press shoud open context menu without a // contextmenu event, user wants to open context menu like as a // secondary button press without Shift key.
!(isSecondaryButton &&
StaticPrefs::dom_event_contextmenu_shift_suppresses_event()); if (isShift) { // If clicked in a link when focused content is editable, we should // collapse selection in the link for compatibility with Blink. if (isEditor) { for (Element* element : mContent->InclusiveAncestorsOfType<Element>()) { if (element->IsLink()) { return nsFrameSelection::FocusMode::kCollapseToNewPoint;
}
}
} return nsFrameSelection::FocusMode::kExtendSelection;
}
if (isPrimaryButtonDown && control) { return nsFrameSelection::FocusMode::kMultiRangeSelection;
}
// We don't handle mouse button up if it's middle button. if (isPrimaryButtonDown && offsets.offset != offsets.secondaryOffset) {
fc->MaintainSelection();
}
if (isPrimaryButtonDown && isEditor && !aMouseEvent->IsShift() &&
(offsets.EndOffset() - offsets.StartOffset()) == 1) { // A single node is selected and we aren't extending an existing selection, // which means the user clicked directly on an object (either // `user-select: all` or a non-text node without children). Therefore, // disable selection extension during mouse moves. // XXX This is a bit hacky; shouldn't editor be able to deal with this?
fc->SetDragState(false);
}
if (NS_WARN_IF(aOffsetAtEventPoint < 0)) { returnfalse;
}
constbool contentIsEditable = aContentAtEventPoint.IsEditable(); const TextControlElement* const contentAsTextControl =
TextControlElement::FromNodeOrNull(
aContentAtEventPoint.IsTextControlElement()
? &aContentAtEventPoint
: aContentAtEventPoint.GetClosestNativeAnonymousSubtreeRoot()); const Selection& selection = aFrameSelection.NormalSelection(); constbool selectionIsCollapsed =
selection.AreNormalAndCrossShadowBoundaryRangesCollapsed(); // If right click in a selection range, we should not collapse // selection. if (!selectionIsCollapsed && nsContentUtils::IsPointInSelection(
selection, aContentAtEventPoint, static_cast<uint32_t>(aOffsetAtEventPoint), true/* aAllowCrossShadowBoundary */)) { returnfalse;
} constbool wantToPreventMoveCaret =
StaticPrefs::
ui_mouse_right_click_move_caret_stop_if_in_focused_editable_node() &&
selectionIsCollapsed && (contentIsEditable || contentAsTextControl); constbool wantToPreventCollapseSelection =
StaticPrefs::
ui_mouse_right_click_collapse_selection_stop_if_non_collapsed_selection() &&
!selectionIsCollapsed; if (wantToPreventMoveCaret || wantToPreventCollapseSelection) { // If currently selection is limited in an editing host, we should not // collapse selection nor move caret if the clicked point is in the // ancestor limiter. Otherwise, this mouse click moves focus from the // editing host to different one or blur the editing host. In this case, // we need to update selection because keeping current selection in the // editing host looks like it's not blurred. // FIXME: If the active editing host is the document element, editor // does not set ancestor limiter properly. Fix it in the editor side. if (nsIContent* ancestorLimiter = selection.GetAncestorLimiter()) {
MOZ_ASSERT(ancestorLimiter->IsEditable()); return !aContentAtEventPoint.IsInclusiveDescendantOf(ancestorLimiter);
}
} // If selection is editable and `stop_if_in_focused_editable_node` pref is // set to true, user does not want to move caret to right click place if // clicked in the focused text control element. if (wantToPreventMoveCaret && contentAsTextControl &&
contentAsTextControl == nsFocusManager::GetFocusedElementStatic()) { returnfalse;
} // If currently selection is not limited in an editing host, we should // collapse selection only when this click moves focus to an editing // host because we need to update selection in this case. if (wantToPreventCollapseSelection && !contentIsEditable) { returnfalse;
}
return !StaticPrefs::
ui_mouse_right_click_collapse_selection_stop_if_non_editable_node() || // The user does not want to collapse selection into non-editable // content by a right button click.
contentIsEditable || // Treat clicking in a text control as always clicked on editable // content because we want a hack only for clicking in normal text // nodes which is outside any editing hosts.
contentAsTextControl;
}
nsresult nsIFrame::SelectByTypeAtPoint(const nsPoint& aPoint,
nsSelectionAmount aBeginAmountType,
nsSelectionAmount aEndAmountType,
uint32_t aSelectFlags) { // No point in selecting if selection is turned off if (!ShouldHandleSelectionMovementEvents()) { return NS_OK;
}
if (nsEventStatus_eConsumeNoDefault == *aEventStatus ||
!ShouldHandleSelectionMovementEvents()) { return NS_OK;
}
// Find out whether we're doing line or paragraph selection. // If browser.triple_click_selects_paragraph is true, triple-click selects // paragraph. Otherwise, triple-click selects line, and quadruple-click // selects paragraph (on platforms that support quadruple-click).
nsSelectionAmount beginAmount, endAmount;
WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent(); if (!mouseEvent) { return NS_OK;
}
if (aAmountBack == eSelectWord) { // To avoid selecting the previous word when at start of word, // first move one character forward.
PeekOffsetStruct pos(eSelectCharacter, eDirNext, aStartPos, nsPoint(0, 0),
peekOffsetOptions, eDefaultBehavior, ancestorLimiter);
rv = PeekOffset(&pos); if (NS_SUCCEEDED(rv)) {
baseFrame = pos.mResultFrame;
baseOffset = pos.mContentOffset;
}
}
// Search backward for a boundary.
PeekOffsetStruct startpos(aAmountBack, eDirPrevious, baseOffset,
nsPoint(0, 0), peekOffsetOptions, eDefaultBehavior,
ancestorLimiter);
rv = baseFrame->PeekOffset(&startpos); if (NS_FAILED(rv)) { return rv;
}
// If the backward search stayed within the same frame, search forward from // that position for the end boundary; but if it crossed out to a sibling or // ancestor, start from the original position. if (startpos.mResultFrame == baseFrame) {
baseOffset = startpos.mContentOffset;
} else {
baseFrame = this;
baseOffset = aStartPos;
}
NS_IMETHODIMP nsIFrame::HandleDrag(nsPresContext* aPresContext,
WidgetGUIEvent* aEvent,
nsEventStatus* aEventStatus) {
MOZ_ASSERT(aEvent->mClass == eMouseEventClass, "HandleDrag can only handle mouse event");
NS_ENSURE_ARG_POINTER(aEventStatus);
RefPtr<nsFrameSelection> frameselection = GetFrameSelection(); if (!frameselection) { return NS_OK;
}
bool mouseDown = frameselection->GetDragState(); if (!mouseDown) { return NS_OK;
}
nsIFrame* scrollbar =
nsLayoutUtils::GetClosestFrameOfType(this, LayoutFrameType::Scrollbar); if (!scrollbar && !ShouldHandleSelectionMovementEvents()) { // XXX Do we really need to exclude non-selectable content here? // GetContentOffsetsFromPoint can handle it just fine, although some // other stuff might not like it. return NS_OK;
}
frameselection->StopAutoScrollTimer();
// Check if we are dragging in a table cell
nsCOMPtr<nsIContent> parentContent;
int32_t contentOffset;
TableSelectionMode target;
WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
mozilla::PresShell* presShell = aPresContext->PresShell();
nsresult result;
result = GetDataForTableSelection(frameselection, presShell, mouseEvent,
getter_AddRefs(parentContent),
&contentOffset, &target);
if (nsEventStatus_eConsumeNoDefault != *aEventStatus) { if (!aHandleTableSel) { if (!aOffsets.content || !aFrameSelection->HasDelayedCaretData()) { return NS_ERROR_FAILURE;
}
// We are doing this to simulate what we would have done on HandlePress. // We didn't do it there to give the user an opportunity to drag // the text, but since they didn't drag, we want to place the // caret. // However, we'll use the mouse position from the release, since: // * it's easier // * that's the normal click position to use (although really, in // the normal case, small movements that don't count as a drag // can do selection)
aFrameSelection->SetDragState(true);
if (!selectionOff) {
frameselection = GetFrameSelection(); if (nsEventStatus_eConsumeNoDefault != *aEventStatus && frameselection) { // Check if the frameselection recorded the mouse going down. // If not, the user must have clicked in a part of the selection. // Place the caret before continuing!
// We might be capturing in some other document and the event just happened to // trickle down here. Make sure that document's frame selection is notified. // Note, this may cause the current nsFrame object to be deleted, bug 336592.
RefPtr<nsFrameSelection> frameSelection; if (activeFrame != this &&
activeFrame->ShouldHandleSelectionMovementEvents()) {
frameSelection = activeFrame->GetFrameSelection();
}
// Also check the selection of the capturing content which might be in a // different document. if (!frameSelection && captureContent) { if (Document* doc = captureContent->GetComposedDoc()) {
mozilla::PresShell* capturingPresShell = doc->GetPresShell(); if (capturingPresShell &&
capturingPresShell != PresContext()->GetPresShell()) {
frameSelection = capturingPresShell->FrameSelection();
}
}
}
if (frameSelection) {
AutoWeakFrame wf(this);
frameSelection->SetDragState(false);
frameSelection->StopAutoScrollTimer(); if (wf.IsAlive()) {
ScrollContainerFrame* scrollContainerFrame =
nsLayoutUtils::GetNearestScrollContainerFrame( this, nsLayoutUtils::SCROLLABLE_SAME_DOC |
nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN); if (scrollContainerFrame) { // Perform any additional scrolling needed to maintain CSS snap point // requirements when autoscrolling is over.
scrollContainerFrame->ScrollSnap();
}
}
}
// Do not call any methods of the current object after this point!!! // The object is perhaps dead!
staticbool IsRelevantBlockFrame(const nsIFrame* aFrame) { if (!aFrame->IsBlockOutside() && !aFrame->IsTableCaption()) { returnfalse;
} if (aFrame->GetContent() &&
aFrame->GetContent()->IsInNativeAnonymousSubtree() &&
!aFrame->GetContent()->HasBeenInUAWidget()) { // This helps skipping things like scrollbar parts. returnfalse;
} auto pseudoType = aFrame->Style()->GetPseudoType(); if (PseudoStyle::IsAnonBox(pseudoType)) { // Table cell contents should be considered block boundaries for this // purpose. return pseudoType == PseudoStyleType::MozCellContent;
} returntrue;
}
// Retrieve the content offsets of a frame static FrameContentRange GetRangeForFrame(const nsIFrame* aFrame, bool includeReplaced = false) {
nsIContent* content = aFrame->GetContent(); if (!content) {
NS_WARNING("Frame has no content"); return FrameContentRange(nullptr, -1, -1);
}
LayoutFrameType type = aFrame->Type(); if (type == LayoutFrameType::Text) { auto [offset, offsetEnd] = aFrame->GetOffsets(); return FrameContentRange(content, offset, offsetEnd);
}
while (content->IsRootOfNativeAnonymousSubtree()) {
content = content->GetParent();
}
if (!includeReplaced && aFrame->IsReplaced()) { if (auto* parent = content->GetParent()) { // TODO(emilio): Revise this in presence of Shadow DOM / display: // contents, it's likely that we don't want to just walk the light tree, // and we need to change the representation of FrameContentRange.
Maybe<uint32_t> index = parent->ComputeIndexOf(content);
MOZ_ASSERT(index.isSome()); return FrameContentRange(parent, static_cast<int32_t>(*index), static_cast<int32_t>(*index + 1));
}
}
// The FrameTarget represents the closest frame to a point that can be selected // The frame is the frame represented, frameEdge says whether one end of the // frame is the result (in which case different handling is needed), and // afterFrame says which end is represented if frameEdge is true struct FrameTarget { explicitoperatorbool() const { return !!frame; }
// See function implementation for information static FrameTarget GetSelectionClosestFrame(nsIFrame* aFrame, const nsPoint& aPoint,
uint32_t aFlags);
staticbool SelfIsSelectable(nsIFrame* aFrame, nsIFrame* aParentFrame,
uint32_t aFlags) { // We should not move selection into a native anonymous subtree when handling // selection outside it. if ((aFlags & nsIFrame::IGNORE_NATIVE_ANONYMOUS_SUBTREE) &&
aParentFrame->GetClosestNativeAnonymousSubtreeRoot() !=
aFrame->GetClosestNativeAnonymousSubtreeRoot()) { returnfalse;
} if ((aFlags & nsIFrame::SKIP_HIDDEN) &&
(!aFrame->StyleVisibility()->IsVisible() ||
aFrame->IsHiddenByContentVisibilityOnAnyAncestor(
nsIFrame::IncludeContentVisibility::Hidden))) { returnfalse;
} if (aFrame->IsGeneratedContentFrame()) { returnfalse;
} if (aFrame->Style()->UserSelect() == StyleUserSelect::None) { returnfalse;
} if (aFrame->IsEmpty() &&
(!aFrame->IsTextFrame() || !aFrame->ContentIsEditable())) { // FIXME(emilio): Historically we haven't treated empty frames as // selectable, but also we had special-cases so that editable empty text // frames returned false from IsEmpty(). Sort this out (probably by // removing the IsEmpty() condition altogether). returnfalse;
} returntrue;
}
staticbool FrameContentCanHaveParentSelectionRange(nsIFrame* aFrame) { // If we are only near (not directly over) then don't traverse frames with // independent selection (e.g. text controls, see bug 268497). Note that this // prevents any of the users of this method from entering form controls. // XXX We might want some way to allow using the up-arrow to go into a form // control, but the focus didn't work right anyway; it'd probably be enough // if the left and right arrows could enter textboxes (which I don't believe // they can at the moment) if (aFrame->IsTextInputFrame()) { returnfalse;
} return !aFrame->IsGeneratedContentFrame();
}
// When the cursor needs to be at the beginning of a block, it shouldn't be // before the first child. A click on a block whose first child is a block // should put the cursor in the child. The cursor shouldn't be between the // blocks, because that's not where it's expected. // Note that this method is guaranteed to succeed. static FrameTarget DrillDownToSelectionFrame(nsIFrame* aFrame, bool aEndFrame,
uint32_t aFlags) { if (SelectionDescendToKids(aFrame)) {
nsIFrame* result = nullptr;
nsIFrame* frame = aFrame->PrincipalChildList().FirstChild(); if (!aEndFrame) { while (frame && !SelfIsSelectable(frame, aFrame, aFlags)) {
frame = frame->GetNextSibling();
} if (frame) {
result = frame;
}
} else { // Because the frame tree is singly linked, to find the last frame, // we have to iterate through all the frames // XXX I have a feeling this could be slow for long blocks, although // I can't find any slowdowns while (frame) { if (SelfIsSelectable(frame, aFrame, aFlags)) {
result = frame;
}
frame = frame->GetNextSibling();
}
} if (result) { return DrillDownToSelectionFrame(result, aEndFrame, aFlags);
}
} // If the current frame has no targetable children, target the current frame return FrameTarget{aFrame, true, aEndFrame};
}
// This method finds the closest valid FrameTarget on a given line; if there is // no valid FrameTarget on the line, it returns a null FrameTarget static FrameTarget GetSelectionClosestFrameForLine(
nsBlockFrame* aParent, nsBlockFrame::LineIterator aLine, const nsPoint& aPoint, uint32_t aFlags) { // Account for end of lines (any iterator from the block is valid) if (aLine == aParent->LinesEnd()) { return DrillDownToSelectionFrame(aParent, true, aFlags);
}
nsIFrame* closestFromIStart = nullptr;
nsIFrame* closestFromIEnd = nullptr;
nscoord closestIStart = aLine->IStart(), closestIEnd = aLine->IEnd();
WritingMode wm = aLine->mWritingMode;
LogicalPoint pt(wm, aPoint, aLine->mContainerSize); bool canSkipBr = false; bool lastFrameWasEditable = false; for (nsIFrame* frame : aLine->ChildFrames()) { // Skip brFrames. Can only skip if the line contains at least // one selectable and non-empty frame before. Also, avoid skipping brs if // the previous thing had a different editableness than us, since then we // may end up not being able to select after it if the br is the last thing // on the line. if (!SelfIsSelectable(frame, aParent, aFlags) ||
(canSkipBr && frame->IsBrFrame() &&
lastFrameWasEditable == frame->GetContent()->IsEditable())) { continue;
}
canSkipBr = true;
lastFrameWasEditable =
frame->GetContent() && frame->GetContent()->IsEditable();
LogicalRect frameRect =
LogicalRect(wm, frame->GetRect(), aLine->mContainerSize); if (pt.I(wm) >= frameRect.IStart(wm)) { if (pt.I(wm) < frameRect.IEnd(wm)) { return GetSelectionClosestFrameForChild(frame, aPoint, aFlags);
} if (frameRect.IEnd(wm) >= closestIStart) {
closestFromIStart = frame;
closestIStart = frameRect.IEnd(wm);
}
} else { if (frameRect.IStart(wm) <= closestIEnd) {
closestFromIEnd = frame;
closestIEnd = frameRect.IStart(wm);
}
}
} if (!closestFromIStart && !closestFromIEnd) { // We should only get here if there are no selectable frames on a line // XXX Do we need more elaborate handling here? return FrameTarget();
} if (closestFromIStart &&
(!closestFromIEnd ||
(abs(pt.I(wm) - closestIStart) <= abs(pt.I(wm) - closestIEnd)))) { return GetSelectionClosestFrameForChild(closestFromIStart, aPoint, aFlags);
} return GetSelectionClosestFrameForChild(closestFromIEnd, aPoint, aFlags);
}
// This method is for the special handling we do for block frames; they're // special because they represent paragraphs and because they are organized // into lines, which have bounds that are not stored elsewhere in the // frame tree. Returns a null FrameTarget for frames which are not // blocks or blocks with no lines except editable one. static FrameTarget GetSelectionClosestFrameForBlock(nsIFrame* aFrame, const nsPoint& aPoint,
uint32_t aFlags) {
nsBlockFrame* bf = do_QueryFrame(aFrame); if (!bf) { return FrameTarget();
}
// This code searches for the correct line
nsBlockFrame::LineIterator end = bf->LinesEnd();
nsBlockFrame::LineIterator curLine = bf->LinesBegin();
nsBlockFrame::LineIterator closestLine = end;
if (curLine != end) { // Convert aPoint into a LogicalPoint in the writing-mode of this block
WritingMode wm = curLine->mWritingMode;
LogicalPoint pt(wm, aPoint, curLine->mContainerSize); do { // Check to see if our point lies within the line's block-direction bounds
nscoord BCoord = pt.B(wm) - curLine->BStart();
nscoord BSize = curLine->BSize(); if (BCoord >= 0 && BCoord < BSize) {
closestLine = curLine; break; // We found the line; stop looking
} if (BCoord < 0) { break;
}
++curLine;
} while (curLine != end);
if (closestLine == end) {
nsBlockFrame::LineIterator prevLine = curLine.prev();
nsBlockFrame::LineIterator nextLine = curLine; // Avoid empty lines while (nextLine != end && nextLine->IsEmpty()) {
++nextLine;
} while (prevLine != end && prevLine->IsEmpty()) {
--prevLine;
}
// This hidden pref dictates whether a point above or below all lines // comes up with a line or the beginning or end of the frame; 0 on // Windows, 1 on other platforms by default at the writing of this code
int32_t dragOutOfFrame =
Preferences::GetInt("browser.drag_out_of_frame_style");
if (prevLine == end) { if (dragOutOfFrame == 1 || nextLine == end) { return DrillDownToSelectionFrame(aFrame, false, aFlags);
}
closestLine = nextLine;
} elseif (nextLine == end) { if (dragOutOfFrame == 1) { return DrillDownToSelectionFrame(aFrame, true, aFlags);
}
closestLine = prevLine;
} else { // Figure out which line is closer if (pt.B(wm) - prevLine->BEnd() < nextLine->BStart() - pt.B(wm)) {
closestLine = prevLine;
} else {
closestLine = nextLine;
}
}
}
}
do { if (auto target =
GetSelectionClosestFrameForLine(bf, closestLine, aPoint, aFlags)) { return target;
}
++closestLine;
} while (closestLine != end);
// Fall back to just targeting the last targetable place return DrillDownToSelectionFrame(aFrame, true, aFlags);
}
// Use frame edge for grid, flex, table, and non-editable images. Choose the // edge based on the point position past the frame rect. If past the middle, // caret should be at end, otherwise at start. This behavior matches Blink. // // TODO(emilio): Can we use this code path for all other frames? We only get // there when we didn't find selectable children... Editable images need _not_ // to use the frame edge tho, see below. staticbool UseFrameEdge(nsIFrame* aFrame) { if (aFrame->IsFlexOrGridContainer() || aFrame->IsTableFrame()) { returntrue;
} // FIXME(bug 713387): The text frame check here shouldn't be needed. if (aFrame->IsReplaced() && !aFrame->IsTextFrame() &&
!aFrame->GetContent()->IsEditable()) { // Editable replaced elements are a special-case because editing relies // on clicking on an editable image selecting it, for it to show resizers. returntrue;
} returnfalse;
}
// GetSelectionClosestFrame is the helper function that calculates the closest // frame to the given point. // It doesn't completely account for offset styles, so needs to be used in // restricted environments. // Cannot handle overlapping frames correctly, so it should receive the output // of GetFrameForPoint // Guaranteed to return a valid FrameTarget. // aPoint is relative to aFrame. static FrameTarget GetSelectionClosestFrame(nsIFrame* aFrame, const nsPoint& aPoint,
uint32_t aFlags) { // Handle blocks; if the frame isn't a block, the method fails if (auto target = GetSelectionClosestFrameForBlock(aFrame, aPoint, aFlags)) { return target;
}
if (nsIFrame* kid = aFrame->PrincipalChildList().FirstChild()) { // Go through all the child frames to find the closest one
nsIFrame::FrameWithDistance closest = {nullptr, nscoord_MAX, nscoord_MAX}; for (; kid; kid = kid->GetNextSibling()) { if (!SelfIsSelectable(kid, aFrame, aFlags)) { continue;
}
kid->FindCloserFrameForSelection(aPoint, &closest);
} if (closest.mFrame) { if (closest.mFrame->IsInSVGTextSubtree()) { return FrameTarget{closest.mFrame, false, false};
} return GetSelectionClosestFrameForChild(closest.mFrame, aPoint, aFlags);
}
}
static nsIFrame::ContentOffsets OffsetsForSingleFrame(nsIFrame* aFrame, const nsPoint& aPoint) {
nsIFrame::ContentOffsets offsets;
FrameContentRange range = GetRangeForFrame(aFrame);
offsets.content = range.content; // If there are continuations (meaning it's not one rectangle), this is the // best this function can do if (aFrame->GetNextContinuation() || aFrame->GetPrevContinuation()) {
offsets.offset = range.start;
offsets.secondaryOffset = range.end;
offsets.associate = CaretAssociationHint::After; return offsets;
}
// Figure out whether the offsets should be over, after, or before the frame
nsRect rect(nsPoint(0, 0), aFrame->GetSize());
static nsIFrame* AdjustFrameForSelectionStyles(nsIFrame* aFrame) {
nsIFrame* adjustedFrame = aFrame; for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) { // These are the conditions that make all children not able to handle // a cursor. auto userSelect = frame->Style()->UserSelect(); if (userSelect != StyleUserSelect::Auto &&
userSelect != StyleUserSelect::All) { break;
} if (userSelect == StyleUserSelect::All ||
frame->IsGeneratedContentFrame()) {
adjustedFrame = frame;
}
} return adjustedFrame;
}
nsIFrame::ContentOffsets nsIFrame::GetContentOffsetsFromPoint( const nsPoint& aPoint, uint32_t aFlags) {
nsIFrame* adjustedFrame; if (aFlags & IGNORE_SELECTION_STYLE) {
adjustedFrame = this;
} else { // This section of code deals with special selection styles. Note that // -moz-all exists, even though it doesn't need to be explicitly handled. // // The offset is forced not to end up in generated content; content offsets // cannot represent content outside of the document's content tree.
// `user-select: all` needs special handling, because clicking on it should // lead to the whole frame being selected. if (adjustedFrame->Style()->UserSelect() == StyleUserSelect::All) {
nsPoint adjustedPoint = aPoint + GetOffsetTo(adjustedFrame); return OffsetsForSingleFrame(adjustedFrame, adjustedPoint);
}
// For other cases, try to find a closest frame starting from the parent of // the unselectable frame if (adjustedFrame != this) {
adjustedFrame = adjustedFrame->GetParent();
}
}
// XXX should I add some kind of offset standardization? // consider <b>xxxxx</b><i>zzzzz</i>; should any click between the last // x and first z put the cursor in the same logical position in addition // to the same visual position?
}
StyleImageRendering nsIFrame::UsedImageRendering() const {
ComputedStyle* style; if (IsCanvasFrame()) { // XXXdholbert Maybe we should use FindCanvasBackground here (instead of // FindBackground), since we're inside an IsCanvasFrame check? Though then // we'd also have to copypaste or abstract-away the multi-part root-frame // lookup that the canvas-flavored API requires.
style = nsCSSRendering::FindBackground(this);
} else {
style = Style();
} return style->StyleVisibility()->mImageRendering;
}
// The touch-action CSS property applies to: all elements except: non-replaced // inline elements, table rows, row groups, table columns, and column groups.
StyleTouchAction nsIFrame::UsedTouchAction() const { if (IsLineParticipant()) { return StyleTouchAction::AUTO;
} auto& disp = *StyleDisplay(); if (disp.IsInternalTableStyleExceptCell()) { return StyleTouchAction::AUTO;
} return disp.mTouchAction;
}
nsIFrame::Cursor nsIFrame::GetCursor(const nsPoint&) {
StyleCursorKind kind = StyleUI()->Cursor().keyword; if (kind == StyleCursorKind::Auto) { // If this is editable, I-beam cursor is better for most elements.
kind = (mContent && mContent->IsEditable()) ? StyleCursorKind::Text
: StyleCursorKind::Default;
} if (kind == StyleCursorKind::Text && GetWritingMode().IsVertical()) { // Per CSS UI spec, UA may treat value 'text' as // 'vertical-text' for vertical text.
kind = StyleCursorKind::VerticalText;
}
/* virtual */ void nsIFrame::MarkIntrinsicISizesDirty() { // If we're a flex item, clear our flex-item-specific cached measurements // (which likely depended on our now-stale intrinsic isize). if (IsFlexItem()) {
nsFlexContainerFrame::MarkCachedFlexMeasurementsDirty(this);
}
if (IsGridItem()) {
nsGridContainerFrame::MarkCachedGridMeasurementsDirty(this);
}
if (HasAnyStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT)) {
nsFontInflationData::MarkFontInflationDataTextDirty(this);
}
}
void nsIFrame::MarkSubtreeDirty() { if (HasAnyStateBits(NS_FRAME_IS_DIRTY)) { return;
} // Unconditionally mark given frame dirty.
AddStateBits(NS_FRAME_IS_DIRTY);
// Mark all descendants dirty, unless: // - Already dirty. // - TableColGroup
AutoTArray<nsIFrame*, 32> stack; for (constauto& childLists : ChildLists()) { for (nsIFrame* kid : childLists.mList) {
stack.AppendElement(kid);
}
} while (!stack.IsEmpty()) {
nsIFrame* f = stack.PopLastElement(); if (f->HasAnyStateBits(NS_FRAME_IS_DIRTY) || f->IsTableColGroupFrame()) { continue;
}
f->AddStateBits(NS_FRAME_IS_DIRTY);
for (constauto& childLists : f->ChildLists()) { for (nsIFrame* kid : childLists.mList) {
stack.AppendElement(kid);
}
}
}
}
/* virtual */ void nsIFrame::AddInlineMinISize(const IntrinsicSizeInput& aInput,
InlineMinISizeData* aData) { // Note: we are one of the children that mPercentageBasisForChildren was // prepared for (i.e. our parent frame prepares the percentage basis for us, // not for our own children). Hence it's fine that we're resolving our // percentages sizes against this basis in IntrinsicForContainer().
nscoord isize = nsLayoutUtils::IntrinsicForContainer(
aInput.mContext, this, IntrinsicISizeType::MinISize,
aInput.mPercentageBasisForChildren);
aData->DefaultAddInlineMinISize(this, isize);
}
/* virtual */ void nsIFrame::AddInlinePrefISize(const IntrinsicSizeInput& aInput,
nsIFrame::InlinePrefISizeData* aData) { // Note: we are one of the children that mPercentageBasisForChildren was // prepared for (i.e. our parent frame prepares the percentage basis for us, // not for our own children). Hence it's fine that we're resolving our // percentages sizes against this basis in IntrinsicForContainer().
nscoord isize = nsLayoutUtils::IntrinsicForContainer(
aInput.mContext, this, IntrinsicISizeType::PrefISize,
aInput.mPercentageBasisForChildren);
aData->DefaultAddInlinePrefISize(isize);
}
void nsIFrame::InlineMinISizeData::DefaultAddInlineMinISize(nsIFrame* aFrame,
nscoord aISize, bool aAllowBreak) { auto parent = aFrame->GetParent();
MOZ_ASSERT(parent, "Must have a parent if we get here!"); constbool mayBreak = aAllowBreak && !aFrame->CanContinueTextRun() &&
!parent->Style()->ShouldSuppressLineBreak() &&
parent->StyleText()->WhiteSpaceCanWrap(parent); if (mayBreak) {
OptionallyBreak();
}
mTrailingWhitespace = 0;
mSkipWhitespace = false;
mCurrentLine += aISize;
mAtStartOfLine = false; if (mayBreak) {
OptionallyBreak();
}
}
void nsIFrame::InlineMinISizeData::OptionallyBreak(nscoord aHyphenWidth) { // If we can fit more content into a smaller width by staying on this // line (because we're still at a negative offset due to negative // text-indent or negative margin), don't break. Otherwise, do the // same as ForceBreak. it doesn't really matter when we accumulate // floats. if (mCurrentLine + aHyphenWidth < 0 || mAtStartOfLine) { return;
}
mCurrentLine += aHyphenWidth;
ForceBreak();
}
void nsIFrame::InlinePrefISizeData::ForceBreak(UsedClear aClearType) { // If this force break is not clearing any float, we can leave all the // floats to the next force break. if (!mFloats.IsEmpty() && aClearType != UsedClear::None) { // Preferred isize accumulated for floats that have already // been cleared past
nscoord floatsDone = 0; // Preferred isize accumulated for floats that have not yet // been cleared past
nscoord floatsCurLeft = 0, floatsCurRight = 0;
if (aClearType == UsedClear::Both) {
mFloats.Clear();
} else { // If the break type does not clear all floats, it means there may // be some floats whose isize should contribute to the intrinsic // isize of the next line. The code here scans the current mFloats // and keeps floats which are not cleared by this break. Note that // floats may be cleared directly or indirectly. See below.
nsTArray<FloatInfo> newFloats;
MOZ_ASSERT(
aClearType == UsedClear::Left || aClearType == UsedClear::Right, "Other values should have been handled in other branches");
UsedFloat clearFloatType =
aClearType == UsedClear::Left ? UsedFloat::Left : UsedFloat::Right; // Iterate the array in reverse so that we can stop when there are // no longer any floats we need to keep. See below. for (FloatInfo& floatInfo : Reversed(mFloats)) { const nsStyleDisplay* floatDisp = floatInfo.Frame()->StyleDisplay(); auto cbWM = floatInfo.Frame()->GetParent()->GetWritingMode(); if (floatDisp->UsedFloat(cbWM) != clearFloatType) {
newFloats.AppendElement(floatInfo);
} else { // This is a float on the side that this break directly clears // which means we're not keeping it in mFloats. However, if // this float clears floats on the opposite side (via a value // of either 'both' or one of 'left'/'right'), any remaining // (earlier) floats on that side would be indirectly cleared // as well. Thus, we should break out of this loop and stop // considering earlier floats to be kept in mFloats.
UsedClear clearType = floatDisp->UsedClear(cbWM); if (clearType != aClearType && clearType != UsedClear::None) { break;
}
}
}
newFloats.Reverse();
mFloats = std::move(newFloats);
}
}
/* virtual */
IntrinsicSize nsIFrame::GetIntrinsicSize() { // Defaults to no intrinsic size. return IntrinsicSize();
}
AspectRatio nsIFrame::GetAspectRatio() const { // Per spec, 'aspect-ratio' property applies to all elements except inline // boxes and internal ruby or table boxes. // https://drafts.csswg.org/css-sizing-4/#aspect-ratio // For those frame types that don't support aspect-ratio, they must not have // the natural ratio, so this early return is fine. if (!SupportsAspectRatio()) { return AspectRatio();
}
const StyleAspectRatio& ar = StylePosition()->mAspectRatio; constbool hasRatio = ar.HasRatio(); // If aspect-ratio is zero or infinite, it's a degenerate ratio and behaves // as auto. // https://drafts.csswg.org/css-sizing-4/#valdef-aspect-ratio-ratio // Non-auto. Return the preferred aspect ratio from the aspect-ratio style. if (hasRatio && !ar.auto_) { if (auto ratio = ar.ratio.AsRatio().ToLayoutRatio(UseBoxSizing::Yes)) { return ratio;
}
}
if (auto intrinsicRatio = GetIntrinsicRatio()) { return intrinsicRatio;
}
if (hasRatio) { // This returns a 0 ratio for degenerate rations, like the auto case below. return ar.ratio.AsRatio().ToLayoutRatio(UseBoxSizing::No);
}
constauto& aspectRatio = aSizeOverrides.mAspectRatio
? *aSizeOverrides.mAspectRatio
: GetAspectRatio(); constauto styleISize =
aSizeOverrides.mStyleISize
? AnchorResolvedSizeHelper::Overridden(*aSizeOverrides.mStyleISize)
: stylePos->ISize(aWM, anchorResolutionParams); // For bsize, we consider overrides *and then* we resolve 'stretch' to a // nscoord value, for convenience (so that we can assume that either // isAutoBSize is true, or styleBSize is of type LengthPercentage()). constauto styleBSize = [&] { auto styleBSizeConsideringOverrides =
aSizeOverrides.mStyleBSize
? AnchorResolvedSizeHelper::Overridden(*aSizeOverrides.mStyleBSize)
: stylePos->BSize(aWM, anchorResolutionParams); if (styleBSizeConsideringOverrides->BehavesLikeStretchOnBlockAxis() &&
aCBSize.BSize(aWM) != NS_UNCONSTRAINEDSIZE) { // We've got a 'stretch' BSize; resolve it to a length:
nscoord stretchBSize = nsLayoutUtils::ComputeStretchBSize(
aCBSize.BSize(aWM), aMargin.BSize(aWM), aBorderPadding.BSize(aWM),
stylePos->mBoxSizing); // Note(dshin): This allocates. return AnchorResolvedSizeHelper::LengthPercentage(
LengthPercentage::FromAppUnits(stretchBSize));
} return styleBSizeConsideringOverrides;
}();
auto parentFrame = GetParent(); auto alignCB = parentFrame; bool isGridItem = IsGridItem(); constbool isSubgrid = IsSubgrid(); if (parentFrame && parentFrame->IsTableWrapperFrame() && IsTableFrame()) { // An inner table frame is sized as a grid item if its table wrapper is, // because they actually have the same CB (the wrapper's CB). // @see ReflowInput::InitCBReflowInput auto tableWrapper = GetParent(); auto grandParent = tableWrapper->GetParent();
isGridItem = grandParent->IsGridContainerFrame() &&
!tableWrapper->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW); if (isGridItem) { // When resolving justify/align-self below, we want to use the grid // container's justify/align-items value and WritingMode.
alignCB = grandParent;
}
}
// flexItemMainAxis is set if this frame is a flex item in a modern flexbox // layout. It indicates which logical axis (in this frame's own WM) // corresponds to its flex container's main axis.
Maybe<LogicalAxis> flexItemMainAxis; if (IsFlexItem() && !parentFrame->HasAnyStateBits(
NS_STATE_FLEX_IS_EMULATING_LEGACY_WEBKIT_BOX)) {
flexItemMainAxis = Some(nsFlexContainerFrame::IsItemInlineAxisMainAxis(this)
? LogicalAxis::Inline
: LogicalAxis::Block);
}
MOZ_ASSERT(isAutoBSize || styleBSize->IsLengthPercentage(), "We should have resolved away any non-'auto'-like flavors " "of styleBSize into a LengthPercentage. (If this fails, we " "might run afoul of some AsLengthPercentage() call below.)");
// Per https://drafts.csswg.org/css-grid/#subgrid-box-alignment, if we are // subgridded in the inline-axis, ignore our style inline-size, and stretch to // fill the CB. constbool shouldComputeISize = !isAutoISize && !isSubgriddedInInlineAxis; if (shouldComputeISize) { auto iSizeResult =
ComputeISizeValue(aSizingInput.mRenderingContext, aWM, aCBSize,
boxSizingAdjust, boxSizingToMarginEdgeISize,
*styleISize, *styleBSize, aspectRatio, aFlags);
result.ISize(aWM) = iSizeResult.mISize;
aspectRatioUsage = iSizeResult.mAspectRatioUsage;
} elseif (MOZ_UNLIKELY(isGridItem) && !IsTrueOverflowContainer()) { // 'auto' inline-size for grid-level box - fill the CB for 'stretch' / // 'normal' and clamp it to the CB if requested: bool isStretchAligned = false; bool mayUseAspectRatio = aspectRatio && !isAutoBSize; if (!aFlags.contains(ComputeSizeFlag::ShrinkWrap) &&
!StyleMargin()->HasInlineAxisAuto(aWM, anchorResolutionParams) &&
!alignCB->IsMasonry(aWM, LogicalAxis::Inline)) { auto inlineAxisAlignment = stylePos->UsedSelfAlignment(
aWM, LogicalAxis::Inline, alignCB->GetWritingMode(),
alignCB->Style());
isStretchAligned = inlineAxisAlignment == StyleAlignFlags::STRETCH ||
(inlineAxisAlignment == StyleAlignFlags::NORMAL &&
!mayUseAspectRatio);
}
// Apply the preferred aspect ratio for alignments other than *stretch* and // *normal without aspect ratio*. // The spec says all other values should size the items as fit-content, and // the intrinsic size should respect the preferred aspect ratio, so we also // apply aspect ratio for all other values. // https://drafts.csswg.org/css-grid/#grid-item-sizing if (!isStretchAligned && mayUseAspectRatio) {
result.ISize(aWM) = ComputeISizeValueFromAspectRatio(
aWM, aCBSize, boxSizingAdjust, styleBSize->AsLengthPercentage(),
aspectRatio);
aspectRatioUsage = AspectRatioUsage::ToComputeISize;
}
if (isStretchAligned ||
aFlags.contains(ComputeSizeFlag::IClampMarginBoxMinSize)) { auto iSizeToFillCB =
std::max(nscoord(0), aCBSize.ISize(aWM) - aBorderPadding.ISize(aWM) -
aMargin.ISize(aWM)); if (isStretchAligned || result.ISize(aWM) > iSizeToFillCB) {
result.ISize(aWM) = iSizeToFillCB;
}
}
} elseif (aspectRatio && !isAutoBSize) { // Note: if both the inline size and the block size are auto, the block axis // is the ratio-dependent axis by default. That means we only need to // transfer the resolved inline size via aspect-ratio to block axis later in // this method, but not the other way around. // // In this branch, we transfer the non-auto block size via aspect-ration to // inline axis.
result.ISize(aWM) = ComputeISizeValueFromAspectRatio(
aWM, aCBSize, boxSizingAdjust, styleBSize->AsLengthPercentage(),
aspectRatio);
aspectRatioUsage = AspectRatioUsage::ToComputeISize;
}
// Calculate and apply transferred min & max size contraints. // https://drafts.csswg.org/css-sizing-4/#aspect-ratio-size-transfers // // Note: The basic principle is that sizing constraints transfer through the // aspect-ratio to the other side to preserve the aspect ratio to the extent // that they can without violating any sizes specified explicitly on that // affected axis. // // FIXME: The spec words may not be correct, so we may have to update this // tentative solution once this spec issue gets resolved. Here, we clamp the // flex base size by the transferred min and max sizes, and don't include // the transferred min & max sizes into its used min & max sizes. So this // lets us match other browsers' current behaviors. // https://github.com/w3c/csswg-drafts/issues/6071 // // Note: This may make more sense if we clamp the flex base size in // FlexItem::ResolveFlexBaseSizeFromAspectRatio(). However, the result should // be identical. FlexItem::ResolveFlexBaseSizeFromAspectRatio() only handles // the case of the definite cross size, and the definite cross size is clamped // by the min & max cross sizes below in this function. This means its flex // base size has been clamped by the transferred min & max size already after // generating the flex items. So here we make the code more general for both // definite cross size and indefinite cross size. constbool isDefiniteISize = styleISize->IsLengthPercentage(); constauto minBSizeCoord = stylePos->MinBSize(aWM, anchorResolutionParams); constauto maxBSizeCoord = stylePos->MaxBSize(aWM, anchorResolutionParams); constbool isAutoMinBSize =
nsLayoutUtils::IsAutoBSize(*minBSizeCoord, aCBSize.BSize(aWM)); constbool isAutoMaxBSize =
nsLayoutUtils::IsAutoBSize(*maxBSizeCoord, aCBSize.BSize(aWM)); if (aspectRatio && !isDefiniteISize) { // Note: the spec mentions that // 1. This transferred minimum is capped by any definite preferred or // maximum size in the destination axis. // 2. This transferred maximum is floored by any definite preferred or // minimum size in the destination axis. // // https://drafts.csswg.org/css-sizing-4/#aspect-ratio-size-transfers // // The spec requires us to clamp these by the specified size (it calls it // the preferred size). However, we actually don't need to worry about that, // because we are here only if the inline size is indefinite. // // We do not need to clamp the transferred minimum and maximum as long as we // always apply the transferred min/max size before the explicit min/max // size; the result will be identical. const nscoord transferredMinISize =
isAutoMinBSize ? 0
: ComputeISizeValueFromAspectRatio(
aWM, aCBSize, boxSizingAdjust,
minBSizeCoord->AsLengthPercentage(), aspectRatio); const nscoord transferredMaxISize =
isAutoMaxBSize ? nscoord_MAX
: ComputeISizeValueFromAspectRatio(
aWM, aCBSize, boxSizingAdjust,
maxBSizeCoord->AsLengthPercentage(), aspectRatio);
// Flex items ignore their min & max sizing properties in their flex // container's main-axis. (Those properties get applied later in the flexbox // algorithm.) constbool isFlexItemInlineAxisMainAxis =
flexItemMainAxis && *flexItemMainAxis == LogicalAxis::Inline; // Grid items that are subgridded in inline-axis also ignore their min & max // sizing properties in that axis. constbool shouldIgnoreMinMaxISize =
isFlexItemInlineAxisMainAxis || isSubgriddedInInlineAxis; constauto maxISizeCoord = stylePos->MaxISize(aWM, anchorResolutionParams);
nscoord maxISize = NS_UNCONSTRAINEDSIZE; if (!maxISizeCoord->IsNone() && !shouldIgnoreMinMaxISize) {
maxISize =
ComputeISizeValue(aSizingInput.mRenderingContext, aWM, aCBSize,
boxSizingAdjust, boxSizingToMarginEdgeISize,
*maxISizeCoord, *styleBSize, aspectRatio, aFlags)
.mISize;
result.ISize(aWM) = std::min(maxISize, result.ISize(aWM));
}
const nscoord bSizeAsPercentageBasis = ComputeBSizeValueAsPercentageBasis(
*styleBSize, *minBSizeCoord, *maxBSizeCoord, aCBSize.BSize(aWM),
boxSizingAdjust.BSize(aWM)); const IntrinsicSizeInput input(
aSizingInput.mRenderingContext,
Some(aCBSize.ConvertTo(GetWritingMode(), aWM)),
Some(LogicalSize(aWM, NS_UNCONSTRAINEDSIZE, bSizeAsPercentageBasis)
.ConvertTo(GetWritingMode(), aWM))); constauto minISizeCoord = stylePos->MinISize(aWM, anchorResolutionParams);
nscoord minISize; if (!minISizeCoord->IsAuto() && !shouldIgnoreMinMaxISize) {
minISize =
ComputeISizeValue(aSizingInput.mRenderingContext, aWM, aCBSize,
boxSizingAdjust, boxSizingToMarginEdgeISize,
*minISizeCoord, *styleBSize, aspectRatio, aFlags)
.mISize;
} elseif (MOZ_UNLIKELY(
aFlags.contains(ComputeSizeFlag::IApplyAutoMinSize))) { // This implements "Implied Minimum Size of Grid Items". // https://drafts.csswg.org/css-grid/#min-size-auto
minISize = std::min(maxISize, GetMinISize(input)); if (styleISize->IsLengthPercentage()) {
minISize = std::min(minISize, result.ISize(aWM));
} elseif (aFlags.contains(ComputeSizeFlag::IClampMarginBoxMinSize)) { // "if the grid item spans only grid tracks that have a fixed max track // sizing function, its automatic minimum size in that dimension is // further clamped to less than or equal to the size necessary to fit // its margin box within the resulting grid area (flooring at zero)" // https://drafts.csswg.org/css-grid/#min-size-auto auto maxMinISize =
std::max(nscoord(0), aCBSize.ISize(aWM) - aBorderPadding.ISize(aWM) -
aMargin.ISize(aWM));
minISize = std::min(minISize, maxMinISize);
}
} elseif (aspectRatioUsage == AspectRatioUsage::ToComputeISize &&
ShouldApplyAutomaticMinimumOnInlineAxis(
aWM, disp->IsScrollableOverflow(), anchorResolutionParams,
stylePos)) { // This means we successfully applied aspect-ratio and now need to check // if we need to apply the automatic content-based minimum size: // https://drafts.csswg.org/css-sizing-4/#aspect-ratio-minimum
MOZ_ASSERT(!HasReplacedSizing(), "aspect-ratio minimums should not apply to replaced elements"); // The inline size computed by aspect-ratio shouldn't less than the // min-content size, which should be capped by its maximum inline size.
minISize = std::min(GetMinISize(input), maxISize);
} else { // Treat "min-width: auto" as 0. // NOTE: Technically, "auto" is supposed to behave like "min-content" on // flex items. However, we don't need to worry about that here, because // flex items' min-sizes are intentionally ignored until the flex // container explicitly considers them during space distribution.
minISize = 0;
}
result.ISize(aWM) = std::max(minISize, result.ISize(aWM));
// Compute block-axis size // (but not if we have auto bsize -- then, we'll just stick with the bsize // that we already calculated in the initial ComputeAutoSize() call. However, // if we have a valid preferred aspect ratio, we still have to compute the // block size because aspect ratio affects the intrinsic content size.) constbool isSubgriddedInBlockAxis =
isSubgrid && static_cast<nsGridContainerFrame*>(this)->IsRowSubgrid();
// Per https://drafts.csswg.org/css-grid/#subgrid-box-alignment, if we are // subgridded in the block-axis, ignore our style block-size, and stretch to // fill the CB. constbool shouldComputeBSize = !isAutoBSize && !isSubgriddedInBlockAxis; if (shouldComputeBSize) {
result.BSize(aWM) = nsLayoutUtils::ComputeBSizeValue(
aCBSize.BSize(aWM), boxSizingAdjust.BSize(aWM),
styleBSize->AsLengthPercentage());
} elseif (MOZ_UNLIKELY(isGridItem) && styleBSize->IsAuto() &&
!aFlags.contains(ComputeSizeFlag::IsGridMeasuringReflow) &&
!IsTrueOverflowContainer() &&
!alignCB->IsMasonry(aWM, LogicalAxis::Block)) { auto cbSize = aCBSize.BSize(aWM); if (cbSize != NS_UNCONSTRAINEDSIZE) { // 'auto' block-size for grid-level box - fill the CB for 'stretch' / // 'normal' and clamp it to the CB if requested: bool isStretchAligned = false; bool mayUseAspectRatio =
aspectRatio && result.ISize(aWM) != NS_UNCONSTRAINEDSIZE; if (!StyleMargin()->HasBlockAxisAuto(aWM, anchorResolutionParams)) { auto blockAxisAlignment = stylePos->UsedSelfAlignment(
aWM, LogicalAxis::Block, alignCB->GetWritingMode(),
alignCB->Style());
isStretchAligned = blockAxisAlignment == StyleAlignFlags::STRETCH ||
(blockAxisAlignment == StyleAlignFlags::NORMAL &&
!mayUseAspectRatio);
}
// Apply the preferred aspect ratio for alignments other than *stretch* // and *normal without aspect ratio*. // The spec says all other values should size the items as fit-content, // and the intrinsic size should respect the preferred aspect ratio, so // we also apply aspect ratio for all other values. // https://drafts.csswg.org/css-grid/#grid-item-sizing if (!isStretchAligned && mayUseAspectRatio) {
result.BSize(aWM) = aspectRatio.ComputeRatioDependentSize(
LogicalAxis::Block, aWM, result.ISize(aWM), boxSizingAdjust);
MOZ_ASSERT(aspectRatioUsage == AspectRatioUsage::None);
aspectRatioUsage = AspectRatioUsage::ToComputeBSize;
}
if (isStretchAligned ||
aFlags.contains(ComputeSizeFlag::BClampMarginBoxMinSize)) { auto bSizeToFillCB = nsLayoutUtils::ComputeStretchContentBoxBSize(
cbSize, aMargin.BSize(aWM), aBorderPadding.BSize(aWM)); if (isStretchAligned || (result.BSize(aWM) != NS_UNCONSTRAINEDSIZE &&
result.BSize(aWM) > bSizeToFillCB)) {
result.BSize(aWM) = bSizeToFillCB;
}
}
}
} elseif (aspectRatio) { // If both inline and block dimensions are auto, the block axis is the // ratio-dependent axis by default. // If we have a super large inline size, aspect-ratio should still be // applied (so aspectRatioUsage flag is set as expected). That's why we // apply aspect-ratio unconditionally for auto block size here.
result.BSize(aWM) = aspectRatio.ComputeRatioDependentSize(
LogicalAxis::Block, aWM, result.ISize(aWM), boxSizingAdjust);
MOZ_ASSERT(aspectRatioUsage == AspectRatioUsage::None);
aspectRatioUsage = AspectRatioUsage::ToComputeBSize;
}
if (result.BSize(aWM) != NS_UNCONSTRAINEDSIZE) { // Flex items ignore their min & max sizing properties in their flex // container's main-axis. (Those properties get applied later in the flexbox // algorithm.) constbool isFlexItemBlockAxisMainAxis =
flexItemMainAxis && *flexItemMainAxis == LogicalAxis::Block; // Grid items that are subgridded in block-axis also ignore their min & max // sizing properties in that axis. constbool shouldIgnoreMinMaxBSize =
isFlexItemBlockAxisMainAxis || isSubgriddedInBlockAxis; if (!isAutoMaxBSize && !shouldIgnoreMinMaxBSize) {
nscoord maxBSize = nsLayoutUtils::ComputeBSizeValueHandlingStretch(
aCBSize.BSize(aWM), aMargin.BSize(aWM), aBorderPadding.BSize(aWM),
boxSizingAdjust.BSize(aWM), *maxBSizeCoord);
result.BSize(aWM) = std::min(maxBSize, result.BSize(aWM));
}
// TODO(dholbert): This is a temporary hack, to be fixed up in bug 1933604. // We don't know have aMargin or aBorderPadding args available, // so we use these dummy zero-valued variables as placeholders in // our call to ComputeBSizeValueHandlingStretch. (This might mean we // end up resolving 'stretch' to something slighlty-too-large for the // purposes of this call, if there's actually nonzero margin/border/padding). const nscoord dummyMargin = 0; const nscoord dummyBorderPadding = 0;
bool nsIFrame::IsAbsolutelyPositionedWithDefiniteContainingBlock() const { // TODO(dshin, Bug 1927861): Even if an absolute container should have a // definite size, in a continuation context, the full extent of the containing // block is not known. return MOZ_UNLIKELY(IsAbsolutelyPositioned()) && !GetPrevInFlow();
}
LogicalSize nsIFrame::ComputeAbsolutePosAutoSize( const SizeComputationInput& aSizingInput, WritingMode aWM, const mozilla::LogicalSize& aCBSize, nscoord aAvailableISize, const mozilla::LogicalSize& aMargin, const mozilla::LogicalSize& aBorderPadding, const StyleSizeOverrides& aSizeOverrides, const ComputeSizeFlags& aFlags) {
MOZ_ASSERT(IsAbsolutelyPositionedWithDefiniteContainingBlock(), "Asking for absolute auto size when not absolute"); // Ideally, this is an assertion, but the containing block could just be // really big.
NS_WARNING_ASSERTION(aCBSize.ISize(aWM) != NS_UNCONSTRAINEDSIZE &&
aCBSize.BSize(aWM) != NS_UNCONSTRAINEDSIZE, "Absolute containing block size not definite?");
LogicalSize result(aWM, static_cast<nscoord>(0xdeadbeef),
NS_UNCONSTRAINEDSIZE);
constauto* stylePos = StylePosition(); constauto anchorResolutionParams =
AnchorPosOffsetResolutionParams::UseCBFrameSize(
AnchorPosResolutionParams::From(&aSizingInput)); constauto& styleISize =
aSizeOverrides.mStyleISize
? AnchorResolvedSizeHelper::Overridden(*aSizeOverrides.mStyleISize)
: stylePos->ISize(aWM, anchorResolutionParams.mBaseParams); constauto& styleBSize =
aSizeOverrides.mStyleBSize
? AnchorResolvedSizeHelper::Overridden(*aSizeOverrides.mStyleBSize)
: stylePos->BSize(aWM, anchorResolutionParams.mBaseParams); constauto iStartOffsetIsAuto =
stylePos
->GetAnchorResolvedInset(LogicalSide::IStart, aWM,
anchorResolutionParams)
->IsAuto(); constauto iEndOffsetIsAuto =
stylePos
->GetAnchorResolvedInset(LogicalSide::IEnd, aWM,
anchorResolutionParams)
->IsAuto(); constauto bStartOffsetIsAuto =
stylePos
->GetAnchorResolvedInset(LogicalSide::BStart, aWM,
anchorResolutionParams)
->IsAuto(); constauto bEndOffsetIsAuto =
stylePos
->GetAnchorResolvedInset(LogicalSide::BEnd, aWM,
anchorResolutionParams)
->IsAuto(); constauto boxSizingAdjust = stylePos->mBoxSizing == StyleBoxSizing::BorderBox
? aBorderPadding
: LogicalSize(aWM); auto shouldStretch = [](StyleAlignFlags aAlignment, const nsIFrame* aFrame, bool aStartIsAuto, bool aEndIsAuto) { if (aStartIsAuto || aEndIsAuto) { // Note(dshin, bug 1930427): This is not part of the current spec [1]; // however, no one implements the new inset behaviour [2], and the old // behaviour [3] ends up computing the static size if both or one inset is // auto. // // [1]: https://drafts.csswg.org/css-position-3/#abspos-auto-size // [2]: https://drafts.csswg.org/css-position-3/#resolving-insets // [3]: https://drafts.csswg.org/css-position-3/#abspos-old returnfalse;
} // Don't care about flag bits for auto-sizing.
aAlignment &= ~StyleAlignFlags::FLAG_BITS;
if (aAlignment == StyleAlignFlags::STRETCH) { returntrue;
}
if (aAlignment == StyleAlignFlags::NORMAL) { // Some replaced elements behave as semi-replaced elements - we want them // to stretch (See bug 1740580). return !aFrame->HasReplacedSizing() && !aFrame->IsTableWrapperFrame();
}
returnfalse;
};
// i.e. Absolute containing block
// Self alignment properties translate `auto` to normal for this purpose. // https://drafts.csswg.org/css-align-3/#valdef-justify-self-auto
nsContainerFrame* contFrame = static_cast<nsContainerFrame*>(this); const StylePositionArea posArea = stylePos->mPositionArea; constauto containerWM = GetParent()->GetWritingMode(); auto containerAxis = [&](LogicalAxis aSubjectAxis) { return aWM.ConvertAxisTo(aSubjectAxis, containerWM);
}; constauto inlineSelfAlign =
contFrame->CSSAlignmentForAbsPosChildWithinContainingBlock(
aSizingInput, containerAxis(LogicalAxis::Inline), posArea, aCBSize); constauto blockSelfAlign =
contFrame->CSSAlignmentForAbsPosChildWithinContainingBlock(
aSizingInput, containerAxis(LogicalAxis::Block), posArea, aCBSize); constauto iShouldStretch = shouldStretch(
inlineSelfAlign, this, iStartOffsetIsAuto, iEndOffsetIsAuto); constauto bShouldStretch =
shouldStretch(blockSelfAlign, this, bStartOffsetIsAuto, bEndOffsetIsAuto); constauto iSizeIsAuto = styleISize->IsAuto(); // Note(dshin, bug 1789477): `auto` in the context of abs-element uses // stretch-fit sizing, given specific alignment conditions [1]. Effectively, // `auto` is `stretch`. `nsLayoutUtils::IsAutoBSize` is not the right tool // here, since the mapping is explicit, and it's incorrect to e.g. map // `fit-content` to `stretch`. // `-moz-available` behaves like `auto` in general, so map the same way. // When Bug 567039 brings `-moz-available` into alignment with `stretch`, this // special check can be removed. TODO(dshin): we're probably duplicating the // `stretch` logic here, since `stretch` is `stretch-fit` sizing [2]. // // [1]: https://drafts.csswg.org/css-position/#abspos-auto-size // [2]: https://drafts.csswg.org/css-sizing-4/#valdef-width-stretch constauto bSizeIsAuto = styleBSize->IsAuto() || styleBSize->IsMozAvailable(); if (bSizeIsAuto && bShouldStretch) {
result.BSize(aWM) = nsLayoutUtils::ComputeStretchContentBoxBSize(
aCBSize.BSize(aWM), aMargin.BSize(aWM), aBorderPadding.BSize(aWM));
} if (iSizeIsAuto) { if (iShouldStretch) { // inline-size to make our margin-box fill the containing block:
result.ISize(aWM) = nsLayoutUtils::ComputeStretchContentBoxISize(
aCBSize.ISize(aWM), aMargin.ISize(aWM), aBorderPadding.ISize(aWM));
} else { // inline-size to make our margin-box fill aAvailableISize:
nscoord availBased = nsLayoutUtils::ComputeStretchContentBoxISize(
aAvailableISize, aMargin.ISize(aWM), aBorderPadding.ISize(aWM));
constauto& aspectRatio = aSizeOverrides.mAspectRatio
? *aSizeOverrides.mAspectRatio
: GetAspectRatio(); if (aspectRatio) { auto aspectRatioUsage = AspectRatioUsage::None; if (iSizeIsAuto != bSizeIsAuto) { // Auto axis is dependent. if (iSizeIsAuto) {
aspectRatioUsage = AspectRatioUsage::ToComputeBSize;
} else {
aspectRatioUsage = AspectRatioUsage::ToComputeISize;
}
} elseif (iSizeIsAuto) { // Both axes are `auto`. if (iShouldStretch != bShouldStretch) { // If an axis has stretch, that behaves like a definite size.
aspectRatioUsage = iShouldStretch ? AspectRatioUsage::ToComputeBSize
: AspectRatioUsage::ToComputeISize;
} elseif (!iShouldStretch) { // If one axis has `auto` inset, that is the ratio dependent axis, // otherwise the block axis is. constbool inlineInsetHasAuto = iStartOffsetIsAuto || iEndOffsetIsAuto; constbool blockInsetHasAuto = bStartOffsetIsAuto || bEndOffsetIsAuto;
aspectRatioUsage = inlineInsetHasAuto && !blockInsetHasAuto
? AspectRatioUsage::ToComputeISize
: AspectRatioUsage::ToComputeBSize;
}
}
nscoord nsIFrame::ShrinkISizeToFit(const IntrinsicSizeInput& aInput,
nscoord aISizeInCB,
ComputeSizeFlags aFlags) { // If we're a container for font size inflation, then shrink // wrapping inside of us should not apply font size inflation.
AutoMaybeDisableFontInflation an(this);
nscoord result;
nscoord minISize = GetMinISize(aInput); if (minISize > aISizeInCB) { constbool clamp = aFlags.contains(ComputeSizeFlag::IClampMarginBoxMinSize);
result = MOZ_UNLIKELY(clamp) ? aISizeInCB : minISize;
} else {
nscoord prefISize = GetPrefISize(aInput); if (prefISize > aISizeInCB) {
result = aISizeInCB;
} else {
result = prefISize;
}
} return result;
}
nscoord nsIFrame::IntrinsicISizeFromInline(const IntrinsicSizeInput& aInput,
IntrinsicISizeType aType) {
MOZ_ASSERT(!IsContainerForFontSizeInflation(), "Should not be a container for font size inflation!");
// If 'this' is a container for font size inflation, then shrink // wrapping inside of it should not apply font size inflation.
AutoMaybeDisableFontInflation an(this); // If we have an aspect-ratio and a definite block size, we should use them to // resolve the sizes with intrinsic keywords. // https://github.com/w3c/csswg-drafts/issues/5032
Maybe<nscoord> iSizeFromAspectRatio = [&]() -> Maybe<nscoord> { if (aSize == ExtremumLength::MozAvailable ||
aSize == ExtremumLength::Stretch) { return Nothing();
} if (!aAspectRatio) { return Nothing();
} if (nsLayoutUtils::IsAutoBSize(aStyleBSize, aCBSize.BSize(aWM))) { return Nothing();
}
// Helper used below to resolve aStyleBSize if it's 'stretch' or an alias. // XXXdholbert Really we should be resolving 'stretch' and its aliases // sooner; see bug 2000035. auto ResolveStretchBSize = [&]() {
MOZ_ASSERT(aStyleBSize.BehavesLikeStretchOnBlockAxis(), "Only call me for 'stretch'-like BSizes");
MOZ_ASSERT(aCBSize.BSize(aWM) != NS_UNCONSTRAINEDSIZE, "If aStyleBSize is stretch-like, then unconstrained " "aCBSize.BSize should make us return via the IsAutoBSize " "check above");
// NOTE: the borderPadding and margin variables might be zero-filled // instead of having the true values, if those values haven't been // stashed in our property-table yet (e.g. if we're in the midst of // setting up a ReflowInput for our first reflow). So ideally, we should // be resolving 'stretch' **in our callers** rather than here, if those // callers have more up-to-date resolved margin/border/padding values. // We'll still make a best-effort attempt to resolve 'stretch' here, // though, for the benefit of callers that might not have handled it, to // be sure we don't abort in aStyleBSize.AsLengthPercentage(). Ultimately // this all can be removed when we fix bug 2000035. constauto borderPadding = GetLogicalUsedBorderAndPadding(aWM); constauto margin = GetLogicalUsedMargin(aWM);
nscoord stretchBSize = nsLayoutUtils::ComputeStretchBSize(
aCBSize.BSize(aWM), margin.BStartEnd(aWM),
borderPadding.BStartEnd(aWM), StylePosition()->mBoxSizing); return LengthPercentage::FromAppUnits(stretchBSize);
};
constauto* stylePos = StylePosition(); constauto anchorResolutionParams = AnchorPosResolutionParams::From(this); const nscoord bSize = ComputeBSizeValueAsPercentageBasis(
aStyleBSize, *stylePos->MinBSize(aWM, anchorResolutionParams),
*stylePos->MaxBSize(aWM, anchorResolutionParams), aCBSize.BSize(aWM),
aContentEdgeToBoxSizing.BSize(aWM)); const IntrinsicSizeInput input(
aRenderingContext, Some(aCBSize.ConvertTo(GetWritingMode(), aWM)),
Some(LogicalSize(aWM, NS_UNCONSTRAINEDSIZE, bSize)
.ConvertTo(GetWritingMode(), aWM)));
nscoord result; switch (aSize) { case ExtremumLength::MaxContent:
result =
iSizeFromAspectRatio ? *iSizeFromAspectRatio : GetPrefISize(input);
NS_ASSERTION(result >= 0, "inline-size less than zero"); return {result, iSizeFromAspectRatio ? AspectRatioUsage::ToComputeISize
: AspectRatioUsage::None}; case ExtremumLength::MinContent:
result =
iSizeFromAspectRatio ? *iSizeFromAspectRatio : GetMinISize(input);
NS_ASSERTION(result >= 0, "inline-size less than zero"); if (MOZ_UNLIKELY(
aFlags.contains(ComputeSizeFlag::IClampMarginBoxMinSize))) {
result = std::min(GetAvailableISize(), result);
} return {result, iSizeFromAspectRatio ? AspectRatioUsage::ToComputeISize
: AspectRatioUsage::None}; case ExtremumLength::FitContentFunction: case ExtremumLength::FitContent: {
nscoord pref = NS_UNCONSTRAINEDSIZE;
nscoord min = 0; if (iSizeFromAspectRatio) { // The min-content and max-content size are identical and equal to the // size computed from the block size and the aspect ratio.
pref = min = *iSizeFromAspectRatio;
} else {
pref = GetPrefISize(input);
min = GetMinISize(input);
}
const nscoord fill = aAvailableISizeOverride ? *aAvailableISizeOverride
: GetAvailableISize(); if (MOZ_UNLIKELY(
aFlags.contains(ComputeSizeFlag::IClampMarginBoxMinSize))) {
min = std::min(min, fill);
}
result = std::max(min, std::min(pref, fill));
NS_ASSERTION(result >= 0, "inline-size less than zero"); return {result};
} case ExtremumLength::MozAvailable: case ExtremumLength::Stretch: return {GetAvailableISize()};
}
MOZ_ASSERT_UNREACHABLE("Unknown extremum length?"); return {};
}
nscoord nsIFrame::ComputeISizeValue(const WritingMode aWM, const LogicalSize& aCBSize, const LogicalSize& aContentEdgeToBoxSizing, const LengthPercentage& aSize) const {
LAYOUT_WARN_IF_FALSE(
aCBSize.ISize(aWM) != NS_UNCONSTRAINEDSIZE, "have unconstrained inline-size; this should only result from " "very large sizes, not attempts at intrinsic inline-size " "calculation");
NS_ASSERTION(aCBSize.ISize(aWM) >= 0, "inline-size less than zero");
nscoord result = aSize.Resolve(aCBSize.ISize(aWM)); // The result of a calc() expression might be less than 0; we // should clamp at runtime (below). (Percentages and coords that // are less than 0 have already been dropped by the parser.)
result -= aContentEdgeToBoxSizing.ISize(aWM); return std::max(0, result);
}
// Clear bits that were used in ReflowInput::InitResizeFlags (see // comment there for why we can't clear it there).
SetHasBSizeChange(false);
SetHasPaddingChange(false);
// Notify the percent bsize observer if there is a percent bsize. // The observer may be able to initiate another reflow with a computed // bsize. This happens in the case where a table cell has no computed // bsize but can fabricate one when the cell bsize is known. if (aReflowInput && aReflowInput->mPercentBSizeObserver && !GetPrevInFlow()) { constauto bsize = aReflowInput->mStylePosition->BSize(
aReflowInput->GetWritingMode(),
AnchorPosResolutionParams::From(aReflowInput)); if (bsize->HasPercent()) {
aReflowInput->mPercentBSizeObserver->NotifyPercentBSize(*aReflowInput);
}
}
aPresContext->ReflowedFrame();
}
/* virtual */ bool nsIFrame::CanContinueTextRun() const { // By default, a frame will *not* allow a text run to be continued // through it. returnfalse;
}
bool nsIFrame::IsHiddenByContentVisibilityOfInFlowParentForLayout() const { constauto* parent = GetInFlowParent(); // The anonymous children owned by parent are important for properly sizing // their parents. return parent && parent->HidesContentForLayout() &&
!(parent->HasAnyStateBits(NS_FRAME_OWNS_ANON_BOXES) &&
Style()->IsAnonBox());
}
nsIFrame* nsIFrame::GetClosestContentVisibilityAncestor( const EnumSet<IncludeContentVisibility>& aInclude) const { auto* parent = GetInFlowParent(); bool isAnonymousBlock = Style()->IsAnonBox() && parent &&
parent->HasAnyStateBits(NS_FRAME_OWNS_ANON_BOXES); for (nsIFrame* cur = parent; cur; cur = cur->GetInFlowParent()) { if (!isAnonymousBlock && cur->HidesContent(aInclude)) { return cur;
}
// Anonymous boxes are not hidden by the content-visibility of their first // non-anonymous ancestor, but can be hidden by ancestors further up the // tree.
isAnonymousBlock = false;
}
if (!oldRelevancy ||
aRelevancyToUpdate.contains(ContentRelevancyReason::Selected)) {
setRelevancyValue(ContentRelevancyReason::Selected,
HasSelectionInSubtree());
}
// If the proximity to the viewport has not been determined yet, // and neither the element nor its contents are focused or selected, // we should wait for the determination of the proximity. Otherwise, // there might be a redundant contentvisibilityautostatechange event. // See https://github.com/w3c/csswg-drafts/issues/9803 bool isProximityToViewportDetermined =
oldRelevancy ? true : element->GetVisibleForContentVisibility().isSome(); if (!isProximityToViewportDetermined && newRelevancy.isEmpty()) { returnfalse;
}
// Per // https://drafts.csswg.org/css-contain/#content-visibility-auto-state-changed // "This event is dispatched by posting a task at the time when the state // change occurs." auto asyncDispatcher =
MakeRefPtr<AsyncEventDispatcher>(element, event.forget());
DebugOnly<nsresult> rv = asyncDispatcher->PostDOMEvent();
NS_ASSERTION(NS_SUCCEEDED(rv), "AsyncEventDispatcher failed to dispatch"); returntrue;
}
nsresult nsIFrame::CharacterDataChanged(const CharacterDataChangeInfo&) {
MOZ_ASSERT_UNREACHABLE("should only be called for text frames"); return NS_OK;
}
NS_ASSERTION(aThis->PresContext() == aOther->PresContext(), "GetOffsetTo called on frames in different documents");
nsPoint offset(0, 0); const nsIFrame* f; for (f = aThis; f != aOther && f; f = f->GetParent()) {
offset += (f->*PositionGetter)();
}
if (f != aOther) { // Looks like aOther wasn't an ancestor of |this|. So now we have // the root-frame-relative position of |this| in |offset|. Convert back // to the coordinates of aOther while (aOther) {
offset -= (aOther->*PositionGetter)();
aOther = aOther->GetParent();
}
}
nsPoint nsIFrame::GetOffsetToCrossDoc(const nsIFrame* aOther, const int32_t aAPD) const {
MOZ_ASSERT(aOther, "Must have frame for destination coordinate system!");
MOZ_DIAGNOSTIC_ASSERT(
PresContext()->GetRootPresContext() ==
aOther->PresContext()->GetRootPresContext(), "trying to get the offset between frames in different document " "hierarchies?");
const nsIFrame* root = nullptr; // offset will hold the final offset // docOffset holds the currently accumulated offset at the current APD, it // will be converted and added to offset when the current APD changes.
nsPoint offset(0, 0), docOffset(0, 0); const nsIFrame* f = this;
int32_t currAPD = PresContext()->AppUnitsPerDevPixel(); while (f && f != aOther) {
docOffset += f->GetPosition();
nsIFrame* parent = f->GetParent(); if (parent) {
f = parent;
} else {
nsPoint newOffset(0, 0);
root = f;
f = nsLayoutUtils::GetCrossDocParentFrameInProcess(f, &newOffset);
int32_t newAPD = f ? f->PresContext()->AppUnitsPerDevPixel() : 0; if (!f || newAPD != currAPD) { // Convert docOffset to the right APD and add it to offset.
offset += docOffset.ScaleToOtherAppUnits(currAPD, aAPD);
docOffset.x = docOffset.y = 0;
}
currAPD = newAPD;
docOffset += newOffset;
}
} if (f == aOther) {
offset += docOffset.ScaleToOtherAppUnits(currAPD, aAPD);
} else { // Looks like aOther wasn't an ancestor of |this|. So now we have // the root-document-relative position of |this| in |offset|. Subtract the // root-document-relative position of |aOther| from |offset|. // This call won't try to recurse again because root is an ancestor of // aOther.
nsPoint negOffset = aOther->GetOffsetToCrossDoc(root, aAPD);
offset -= negOffset;
}
Matrix4x4Flagged nsIFrame::GetTransformMatrix(ViewportType aViewportType,
RelativeTo aStopAtAncestor,
nsIFrame** aOutAncestor,
uint32_t aFlags) const {
MOZ_ASSERT(aOutAncestor, "Need a place to put the ancestor!");
/* If we're transformed, we want to hand back the combination *transform/translatematrixthatwillapplyourcurrenttransform,then *shiftustoourparent.
*/ constbool isTransformed = IsTransformed(); const nsIFrame* zoomedContentRoot = nullptr; if (aStopAtAncestor.mViewportType == ViewportType::Visual) {
zoomedContentRoot = ViewportUtils::IsZoomedContentRoot(this); if (zoomedContentRoot) {
MOZ_ASSERT(aViewportType != ViewportType::Visual);
}
}
/* Compute the delta to the parent, which we need because we are converting *coordinatestoourparent.
*/ if (isTransformed) { // Note: this converts from Matrix4x4 to Matrix4x4Flagged.
result = nsDisplayTransform::GetResultingTransformMatrix( this, nsPoint(), scaleFactor,
nsDisplayTransform::INCLUDE_PERSPECTIVE);
}
// The offset from a zoomed content root to its parent (e.g. from // a canvas frame to a scroll frame) is in layout coordinates, so // apply it before applying any layout-to-visual transform.
*aOutAncestor = GetParent();
nsPoint delta = GetPosition(); /* Combine the raw transform with a translation to our parent. */
result.PostTranslate(NSAppUnitsToFloatPixels(delta.x, scaleFactor),
NSAppUnitsToFloatPixels(delta.y, scaleFactor), 0.0f);
if (zoomedContentRoot) {
Matrix4x4Flagged layoutToVisual; if (aFlags & nsIFrame::IN_CSS_UNITS) {
layoutToVisual = ViewportUtils::GetVisualToLayoutTransform(
zoomedContentRoot->GetContent())
.Inverse()
.ToUnknownMatrix();
} else {
layoutToVisual =
ViewportUtils::GetVisualToLayoutTransform<LayoutDevicePixel>(
zoomedContentRoot->GetContent())
.Inverse()
.ToUnknownMatrix();
}
result = result * layoutToVisual;
}
return result;
}
// We are not transformed, so the returned transform is just going to be a // translation up to whatever ancestor we decide to stop at.
/* Otherwise, we're not transformed. In that case, we'll walk up the frame *treeuntilweeitherhittherootframeorsomethingthatmaybe *transformed.We'llthenchangecoordinatesintothatframe,sincewe're *guaranteedthatnothingin-betweencanbetransformed.First,however, *wehavetochecktoseeifwehaveaparent.Ifnot,we'llsetthe *outparamtonull(indicatingthatthere'snothingleft)andwillhandback *theidentitymatrix.
*/ if (!*aOutAncestor) { return Matrix4x4Flagged();
}
/* Keep iterating while the frame can't possibly be transformed. */ const nsIFrame* current = this; auto shouldStopAt = [](const nsIFrame* aCurrent, RelativeTo& aStopAtAncestor,
nsIFrame* aOutAncestor, uint32_t aFlags) { return aOutAncestor->IsTransformed() ||
((aStopAtAncestor.mViewportType == ViewportType::Visual) &&
ViewportUtils::IsZoomedContentRoot(aOutAncestor)) ||
((aFlags & STOP_AT_STACKING_CONTEXT_AND_DISPLAY_PORT) &&
(aOutAncestor->IsStackingContext() ||
DisplayPortUtils::FrameHasDisplayPort(aOutAncestor, aCurrent)));
};
// We run the GetOffsetToCrossDoc code here as an optimization, instead of // walking the parent chain here and then asking GetOffsetToCrossDoc to walk // the same parent chain and compute the offset. const int32_t finalAPD = PresContext()->AppUnitsPerDevPixel(); // offset accumulates the offset at finalAPD.
nsPoint offset = GetPosition();
int32_t currAPD = (*aOutAncestor)->PresContext()->AppUnitsPerDevPixel(); // docOffset accumulates the current offset at currAPD, and then flushes to // offset at finalAPD when the APD changes or we finish.
nsPoint docOffset = crossdocOffset;
MOZ_ASSERT(crossdocOffset == nsPoint(0, 0) || !GetParent());
// No need to schedule a paint for an external document since they aren't // painted directly. if (!pres || (pres->Document() && pres->Document()->IsResourceDoc())) { return;
} if (!pres->GetContainerWeak()) {
NS_WARNING("Shouldn't call SchedulePaint in a detached pres context"); return;
}
pres->PresShell()->SchedulePaint();
if (aType == nsIFrame::PAINT_DEFAULT) {
aDisplayRoot->AddStateBits(NS_FRAME_UPDATE_LAYER_TREE);
}
}
if (aRebuildDisplayItems) {
aFrame->MarkNeedsDisplayItemRebuild();
}
SVGObserverUtils::InvalidateDirectRenderingObservers(aFrame); bool needsSchedulePaint = false; if (nsLayoutUtils::IsPopup(aFrame)) {
needsSchedulePaint = true;
} else {
nsIFrame* parent = nsLayoutUtils::GetCrossDocParentFrameInProcess(aFrame); while (parent &&
!parent->HasAnyStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT)) { if (aHasDisplayItem && !parent->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
parent->AddStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT);
}
SVGObserverUtils::InvalidateDirectRenderingObservers(parent);
// If we're inside a popup, then we need to make sure that we // call schedule paint so that the NS_FRAME_UPDATE_LAYER_TREE // flag gets added to the popup display root frame. if (nsLayoutUtils::IsPopup(parent)) {
needsSchedulePaint = true; break;
}
parent = nsLayoutUtils::GetCrossDocParentFrameInProcess(parent);
} if (!parent) {
needsSchedulePaint = true;
}
} if (!aHasDisplayItem) { return;
} if (needsSchedulePaint) {
nsIFrame* displayRoot = nsLayoutUtils::GetDisplayRootFrame(aFrame);
SchedulePaintInternal(displayRoot, aFrame);
} if (aFrame->HasAnyStateBits(NS_FRAME_HAS_INVALID_RECT)) {
aFrame->RemoveProperty(nsIFrame::InvalidationRect());
aFrame->RemoveStateBits(NS_FRAME_HAS_INVALID_RECT);
}
}
for (constauto& childList : CrossDocChildLists()) { for (nsIFrame* child : childList.mList) { // Don't explicitly rebuild display items for our descendants, // since we should be marked and it implicitly includes all // descendants.
child->InvalidateFrameSubtree(false);
}
}
}
void nsIFrame::ClearInvalidationStateBits() { if (HasAnyStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT)) { for (constauto& childList : CrossDocChildLists()) { for (nsIFrame* child : childList.mList) {
child->ClearInvalidationStateBits();
}
}
}
nsRect* rect; if (HasAnyStateBits(NS_FRAME_HAS_INVALID_RECT)) {
rect = GetProperty(InvalidationRect());
MOZ_ASSERT(rect);
} else { if (alreadyInvalid) { return;
}
rect = new nsRect();
AddProperty(InvalidationRect(), rect);
AddStateBits(NS_FRAME_HAS_INVALID_RECT);
}
*rect = rect->Union(aRect);
}
bool nsIFrame::IsInvalid(nsRect& aRect) { if (!HasAnyStateBits(NS_FRAME_NEEDS_PAINT)) { returnfalse;
}
if (HasAnyStateBits(NS_FRAME_HAS_INVALID_RECT)) {
nsRect* rect = GetProperty(InvalidationRect());
NS_ASSERTION(
rect, "Must have an invalid rect if NS_FRAME_HAS_INVALID_RECT is set!");
aRect = *rect;
} else {
aRect.SetEmpty();
} returntrue;
}
void nsIFrame::SchedulePaint(PaintType aType, bool aFrameChanged) { if (PresShell()->IsPaintingSuppressed()) { // We can't have any display items yet, and when we unsuppress we will // invalidate the root frame. return;
}
nsIFrame* displayRoot = nsLayoutUtils::GetDisplayRootFrame(this);
InvalidateRenderingObservers(displayRoot, this, aFrameChanged);
SchedulePaintInternal(displayRoot, this, aType);
}
// Check if frame supports WebRender's async update if ((aFlags & UPDATE_IS_ASYNC) &&
WebRenderUserData::SupportsAsyncUpdate(this)) { // WebRender does not use layer, then return nullptr. return;
}
if (aFrameDamageRect && aFrameDamageRect->IsEmpty()) { return;
}
// In the bug 930056, dialer app startup but not shown on the // screen because sometimes we don't have any retainned data // for remote type displayitem and thus Repaint event is not // triggered. So, always invalidate in this case.
DisplayItemType displayItemKey = aDisplayItemKey; if (aDisplayItemKey == DisplayItemType::TYPE_REMOTE) {
displayItemKey = DisplayItemType::TYPE_ZERO;
}
if (aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) { // For SVG frames, we only need to account for filters. // TODO: We could also take account of clipPath and mask to reduce the // ink overflow, but that's not essential. if (aFrame->StyleEffects()->HasFilters()) {
aFrame->SetOrUpdateDeletableProperty(nsIFrame::PreEffectsBBoxProperty(),
r);
r = SVGUtils::GetPostFilterInkOverflowRect(aFrame, aOverflowRect);
} return r;
}
// border-image-outset. // We need to include border-image-outset because it can cause the // border image to be drawn beyond the border box.
// (1) It's important we not check whether there's a border-image // since the style hint for a change in border image doesn't cause // reflow, and that's probably more important than optimizing the // overflow areas for the silly case of border-image-outset without // border-image // (2) It's important that we not check whether the border-image // is actually loaded, since that would require us to reflow when // the image loads. const nsStyleBorder* styleBorder = aFrame->StyleBorder();
nsMargin outsetMargin = styleBorder->GetImageOutset();
// Note that we don't remove the outlineInnerRect if a frame loses outline // style. That would require an extra property lookup for every frame, // or a new frame state bit to track whether a property had been stored, // or something like that. It's not worth doing that here. At most it's // only one heap-allocated rect per frame and it will be cleaned up when // the frame dies.
if (SVGIntegrationUtils::UsingOverflowAffectingEffects(aFrame)) {
aFrame->SetOrUpdateDeletableProperty(nsIFrame::PreEffectsBBoxProperty(), r);
r = SVGIntegrationUtils::ComputePostEffectsInkOverflowRect(aFrame, r);
}
nsRect nsIFrame::GetOverflowRect(OverflowType aType) const { // Note that in some cases the overflow area might not have been // updated (yet) to reflect any outline set on the frame or the area // of child frames. That's OK because any reflow that updates these // areas will invalidate the appropriate area, so any (mis)uses of // this method will be fixed up.
if (mOverflow.mType == OverflowStorageType::Large) { // there is an overflow rect, and it's not stored as deltas but as // a separately-allocated rect return GetOverflowAreasProperty()->Overflow(aType);
}
OverflowAreas nsIFrame::GetOverflowAreas() const { if (mOverflow.mType == OverflowStorageType::Large) { // there is an overflow rect, and it's not stored as deltas but as // a separately-allocated rect return *GetOverflowAreasProperty();
}
OverflowAreas nsIFrame::GetActualAndNormalOverflowAreasRelativeToParent() const { if (MOZ_LIKELY(!IsRelativelyOrStickyPositioned())) { return GetOverflowAreasRelativeToParent();
}
const OverflowAreas overflows = GetOverflowAreas();
OverflowAreas actualAndNormalOverflows = overflows + GetNormalPosition(); if (IsRelativelyPositioned()) {
actualAndNormalOverflows.UnionWith(overflows + GetPosition());
} else { // For sticky positioned elements, we only use the normal position for the // scrollable overflow. This avoids circular dependencies between sticky // positioned elements and their scroll container. (The scroll position and // the scroll container's size impact the sticky position, so we don't want // the sticky position to impact them.)
MOZ_ASSERT(IsStickyPositioned());
actualAndNormalOverflows.UnionWith(
OverflowAreas(overflows.InkOverflow() + GetPosition(), nsRect()));
} return actualAndNormalOverflows;
}
if (!ComputeCustomOverflow(overflowAreas)) { // If updating overflow wasn't supported by this frame, then it should // have scheduled any necessary reflows. We can return false to say nothing // changed, and wait for reflow to correct it. returnfalse;
}
UnionChildOverflow(overflowAreas);
if (FinishAndStoreOverflow(overflowAreas, GetSize())) { returntrue;
}
// Frames that combine their 3d transform with their ancestors // only compute a pre-transform overflow rect, and then contribute // to the normal overflow rect of the preserve-3d root. Always return // true here so that we propagate changes up to the root for final // calculation. return Combines3DTransformWithAncestors();
}
// Return true if this form control element's preferred size property (but not // percentage max size property) contains a percentage value that should be // resolved against zero when calculating its min-content contribution in the // corresponding axis. // // For proper replaced elements, the percentage value in both their max size // property or preferred size property should be resolved against zero. This is // handled in IsPercentageResolvedAgainstZero(). inlinestaticbool FormControlShrinksForPercentSize(const nsIFrame* aFrame) { if (!aFrame->IsReplaced()) { // Quick test to reject most frames. returnfalse;
}
switch (aFrame->Type()) { case LayoutFrameType::Progress: case LayoutFrameType::Range: case LayoutFrameType::TextInput: case LayoutFrameType::ColorControl: case LayoutFrameType::ComboboxControl: case LayoutFrameType::ListControl: case LayoutFrameType::CheckboxRadio: case LayoutFrameType::FileControl: case LayoutFrameType::ImageControl: returntrue; default: // True buttons (<button>, backed by block/grid/flex frame) and most // button-flavored <inputs> (those backed by InputButtonControlFrame) // don't have this shrinking behavior. But color-inputs and comboboxes do; // and both of those derive from ButtonControlFrame. So: we can't easily // use do_QueryFrame to differentiate the buttons-that-do vs. the // buttons-that-don't. So we explicitly list the buttons-that-do by // LayoutFrameType above, and the others fall into this catch-all. returnfalse;
}
}
// Summary of the Cyclic-Percentage Intrinsic Size Contribution Rules: // // Element Type | Replaced | Non-replaced // Contribution Type | min-content max-content | min-content max-content // --------------------------------------------------------------------------- // min size | zero zero | zero zero // max & preferred size | zero initial | initial initial // // https://drafts.csswg.org/css-sizing-3/#cyclic-percentage-contribution bool nsIFrame::IsPercentageResolvedAgainstZero(const LengthPercentage& aSize,
SizeProperty aProperty) const { // Early return to avoid calling the virtual function, IsFrameOfType(). if (aProperty == SizeProperty::MinSize) { returntrue;
}
static nsIFrame* GetNearestBlockContainer(nsIFrame* frame) { while (!frame->IsBlockContainer()) {
frame = frame->GetParent();
NS_ASSERTION(
frame, "How come we got to the root frame without seeing a containing block?");
} return frame;
}
bool nsIFrame::IsBlockContainer() const { // The block wrappers we use to wrap blocks inside inlines aren't // described in the CSS spec. We need to make them not be containing // blocks. // Since the parent of such a block is either a normal block or // another such pseudo, this shouldn't cause anything bad to happen. // Also the anonymous blocks inside table cells are not containing blocks. // // If we ever start skipping table row groups from being containing blocks, // you need to remove the StickyScrollContainer hack referencing bug 1421660. // Table rows are not containing blocks either return !IsLineParticipant() && !IsBlockWrapper() && !IsTableRowFrame();
}
// Keep this in sync with MightBeContainingBlockFor in ReflowInput.cpp.
if (!GetParent()) { return nullptr;
} // MathML frames might have absolute positioning style, but they would // still be in-flow. So we have to check to make sure that the frame // is really out-of-flow too.
nsIFrame* f; if (IsAbsolutelyPositioned(aStyleDisplay)) {
f = GetParent(); // the parent is always the containing block
} else {
f = GetNearestBlockContainer(GetParent());
}
if (aFlags & SKIP_SCROLLED_FRAME && f &&
f->Style()->GetPseudoType() == PseudoStyleType::MozScrolledContent) {
f = f->GetParent();
} return f;
}
bool nsIFrame::CachedIsEmpty() {
NS_ASSERTION(!HasAnyStateBits(NS_FRAME_IS_DIRTY) ||
IsHiddenByContentVisibilityOfInFlowParentForLayout(), "Must only be called on reflowed lines or those hidden by " "content-visibility."); return IsEmpty();
}
// Find the direction of the frame from the EmbeddingLevelProperty, // which is the resolved bidi level set in // nsBidiPresUtils::ResolveParagraph (odd levels = right-to-left). // If the embedding level isn't set, just use the CSS direction // property. bool hasBidiData;
FrameBidiData bidiData = GetProperty(BidiDataProperty(), &hasBidiData); bool isRTL = hasBidiData
? bidiData.embeddingLevel.IsRTL()
: StyleVisibility()->mDirection == StyleDirection::Rtl; if ((!isRTL && inOffset > newOffset) ||
(isRTL && inOffset <= newOffset)) {
pt = contentRect.TopRight();
}
}
}
*outPoint = pt; return NS_OK;
}
nsresult nsIFrame::GetCharacterRectsInRange(int32_t aInOffset, int32_t aLength,
nsTArray<nsRect>& aOutRect) { /* no text */ return NS_ERROR_FAILURE;
}
nsresult nsIFrame::GetChildFrameContainingOffset(int32_t inContentOffset, bool inHint,
int32_t* outFrameContentOffset,
nsIFrame** outChildFrame) {
MOZ_ASSERT(outChildFrame && outFrameContentOffset, "Null parameter");
*outFrameContentOffset = (int32_t)inHint; // the best frame to reflect any given offset would be a visible frame if // possible i.e. we are looking for a valid frame to place the blinking caret
nsRect rect = GetRect(); if (!rect.width || !rect.height) { // if we have a 0 width or height then lets look for another frame that // possibly has the same content. If we have no frames in flow then just // let us return 'this' frame
nsIFrame* nextFlow = GetNextInFlow(); if (nextFlow) { return nextFlow->GetChildFrameContainingOffset(
inContentOffset, inHint, outFrameContentOffset, outChildFrame);
}
}
*outChildFrame = this; return NS_OK;
}
// // What I've pieced together about this routine: // Starting with a block frame (from which a line frame can be gotten) // and a line number, drill down and get the first/last selectable // frame on that line, depending on aPos->mDirection. // aOutSideLimit != 0 means ignore aLineStart, instead work from // the end (if > 0) or beginning (if < 0). // static nsresult GetNextPrevLineFromBlockFrame(PeekOffsetStruct* aPos,
nsIFrame* aBlockFrame,
int32_t aLineStart,
int8_t aOutSideLimit) {
MOZ_ASSERT(aPos);
MOZ_ASSERT(aBlockFrame);
nsPresContext* pc = aBlockFrame->PresContext();
// magic numbers: aLineStart will be -1 for end of block, 0 will be start of // block.
AutoAssertNoDomMutations guard;
nsILineIterator* it = aBlockFrame->GetLineIterator(); if (!it) { return NS_ERROR_FAILURE;
}
int32_t searchingLine = aLineStart;
int32_t countLines = it->GetNumLines(); if (aOutSideLimit > 0) { // start at end
searchingLine = countLines;
} elseif (aOutSideLimit < 0) { // start at beginning
searchingLine = -1; //"next" will be 0
} elseif ((aPos->mDirection == eDirPrevious && searchingLine == 0) ||
(aPos->mDirection == eDirNext &&
searchingLine >= (countLines - 1))) { // Not found. return NS_ERROR_FAILURE;
}
nsIFrame* resultFrame = nullptr;
nsIFrame* farStoppingFrame = nullptr; // we keep searching until we find a // "this" frame then we go to next line
nsIFrame* nearStoppingFrame = nullptr; // if we are backing up from edge, // stop here bool isBeforeFirstFrame, isAfterLastFrame; bool found = false;
constbool forceInEditableRegion =
aPos->mOptions.contains(PeekOffsetOption::ForceEditableRegion); while (!found) { if (aPos->mDirection == eDirPrevious) {
searchingLine--;
} else {
searchingLine++;
} if ((aPos->mDirection == eDirPrevious && searchingLine < 0) ||
(aPos->mDirection == eDirNext && searchingLine >= countLines)) { // we need to jump to new block frame. return NS_ERROR_FAILURE;
}
{ auto line = it->GetLine(searchingLine).unwrap(); if (!line.mNumFramesOnLine) { continue;
}
nsIFrame* firstFrame = nullptr;
nsIFrame* lastFrame = nullptr;
nsIFrame* frame = line.mFirstFrameOnLine;
int32_t i = line.mNumFramesOnLine; do { // If the caller wants a frame for a inclusive ancestor of the ancestor // limiter, ignore frames for outside the limiter. if (aPos->FrameContentIsInAncestorLimiter(frame)) { if (!firstFrame) {
firstFrame = frame;
}
lastFrame = frame;
} if (i == 1) { break;
}
frame = frame->GetNextSibling(); if (!frame) {
NS_ERROR("GetLine promised more frames than could be found"); return NS_ERROR_FAILURE;
}
} while (--i); if (!lastFrame) { // If we're looking for an editable content frame, but all frames in the // line are not in the specified editing host, return error because we // must reach the editing host boundary. return NS_ERROR_FAILURE;
} // Don't enter into native anonymous subtrees. if (!lastFrame->ContentIsRootOfNativeAnonymousSubtree()) {
nsIFrame::GetLastLeaf(&lastFrame);
}
if (aPos->mDirection == eDirNext) {
nearStoppingFrame = firstFrame;
farStoppingFrame = lastFrame;
} else {
nearStoppingFrame = lastFrame;
farStoppingFrame = firstFrame;
}
}
nsPoint offset = aBlockFrame->GetOffsetToRootFrame();
nsPoint newDesiredPos =
aPos->mDesiredCaretPos -
offset; // get desired position into blockframe coords // TODO: nsILineIterator::FindFrameAt should take optional editing host // parameter and if it's set, it should return the nearest editable frame // for the editing host when the frame at the desired position is not // editable.
nsresult rv = it->FindFrameAt(searchingLine, newDesiredPos, &resultFrame,
&isBeforeFirstFrame, &isAfterLastFrame); if (NS_FAILED(rv)) { continue;
}
if (resultFrame) { // If ancestor limiter is specified and we reached outside content of it, // return error because we reached its element boundary. if (!aPos->FrameContentIsInAncestorLimiter(resultFrame)) { return NS_ERROR_FAILURE;
} // Check to see if this is ANOTHER blockframe inside the other one that // we should look inside. if (resultFrame->CanProvideLineIterator() &&
IsRelevantBlockFrame(resultFrame)) {
aPos->mResultFrame = resultFrame; return NS_OK;
} // resultFrame is not a block frame
Maybe<nsFrameIterator> frameIterator;
frameIterator.emplace(
pc, resultFrame, nsFrameIterator::Type::PostOrder, false, // aVisual
aPos->mOptions.contains(PeekOffsetOption::StopAtScroller), false, // aFollowOOFs false// aSkipPopupChecks
);
auto FoundValidFrame = [forceInEditableRegion, aPos]( const nsIFrame::ContentOffsets& aOffsets, const nsIFrame* aFrame) { if (!aOffsets.content) { returnfalse;
} if (!aFrame->IsSelectable()) { returnfalse;
} if (aPos->mAncestorLimiter &&
!aOffsets.content->IsInclusiveDescendantOf(
aPos->mAncestorLimiter)) { returnfalse;
} if (forceInEditableRegion && !aOffsets.content->IsEditable()) { returnfalse;
} returntrue;
};
if (!resultFrame->IsViewportFrame()) {
nsPoint offset = resultFrame->GetOffsetToRootFrame();
nsIFrame::ContentOffsets offsets =
resultFrame->GetContentOffsetsFromPoint(
point - offset, nsIFrame::IGNORE_NATIVE_ANONYMOUS_SUBTREE);
aPos->mResultContent = offsets.content;
aPos->mContentOffset = offsets.offset;
aPos->mAttach = offsets.associate; if (FoundValidFrame(offsets, resultFrame)) {
found = true; break;
}
}
if (aPos->mDirection == eDirPrevious &&
resultFrame == farStoppingFrame) { break;
} if (aPos->mDirection == eDirNext && resultFrame == nearStoppingFrame) { break;
} // always try previous on THAT line if that fails go the other way
resultFrame = frameIterator->Traverse(/* aForward = */ false); if (!resultFrame) { return NS_ERROR_FAILURE;
}
}
if (!found) {
resultFrame = storeOldResultFrame;
frameIterator.reset();
frameIterator.emplace(
pc, resultFrame, nsFrameIterator::Type::Leaf, false, // aVisual
aPos->mOptions.contains(PeekOffsetOption::StopAtScroller), false, // aFollowOOFs false// aSkipPopupChecks
);
MOZ_ASSERT(frameIterator);
} while (!found) {
nsPoint point = aPos->mDesiredCaretPos;
nsPoint offset = resultFrame->GetOffsetToRootFrame();
nsIFrame::ContentOffsets offsets =
resultFrame->GetContentOffsetsFromPoint(
point - offset, nsIFrame::IGNORE_NATIVE_ANONYMOUS_SUBTREE);
aPos->mResultContent = offsets.content;
aPos->mContentOffset = offsets.offset;
aPos->mAttach = offsets.associate; if (FoundValidFrame(offsets, resultFrame)) {
found = true;
aPos->mAttach = resultFrame == farStoppingFrame
? CaretAssociationHint::Before
: CaretAssociationHint::After; break;
} if (aPos->mDirection == eDirPrevious &&
resultFrame == nearStoppingFrame) { break;
} if (aPos->mDirection == eDirNext && resultFrame == farStoppingFrame) { break;
} // previous didnt work now we try "next"
nsIFrame* tempFrame = frameIterator->Traverse(/* aForward = */ true); if (!tempFrame) { break;
}
resultFrame = tempFrame;
}
aPos->mResultFrame = resultFrame;
} else { // we need to jump to new block frame.
aPos->mAmount = eSelectLine;
aPos->mStartOffset = 0;
aPos->mAttach = aPos->mDirection == eDirNext
? CaretAssociationHint::Before
: CaretAssociationHint::After; if (aPos->mDirection == eDirPrevious) {
aPos->mStartOffset = -1; // start from end
} return aBlockFrame->PeekOffset(aPos);
}
} return NS_OK;
}
// If this is a preformatted text frame, see if it ends with a newline static nsContentAndOffset FindLineBreakInText(nsIFrame* aFrame,
nsDirection aDirection) {
nsContentAndOffset result;
if (aFrame->IsGeneratedContentFrame() ||
!aFrame->HasSignificantTerminalNewline()) { return result;
}
// Find the first (or last) descendant of the given frame // which is either a block-level frame or a BRFrame, or some other kind of break // which stops the line. static nsContentAndOffset FindLineBreakingFrame(nsIFrame* aFrame,
nsDirection aDirection) {
nsContentAndOffset result;
if (aFrame->IsGeneratedContentFrame()) { return result;
}
// Treat form controls and other replaced inline level elements as inline // leaves. if (aFrame->IsReplaced() && aFrame->IsInlineOutside() &&
!aFrame->IsBrFrame() && !aFrame->IsTextFrame()) { return result;
}
// Check the frame itself // Fall through block-in-inline split frames because their mContent is // the content of the inline frames they were created from. The // first/last child of such frames is the real block frame we're // looking for. if ((IsRelevantBlockFrame(aFrame) &&
!aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) ||
aFrame->IsBrFrame()) {
nsIContent* content = aFrame->GetContent();
result.mContent = content->GetParent(); // In some cases (bug 310589, bug 370174) we end up here with a null // content. This probably shouldn't ever happen, but since it sometimes // does, we want to avoid crashing here.
NS_ASSERTION(result.mContent, "Unexpected orphan content"); if (result.mContent) {
result.mOffset = result.mContent->ComputeIndexOf_Deprecated(content) +
(aDirection == eDirPrevious ? 1 : 0);
} return result;
}
result = FindLineBreakInText(aFrame, aDirection); if (result.mContent) { return result;
}
// Iterate over children and call ourselves recursively if (aDirection == eDirPrevious) {
nsIFrame* child = aFrame->PrincipalChildList().LastChild(); while (child && !result.mContent) {
result = FindLineBreakingFrame(child, aDirection);
child = child->GetPrevSibling();
}
} else { // eDirNext
nsIFrame* child = aFrame->PrincipalChildList().FirstChild(); while (child && !result.mContent) {
result = FindLineBreakingFrame(child, aDirection);
child = child->GetNextSibling();
}
} return result;
}
enumclass OffsetIsAtLineEdge : bool { No, Yes };
staticvoid SetPeekResultFromFrame(PeekOffsetStruct& aPos, nsIFrame* aFrame,
int32_t aOffset,
OffsetIsAtLineEdge aAtLineEdge) {
FrameContentRange range = GetRangeForFrame(aFrame);
aPos.mResultFrame = aFrame;
aPos.mResultContent = range.content; // Output offset is relative to content, not frame
aPos.mContentOffset =
aOffset < 0 ? range.end + aOffset + 1 : range.start + aOffset; // Ensure we don't go past the range. This is important if aFrame is empty.
aPos.mContentOffset = std::clamp(aPos.mContentOffset, range.start, range.end); if (aAtLineEdge == OffsetIsAtLineEdge::Yes) {
aPos.mAttach = aPos.mContentOffset == range.start
? CaretAssociationHint::After
: CaretAssociationHint::Before;
}
}
// Go through containing frames until reaching a block frame. // In each step, search the previous (or next) siblings for the closest // "stop frame" (a block frame or a BRFrame). // If found, set it to be the selection boundary and abort. while (!reachedLimit) {
nsIFrame* parent = frame->GetParent(); // Treat a frame associated with the root content as if it were a block // frame. if (!frame->mContent || !frame->mContent->GetParent()) {
reachedLimit = true; break;
}
if (aPos->mDirection == eDirNext) { // Try to find our own line-break before looking at our siblings.
blockFrameOrBR = FindLineBreakInText(frame, eDirNext);
}
if (reachedLimit) { // no "stop frame" found if (aPos->mOptions.contains(PeekOffsetOption::ForCaretMove)) { // For a caret move, drill down to a leaf so scroll-into-view geometry, // which only uses text-frame offsets when the focus node is a Text node, // gets a usable position. Frame children include flattened-tree // descendants, so this also covers shadow-DOM content that the element // branch below reaches via GetShadowRootForSelection. constbool atEnd = aPos->mDirection == eDirNext;
FrameTarget targetFrame = DrillDownToSelectionFrame(
frame, atEnd, nsIFrame::IGNORE_NATIVE_ANONYMOUS_SUBTREE);
SetPeekResultFromFrame(*aPos, targetFrame.frame, atEnd ? -1 : 0,
OffsetIsAtLineEdge::Yes);
} else { // For selection (e.g. triple-click paragraph selection), the block // container itself is the expected boundary.
aPos->mResultContent = frame->GetContent(); if (aPos->mResultContent) { if (ShadowRoot* shadowRoot =
aPos->mResultContent->GetShadowRootForSelection()) { // Even if there's no children for this node, // the elements inside the shadow root is still // selectable
aPos->mResultContent = shadowRoot;
}
} if (aPos->mDirection == eDirPrevious) {
aPos->mContentOffset = 0;
} elseif (aPos->mResultContent) {
aPos->mContentOffset = aPos->mResultContent->GetChildCount();
}
}
} return NS_OK;
}
// Determines "are we looking for a boundary between whitespace and // non-whitespace (in the direction we're moving in)". It is true when moving // forward and looking for a beginning of a word, or when moving backwards and // looking for an end of a word. staticbool ShouldWordSelectionEatSpace(const PeekOffsetStruct& aPos) { if (aPos.mWordMovementType != eDefaultBehavior) { // aPos->mWordMovementType possible values: // eEndWord: eat the space if we're moving backwards // eStartWord: eat the space if we're moving forwards return (aPos.mWordMovementType == eEndWord) ==
(aPos.mDirection == eDirPrevious);
} // Use the hidden preference which is based on operating system // behavior. This pref only affects whether moving forward by word // should go to the end of this word or start of the next word. When // going backwards, the start of the word is always used, on every // operating system. return aPos.mDirection == eDirNext &&
StaticPrefs::layout_word_select_eat_space_to_next_word();
}
if (current.mJumpedLine) { // If we jumped lines, it's as if we found a character, but we still need // to eat non-renderable content on the new line.
peekSearchState = current.PeekOffsetNoAmount(movingInFrameDirection);
} else {
PeekOffsetCharacterOptions options;
options.mRespectClusters = aPos->mAmount == eSelectCluster;
peekSearchState =
current.PeekOffsetCharacter(movingInFrameDirection, options); if (peekSearchState == FOUND && forceEditableRegion &&
!current.mFrame->ContentIsEditable()) { // Treat non-editable content as unselectable. Note that we may need to // set mJumpedLine propery even if it's not editable. Therefore, we // cannot skip the above call.
peekSearchState = CONTINUE_UNSELECTABLE;
}
}
if (peekSearchState != FOUND) {
SelectablePeekReport next = current.mFrame->GetFrameFromDirection(*aPos); if (next.Failed()) { return NS_ERROR_FAILURE;
}
next.mJumpedLine |= current.mJumpedLine;
next.mMovedOverNonSelectableText |= current.mMovedOverNonSelectableText;
next.mHasSelectableFrame |= current.mHasSelectableFrame;
current = next;
}
// Found frame, but because we moved over non selectable text we want // the offset to be at the frame edge. Note that if we are extending the // selection, this doesn't matter. if (peekSearchState == FOUND && current.mMovedOverNonSelectableText &&
(!aPos->mOptions.contains(PeekOffsetOption::Extend) ||
current.mHasSelectableFrame)) { auto [start, end] = current.mFrame->GetOffsets();
current.mOffset = aPos->mDirection == eDirNext ? 0 : end - start;
}
}
// Set outputs
current.TransferTo(*aPos); // If we're dealing with a text frame and moving backward positions us at // the end of that line, decrease the offset by one to make sure that // we're placed before the linefeed character on the previous line. if (current.mOffset < 0 && current.mJumpedLine &&
aPos->mDirection == eDirPrevious &&
current.mFrame->HasSignificantTerminalNewline() &&
!current.mIgnoredBrFrame) {
--aPos->mContentOffset;
} return NS_OK;
}
SelectablePeekReport next = [&]() {
PeekOffsetOptions options = aPos->mOptions; if (state.mSawInlineCharacter) { // If we've already found a character, we don't want to stop at // placeholder frame boundary if there is in the word.
options += PeekOffsetOption::StopAtPlaceholder;
} return current.mFrame->GetFrameFromDirection(aPos->mDirection, options,
aPos->mAncestorLimiter);
}(); if (next.Failed()) { // If we've crossed the line boundary, check to make sure that we // have not consumed a trailing newline as whitespace if it's // significant. if (next.mJumpedLine && wordSelectEatSpace &&
current.mFrame->HasSignificantTerminalNewline() &&
current.mFrame->StyleText()->mWhiteSpaceCollapse !=
StyleWhiteSpaceCollapse::PreserveBreaks) {
current.mOffset -= 1;
} break;
}
if ((next.mJumpedLine || next.mFoundPlaceholder) && !wordSelectEatSpace &&
state.mSawBeforeType) { // We can't jump lines if we're looking for whitespace following // non-whitespace, and we already encountered non-whitespace. break;
}
if (shouldStopAtHardBreak && next.mJumpedHardBreak) { /** *Prev,always:Jumpandstoprightthere *Next,sawinline:juststop *Next,noinline:Jumpandconsumewhitespaces
*/ if (aPos->mDirection == eDirPrevious) { // Try moving to the previous line if exists
current.TransferTo(*aPos);
current.mFrame->PeekOffsetForCharacter(aPos, current.mOffset); return NS_OK;
} if (state.mSawInlineCharacter || current.mJumpedHardBreak) { if (current.mFrame->HasSignificantTerminalNewline()) {
current.mOffset -= 1;
}
current.TransferTo(*aPos); return NS_OK;
} // Mark the state as whitespace and continue
state.Update(false, true);
}
if (next.mJumpedLine) {
state.mContext.Truncate();
}
current = next; // Jumping a line is equivalent to encountering whitespace // This affects only when it already met an actual character if (wordSelectEatSpace && next.mJumpedLine) {
state.SetSawBeforeType();
}
}
// Set outputs
current.TransferTo(*aPos); return NS_OK;
}
for (nsIFrame* child : aParentFrame->PrincipalChildList()) { // some children may not be selectable, e.g. :before / :after pseudoelements // content with user-select: none, or contenteditable="false" // we need to skip them if (child->CanProvideLineIterator() && FoundValidFrame(child)) { return child;
} if (nsIFrame* nested = GetFirstSelectableDescendantWithLineIterator(
aPeekOffsetStruct, child)) { return nested;
}
} return nullptr;
}
// outer loop // moving to a next block when no more blocks are available in a subtree
AutoAssertNoDomMutations guard; while (NS_FAILED(result)) { auto [newBlock, lineFrame] = blockFrame->GetContainingBlockForLine(
aPos->mOptions.contains(PeekOffsetOption::StopAtScroller)); if (!newBlock) { return NS_ERROR_FAILURE;
} // FYI: If the editing host is an inline element, the block frame content // may be either not editable or editable but belonging to different editing // host.
blockFrame = newBlock;
nsILineIterator* iter = blockFrame->GetLineIterator();
int32_t thisLine = iter->FindLineContaining(lineFrame); if (NS_WARN_IF(thisLine < 0)) { return NS_ERROR_FAILURE;
}
int8_t edgeCase = 0; // no edge case. This should look at thisLine
// this part will find a frame or a block frame. If it's a block frame // it will "drill down" to find a viable frame or it will return an // error.
nsIFrame* lastFrame = this;
// inner loop - crawling the frames within a specific block subtree while (true) {
result =
GetNextPrevLineFromBlockFrame(aPos, blockFrame, thisLine, edgeCase); // we came back to same spot! keep going if (NS_SUCCEEDED(result) &&
(!aPos->mResultFrame || aPos->mResultFrame == lastFrame)) {
aPos->mResultFrame = nullptr;
lastFrame = nullptr; if (aPos->mDirection == eDirPrevious) {
thisLine--;
} else {
thisLine++;
} continue;
}
if (NS_FAILED(result)) { break;
}
lastFrame = aPos->mResultFrame; // set last frame /* SPECIAL CHECK FOR NAVIGATION INTO TABLES *whenwehitaframewhichdoesn'thavelineiterator,weneedto *drilldownandfindachildwiththelineiteratortopreventthe *crawlingprocesstoprematurelyfinish.Notethatthisisonlysoundif *we'reguaranteedtonothavemultiplechildrenimplementing *LineIterator. * *Sofarknowncasesare: *1)tablewrapper(drilldownintotablerowgroup) *2)tablecell(drilldownintoitsonlyanonchild)
*/ constbool shouldDrillIntoChildren =
aPos->mResultFrame->IsTableWrapperFrame() ||
aPos->mResultFrame->IsTableCellFrame();
if (shouldDrillIntoChildren) {
nsIFrame* child = GetFirstSelectableDescendantWithLineIterator(
*aPos, aPos->mResultFrame); if (child) {
aPos->mResultFrame = child;
}
}
if (!aPos->mResultFrame->CanProvideLineIterator()) { // no more selectable content at this level break;
}
if (aPos->mResultFrame == blockFrame) { // Make sure block element is not the same as the one we had before. break;
}
// we've struck another block element with selectable content! if (aPos->mDirection == eDirPrevious) {
edgeCase = 1; // far edge, search from end backwards
} else {
edgeCase = -1; // near edge search from beginning onwards
}
thisLine = 0; // this line means nothing now. // everything else means something so keep looking "inside" the // block
blockFrame = aPos->mResultFrame;
}
} return result;
}
nsresult nsIFrame::PeekOffsetForLineEdge(PeekOffsetStruct* aPos) { // Adjusted so that the caret can't get confused when content changes
nsIFrame* frame = AdjustFrameForSelectionStyles(this); // FIXME: Use PeekOffsetStruct::mAncestorLimiter instead.
Element* editingHost = frame->GetContent()->GetEditingHost();
auto [blockFrame, lineFrame] = frame->GetContainingBlockForLine(
aPos->mOptions.contains(PeekOffsetOption::StopAtScroller)); if (!blockFrame) { return NS_ERROR_FAILURE;
}
AutoAssertNoDomMutations guard;
nsILineIterator* it = blockFrame->GetLineIterator();
int32_t thisLine = it->FindLineContaining(lineFrame); if (thisLine < 0) { return NS_ERROR_FAILURE;
}
if (aPos->mOptions.contains(PeekOffsetOption::Visual) &&
PresContext()->BidiEnabled()) {
nsIFrame* firstFrame; bool isReordered;
nsIFrame* lastFrame;
MOZ_TRY(
it->CheckLineOrder(thisLine, &isReordered, &firstFrame, &lastFrame));
baseFrame = endOfLine ? lastFrame : firstFrame;
} else { auto line = it->GetLine(thisLine).unwrap();
nsIFrame* frame = line.mFirstFrameOnLine; bool lastFrameWasEditable = false; for (int32_t count = line.mNumFramesOnLine; count;
--count, frame = frame->GetNextSibling()) { if (frame->IsGeneratedContentFrame()) { continue;
} // When jumping to the end of the line with the "end" key, // try to skip over brFrames if (endOfLine && line.mNumFramesOnLine > 1 && frame->IsBrFrame() &&
lastFrameWasEditable == frame->GetContent()->IsEditable()) { continue;
}
lastFrameWasEditable =
frame->GetContent() && frame->GetContent()->IsEditable();
baseFrame = frame; if (!endOfLine) { break;
}
}
} if (!baseFrame) { return NS_ERROR_FAILURE;
} // Make sure we are not leaving our inline editing host if exists if (editingHost) { if (nsIFrame* frame = editingHost->GetPrimaryFrame()) { if (frame->IsInlineOutside() &&
!editingHost->Contains(baseFrame->GetContent())) {
baseFrame = frame; if (endOfLine) {
baseFrame = baseFrame->LastContinuation();
}
}
}
}
FrameTarget targetFrame = DrillDownToSelectionFrame(
baseFrame, endOfLine, nsIFrame::IGNORE_NATIVE_ANONYMOUS_SUBTREE);
SetPeekResultFromFrame(*aPos, targetFrame.frame, endOfLine ? -1 : 0,
OffsetIsAtLineEdge::Yes); if (endOfLine && targetFrame.frame->HasSignificantTerminalNewline()) { // Do not position the caret after the terminating newline if we're // trying to move to the end of line (see bug 596506)
--aPos->mContentOffset;
} if (!aPos->mResultContent) { return NS_ERROR_FAILURE;
} return NS_OK;
}
if (NS_WARN_IF(HasAnyStateBits(NS_FRAME_IS_DIRTY))) { // FIXME(Bug 1654362): <caption> currently can remain dirty. return NS_ERROR_UNEXPECTED;
}
// Translate content offset to be relative to frame
int32_t offset = aPos->mStartOffset - GetRangeForFrame(this).start;
switch (aPos->mAmount) { case eSelectCharacter: case eSelectCluster: return PeekOffsetForCharacter(aPos, offset); case eSelectWordNoSpace: // eSelectWordNoSpace means that we should not be eating any whitespace // when moving to the adjacent word. This means that we should set aPos-> // mWordMovementType to eEndWord if we're moving forwards, and to // eStartWord if we're moving backwards. if (aPos->mDirection == eDirPrevious) {
aPos->mWordMovementType = eStartWord;
} else {
aPos->mWordMovementType = eEndWord;
} // Intentionally fall through the eSelectWord case.
[[fallthrough]]; case eSelectWord: return PeekOffsetForWord(aPos, offset); case eSelectLine: return PeekOffsetForLine(aPos); case eSelectBeginLine: case eSelectEndLine: return PeekOffsetForLineEdge(aPos); case eSelectParagraph: return PeekOffsetForParagraph(aPos); default: {
NS_ASSERTION(false, "Invalid amount"); return NS_ERROR_FAILURE;
}
}
}
nsIFrame::FrameSearchResult nsIFrame::PeekOffsetNoAmount(bool aForward,
int32_t* aOffset) {
NS_ASSERTION(aOffset && *aOffset <= 1, "aOffset out of range"); // Sure, we can stop right here. return FOUND;
}
nsIFrame::FrameSearchResult nsIFrame::PeekOffsetCharacter( bool aForward, int32_t* aOffset, PeekOffsetCharacterOptions aOptions) {
NS_ASSERTION(aOffset && *aOffset <= 1, "aOffset out of range");
int32_t startOffset = *aOffset; // A negative offset means "end of frame", which in our case means offset 1. if (startOffset < 0) {
startOffset = 1;
} if (aForward == (startOffset == 0)) { // We're before the frame and moving forward, or after it and moving // backwards: skip to the other side and we're done.
*aOffset = 1 - startOffset; return FOUND;
} returnCONTINUE;
}
nsIFrame::FrameSearchResult nsIFrame::PeekOffsetWord( bool aForward, bool aWordSelectEatSpace, bool aIsKeyboardSelect,
int32_t* aOffset, PeekWordState* aState, bool/*aTrimSpaces*/) {
NS_ASSERTION(aOffset && *aOffset <= 1, "aOffset out of range");
int32_t startOffset = *aOffset; // This isn't text, so truncate the context
aState->mContext.Truncate(); if (startOffset < 0) {
startOffset = 1;
} if (aForward == (startOffset == 0)) { // We're before the frame and moving forward, or after it and moving // backwards. If we're looking for non-whitespace, we found it (without // skipping this frame). if (!aState->mAtStart) { if (aState->mLastCharWasPunctuation) { // We're not punctuation, so this is a punctuation boundary. if (BreakWordBetweenPunctuation(aState, aForward, false, false,
aIsKeyboardSelect)) { return FOUND;
}
} else { // This is not a punctuation boundary. if (aWordSelectEatSpace && aState->mSawBeforeType) { return FOUND;
}
}
} // Otherwise skip to the other side and note that we encountered // non-whitespace.
*aOffset = 1 - startOffset;
aState->Update(false, // not punctuation false// not whitespace
); if (!aWordSelectEatSpace) {
aState->SetSawBeforeType();
}
} returnCONTINUE;
}
// static bool nsIFrame::BreakWordBetweenPunctuation(const PeekWordState* aState, bool aForward, bool aPunctAfter, bool aWhitespaceAfter, bool aIsKeyboardSelect) {
NS_ASSERTION(aPunctAfter != aState->mLastCharWasPunctuation, "Call this only at punctuation boundaries"); if (aState->mLastCharWasWhitespace) { // We always stop between whitespace and punctuation returntrue;
} if (!StaticPrefs::layout_word_select_stop_at_punctuation()) { // When this pref is false, we never stop at a punctuation boundary unless // it's followed by whitespace (in the relevant direction). return aWhitespaceAfter;
} if (!aIsKeyboardSelect) { // mouse caret movement (e.g. word selection) always stops at every // punctuation boundary returntrue;
} bool afterPunct = aForward ? aState->mLastCharWasPunctuation : aPunctAfter; if (!afterPunct) { // keyboard caret movement only stops after punctuation (in content order) returnfalse;
} // Stop only if we've seen some non-punctuation since the last whitespace; // don't stop after punctuation that follows whitespace. return aState->mSeenNonPunctuationSinceWhitespace;
}
std::pair<nsIFrame*, nsIFrame*> nsIFrame::GetContainingBlockForLine( bool aLockScroll) const { const nsIFrame* parentFrame = this; const nsIFrame* frame; while (parentFrame) {
frame = parentFrame; if (frame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) { // if we are searching for a frame that is not in flow we will not find // it. we must instead look for its placeholder if (frame->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) { // abspos continuations don't have placeholders, get the fif
frame = frame->FirstInFlow();
}
frame = frame->GetPlaceholderFrame(); if (!frame) { return std::pair(nullptr, nullptr);
}
}
parentFrame = frame->GetParent(); if (parentFrame) { if (aLockScroll && parentFrame->IsScrollContainerFrame()) { return std::pair(nullptr, nullptr);
} if (parentFrame->CanProvideLineIterator()) { return std::pair(const_cast<nsIFrame*>(parentFrame), const_cast<nsIFrame*>(frame));
}
}
} return std::pair(nullptr, nullptr);
}
Result<bool, nsresult> nsIFrame::IsVisuallyAtLineEdge(
nsILineIterator* aLineIterator, int32_t aLine, nsDirection aDirection) { auto line = aLineIterator->GetLine(aLine).unwrap();
nsIFrame *firstFrame = nullptr, *lastFrame = nullptr; bool isReordered = false;
MOZ_TRY(aLineIterator->CheckLineOrder(aLine, &isReordered, &firstFrame,
&lastFrame)); if (!firstFrame || !lastFrame) { returntrue; // XXX: Why true? We check whether `this` is at the edge...
}
nsIFrame* leftmostFrame = lineIsRTL ? lastFrame : firstFrame;
nsIFrame* rightmostFrame = lineIsRTL ? firstFrame : lastFrame; auto FrameIsRTL = [](nsIFrame* aFrame) { return nsBidiPresUtils::FrameDirection(aFrame) ==
mozilla::intl::BidiDirection::RTL;
}; if (!lineIsRTL == (aDirection == eDirPrevious)) {
nsIFrame* maybeLeftmostFrame = leftmostFrame; for ([[maybe_unused]] int32_t i : IntegerRange(line.mNumFramesOnLine)) { if (maybeLeftmostFrame == this) { returntrue;
} // If left edge of the line starts with placeholder frames, we can ignore // them and should keep checking the following frames. if (!maybeLeftmostFrame->IsPlaceholderFrame()) { if ((FrameIsRTL(maybeLeftmostFrame) == lineIsRTL) ==
(aDirection == eDirPrevious)) {
nsIFrame::GetFirstLeaf(&maybeLeftmostFrame);
} else {
nsIFrame::GetLastLeaf(&maybeLeftmostFrame);
} return maybeLeftmostFrame == this;
}
maybeLeftmostFrame = nsBidiPresUtils::GetFrameToRightOf(
maybeLeftmostFrame, line.mFirstFrameOnLine, line.mNumFramesOnLine); if (!maybeLeftmostFrame) { returnfalse;
}
} returnfalse;
}
nsIFrame* maybeRightmostFrame = rightmostFrame; for ([[maybe_unused]] int32_t i : IntegerRange(line.mNumFramesOnLine)) { if (maybeRightmostFrame == this) { returntrue;
} // If the line ends with placehlder frames, we can ignore them and should // keep checking the preceding frames. if (!maybeRightmostFrame->IsPlaceholderFrame()) { if ((FrameIsRTL(maybeRightmostFrame) == lineIsRTL) ==
(aDirection == eDirPrevious)) {
nsIFrame::GetFirstLeaf(&maybeRightmostFrame);
} else {
nsIFrame::GetLastLeaf(&maybeRightmostFrame);
} return maybeRightmostFrame == this;
}
maybeRightmostFrame = nsBidiPresUtils::GetFrameToLeftOf(
maybeRightmostFrame, line.mFirstFrameOnLine, line.mNumFramesOnLine); if (!maybeRightmostFrame) { returnfalse;
}
} returnfalse;
}
Result<bool, nsresult> nsIFrame::IsLogicallyAtLineEdge(
nsILineIterator* aLineIterator, int32_t aLine, nsDirection aDirection) { auto line = aLineIterator->GetLine(aLine).unwrap(); if (!line.mNumFramesOnLine) { returnfalse;
}
MOZ_ASSERT(line.mFirstFrameOnLine);
if (aDirection == eDirPrevious) {
nsIFrame* maybeFirstFrame = line.mFirstFrameOnLine; for ([[maybe_unused]] int32_t i : IntegerRange(line.mNumFramesOnLine)) { if (maybeFirstFrame == this) { returntrue;
} // If the line starts with placeholder frames, we can ignore them and // should keep checking the following frames. if (!maybeFirstFrame->IsPlaceholderFrame()) {
nsIFrame::GetFirstLeaf(&maybeFirstFrame); return maybeFirstFrame == this;
}
maybeFirstFrame = maybeFirstFrame->GetNextSibling(); if (!maybeFirstFrame) { returnfalse;
}
} returnfalse;
}
// eDirNext
nsIFrame* maybeLastFrame = line.GetLastFrameOnLine(); for ([[maybe_unused]] int32_t i : IntegerRange(line.mNumFramesOnLine)) { if (maybeLastFrame == this) { returntrue;
} // If the line ends with placehlder frames, we can ignore them and should // keep checking the preceding frames. if (!maybeLastFrame->IsPlaceholderFrame()) {
nsIFrame::GetLastLeaf(&maybeLastFrame); return maybeLastFrame == this;
}
maybeLastFrame = maybeLastFrame->GetPrevSibling();
} returnfalse;
}
if (aOptions.contains(PeekOffsetOption::StopAtPlaceholder) &&
traversedFrame->IsPlaceholderFrame()) { // XXX If the placeholder frame does not have meaningful content, the user // may want to select as a word around the out-of-flow cotent. However, // non-text frame resets context in nsIFrame::PeekOffsetWord(). Therefore, // next text frame considers the new word starts from its edge. So, it's // not enough to implement such behavior with adding a check here whether // the real frame may change the word with its contents if it were not // out-of-flow.
result.mFoundPlaceholder = true; return result;
}
auto IsSelectableFrame = [aAncestorLimiter, aOptions,
frameSelection](const nsIFrame* aFrame) { if (!aFrame->IsSelectable() || MOZ_UNLIKELY(!aFrame->GetContent())) { returnfalse;
} // If the found frame content is managed by different nsFrameSelection, we // cannot select the frame content with current selection. if (frameSelection != aFrame->GetContent()->GetFrameSelection()) { returnfalse;
} if (MOZ_UNLIKELY(aAncestorLimiter &&
!aAncestorLimiter->GetPrimaryFrame()) &&
!aFrame->GetContent()->IsInclusiveFlatTreeDescendantOf(
aAncestorLimiter)) { returnfalse;
} return !aOptions.contains(PeekOffsetOption::ForceEditableRegion) ||
aFrame->GetContent()->IsEditable();
};
// Skip br frames, but only if we can select something before hitting the // end of the line or a non-selectable region. if (atLineEdge && aDirection == eDirPrevious &&
traversedFrame->IsBrFrame()) { for (nsIFrame* current = traversedFrame->GetPrevSibling(); current;
current = current->GetPrevSibling()) { if (!current->IsBlockOutside() && IsSelectableFrame(current)) { if (!current->IsBrFrame()) {
result.mIgnoredBrFrame = true;
} break;
}
} if (result.mIgnoredBrFrame) { continue;
}
}
selectable = IsSelectableFrame(traversedFrame); if (MOZ_UNLIKELY(!frameSelection) && selectable &&
MOZ_LIKELY(traversedFrame->GetContent())) {
frameSelection = traversedFrame->GetContent()->GetFrameSelection();
} if (!selectable) { if (traversedFrame->IsSelectable()) {
result.mHasSelectableFrame = true;
}
result.mMovedOverNonSelectableText = true;
}
} // while (!selectable)
if (aOptions.contains(PeekOffsetOption::Visual) &&
nsBidiPresUtils::IsReversedDirectionFrame(traversedFrame)) { // The new frame is reverse-direction, go to the other end
result.mOffset = -1 - result.mOffset;
}
result.mFrame = traversedFrame; return result;
}
/* virtual */ void nsIFrame::ChildIsDirty(nsIFrame* aChild) {
MOZ_ASSERT_UNREACHABLE( "should never be called on a frame that doesn't " "inherit from nsContainerFrame");
}
// Don't bother with converting to the deltas form if we already // have a property. return changed;
}
const nsRect& vis = aOverflowAreas.InkOverflow();
uint32_t l = -vis.x, // left edge: positive delta is leftwards
t = -vis.y, // top: positive is upwards
r = vis.XMost() - mRect.width, // right: positive is rightwards
b = vis.YMost() - mRect.height; // bottom: positive is downwards if (aOverflowAreas.ScrollableOverflow().IsEqualEdges(
nsRect(nsPoint(0, 0), GetSize())) &&
l <= InkOverflowDeltas::kMax && t <= InkOverflowDeltas::kMax &&
r <= InkOverflowDeltas::kMax && b <= InkOverflowDeltas::kMax && // we have to check these against zero because we *never* want to // set a frame as having no overflow in this function. This is // because FinishAndStoreOverflow calls this function prior to // SetRect based on whether the overflow areas match aNewSize. // In the case where the overflow areas exactly match mRect but // do not match aNewSize, we need to store overflow in a property // so that our eventual SetRect/SetSize will know that it has to // reset our overflow areas.
(l | t | r | b) != 0) {
InkOverflowDeltas oldDeltas = mOverflow.mInkOverflowDeltas; // It's a "small" overflow area so we store the deltas for each edge // directly in the frame, rather than allocating a separate rect. // If they're all zero, that's fine; we're setting things to // no-overflow.
mOverflow.mInkOverflowDeltas.mLeft = l;
mOverflow.mInkOverflowDeltas.mTop = t;
mOverflow.mInkOverflowDeltas.mRight = r;
mOverflow.mInkOverflowDeltas.mBottom = b; // There was no scrollable overflow before, and there isn't now. return oldDeltas != mOverflow.mInkOverflowDeltas;
} else { bool changed =
!aOverflowAreas.ScrollableOverflow().IsEqualEdges(
nsRect(nsPoint(0, 0), GetSize())) ||
!aOverflowAreas.InkOverflow().IsEqualEdges(InkOverflowFromDeltas());
// it's a large overflow area that we need to store as a property
mOverflow.mType = OverflowStorageType::Large;
AddProperty(OverflowAreasProperty(), new OverflowAreas(aOverflowAreas)); return changed;
}
}
// The SVG container frames besides SVGTextFrame do not maintain // an accurate mRect. It will make the outline be larger than // we expect, we need to make them narrow to their children's outline. // aOutValid is set to false if the returned nsRect is not valid // and should not be included in the outline rectangle.
aOutValid = !aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT) ||
!aFrame->IsSVGContainerFrame() || aFrame->IsSVGTextFrame();
nsRect u;
if (!aFrame->FrameMaintainsOverflow()) { return u;
}
// Start from our border-box, transformed. See comment below about // transform of children. bool doTransform =
aApplyTransform == ApplyTransform::Yes && aFrame->IsTransformed();
TransformReferenceBox boundsRefBox(nullptr, bounds); if (doTransform) {
u = nsDisplayTransform::TransformRect(bounds, aFrame, boundsRefBox);
} else {
u = bounds;
}
if (aOutValid && !StaticPrefs::layout_outline_include_overflow()) { return u;
}
// Only iterate through the children if the overflow areas suggest // that we might need to, and if the frame doesn't clip its overflow // anyway. if (aOverflowOverride) { if (!doTransform && bounds.IsEqualEdges(aOverflowOverride->InkOverflow()) &&
bounds.IsEqualEdges(aOverflowOverride->ScrollableOverflow())) { return u;
}
} else { if (!doTransform && bounds.IsEqualEdges(aFrame->InkOverflowRect()) &&
bounds.IsEqualEdges(aFrame->ScrollableOverflowRect())) { return u;
}
} const nsStyleDisplay* disp = aFrame->StyleDisplay();
LayoutFrameType fType = aFrame->Type(); if (fType == LayoutFrameType::ScrollContainer ||
fType == LayoutFrameType::ListControl ||
fType == LayoutFrameType::SVGOuterSVG) { return u;
}
auto overflowClipAxes = aFrame->ShouldApplyOverflowClipping(disp); auto overflowClipMargin = aFrame->OverflowClipMargin(
overflowClipAxes, /* aAllowNegative = */ false); if (overflowClipAxes == kPhysicalAxesBoth && overflowClipMargin.IsAllZero()) { return u;
}
// Iterate over all children except pop-up, absolutely-positioned, // float, and overflow ones. const FrameChildListIDs skip = {FrameChildListID::Absolute,
FrameChildListID::Float,
FrameChildListID::Overflow}; for (constauto& [list, listID] : aFrame->ChildLists()) { if (skip.contains(listID)) { continue;
}
for (nsIFrame* child : list) { if (child->IsPlaceholderFrame()) { continue;
}
// Note that passing ApplyTransform::Yes when // child->Combines3DTransformWithAncestors() returns true is incorrect if // our aApplyTransform is No... but the opposite would be as well. // This is because elements within a preserve-3d scene are always // transformed up to the top of the scene. This means we don't have a // mechanism for getting a transform up to an intermediate point within // the scene. We choose to over-transform rather than under-transform // because this is consistent with other overflow areas. bool validRect = true;
nsRect childRect =
ComputeOutlineInnerRect(child, ApplyTransform::Yes, validRect) +
child->GetPosition();
if (!validRect) { continue;
}
if (clipPropClipRect) { // Intersect with the clip before transforming.
childRect.IntersectRect(childRect, *clipPropClipRect);
}
// Note that we transform each child separately according to // aFrame's transform, and then union, which gives a different // (smaller) result from unioning and then transforming the // union. This doesn't match the way we handle overflow areas // with 2-D transforms, though it does match the way we handle // overflow areas in preserve-3d 3-D scenes. if (doTransform && !child->Combines3DTransformWithAncestors()) {
childRect =
nsDisplayTransform::TransformRect(childRect, aFrame, boundsRefBox);
}
// If a SVGContainer has a non-SVGContainer child, we assign // its child's outline to this SVGContainer directly. if (!aOutValid && validRect) {
u = childRect;
aOutValid = true;
} else {
u = u.UnionEdges(childRect);
}
}
}
// When the outline property is set on a :-moz-block-inside-inline-wrapper // pseudo-element, it inherited that outline from the inline that was broken // because it contained a block. In that case, we don't want a really wide // outline if the block inside the inline is narrow, so union the actual // contents of the anonymous blocks.
nsIFrame* frameForArea = aFrame; do {
PseudoStyleType pseudoType = frameForArea->Style()->GetPseudoType(); if (pseudoType != PseudoStyleType::MozBlockInsideInlineWrapper) { break;
} // If we're done, we really want it and all its later siblings.
frameForArea = frameForArea->PrincipalChildList().FirstChild();
NS_ASSERTION(frameForArea, "anonymous block with no children?");
} while (frameForArea);
// Find the union of the border boxes of all descendants, or in // the block-in-inline case, all descendants we care about. // // Note that the interesting perspective-related cases are taken // care of by the code that handles those issues for overflow // calling FinishAndStoreOverflow again, which in turn calls this // function again. We still need to deal with preserve-3d a bit.
nsRect innerRect; bool validRect = false; if (frameForArea == aFrame) {
innerRect = ComputeOutlineInnerRect(aFrame, ApplyTransform::No, validRect,
&aNewSize, &aOverflowAreas);
} else { for (; frameForArea; frameForArea = frameForArea->GetNextSibling()) {
nsRect r =
ComputeOutlineInnerRect(frameForArea, ApplyTransform::Yes, validRect);
// Adjust for offsets transforms up to aFrame's pre-transform // (i.e., normal) coordinate space; see comments in // UnionBorderBoxes for some of the subtlety here. for (nsIFrame *f = frameForArea, *parent = f->GetParent(); /* see middle of loop */; f = parent, parent = f->GetParent()) {
r += f->GetPosition(); if (parent == aFrame) { break;
} if (parent->IsTransformed() && !f->Combines3DTransformWithAncestors()) {
TransformReferenceBox refBox(parent);
r = nsDisplayTransform::TransformRect(r, parent, refBox);
}
}
innerRect.UnionRect(innerRect, r);
}
}
// Keep this code in sync with nsDisplayOutline::GetInnerRect. if (innerRect == aFrame->GetRectRelativeToSelf()) {
aFrame->RemoveProperty(nsIFrame::OutlineInnerRectProperty());
} else {
aFrame->SetOrUpdateDeletableProperty(nsIFrame::OutlineInnerRectProperty(),
innerRect);
}
nsRect bounds(nsPoint(), aNewSize); // Store the passed in overflow area if we are a preserve-3d frame or we have // a transform, and it's not just the frame bounds. if (hasTransform || Combines3DTransformWithAncestors()) { if (!aOverflowAreas.InkOverflow().IsEqualEdges(bounds) ||
!aOverflowAreas.ScrollableOverflow().IsEqualEdges(bounds)) {
OverflowAreas* initial = GetProperty(nsIFrame::InitialOverflowProperty()); if (!initial) {
AddProperty(nsIFrame::InitialOverflowProperty(), new OverflowAreas(aOverflowAreas));
} elseif (initial != &aOverflowAreas) {
*initial = aOverflowAreas;
}
} else {
RemoveProperty(nsIFrame::InitialOverflowProperty());
} #ifdef DEBUG
SetProperty(nsIFrame::DebugInitialOverflowPropertyApplied(), true); #endif
} else { #ifdef DEBUG
RemoveProperty(nsIFrame::DebugInitialOverflowPropertyApplied()); #endif
}
// Our frame size may not have been computed and set yet, but code under // functions such as ComputeEffectsRect (which we're about to call) use the // values that are stored in our frame rect to compute their results. We // need the results from those functions to be based on the frame size that // we *will* have, so we temporarily set our frame size here before calling // those functions. // // XXX Someone should document here why we revert the frame size before we // return rather than just leaving it set. // // We pass false here to avoid invalidating display items for this temporary // change. We sometimes reflow frames multiple times, with the final size // being the same as the initial. The single call to SetSize after reflow is // done will take care of invalidating display items if the size has actually // changed.
SetSize(aNewSize, false);
if (sizeChanged && ChildrenHavePerspective(disp) &&
RecomputePerspectiveChildrenOverflow(this)) {
aOverflowAreas.SetAllTo(bounds);
DebugOnly<bool> ok = ComputeCustomOverflow(aOverflowAreas); // ComputeCustomOverflow() should not return false, when // FrameMaintainsOverflow() returns true.
MOZ_ASSERT(ok, "FrameMaintainsOverflow() != ComputeCustomOverflow()");
UnionChildOverflow(aOverflowAreas);
}
// This is now called FinishAndStoreOverflow() instead of // StoreOverflow() because frame-generic ways of adding overflow // can happen here, e.g. CSS2 outline and native theme. // If the overflow area width or height is nscoord_MAX, then a saturating // union may have encountered an overflow, so the overflow may not contain the // frame border-box. Don't warn in that case. // Don't warn for SVG either, since SVG doesn't need the overflow area // to contain the frame bounds. #ifdef DEBUG for (constauto otype : AllOverflowTypes()) { const nsRect& r = aOverflowAreas.Overflow(otype);
NS_ASSERTION(aNewSize.width == 0 || aNewSize.height == 0 ||
r.width == nscoord_MAX || r.height == nscoord_MAX ||
HasAnyStateBits(NS_FRAME_SVG_LAYOUT) ||
r.Contains(nsRect(nsPoint(), aNewSize)), "Computed overflow area must contain frame bounds");
} #endif
// Overflow area must always include the frame's top-left and bottom-right, // even if the frame rect is empty (so we can scroll to those positions). constbool shouldIncludeBounds = [&] { if (aNewSize.width == 0 && IsInlineFrame()) { // Pending a real fix for bug 426879, don't do this for inline frames with // zero width. returnfalse;
} if (HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) { // Do not do this for SVG either, since it will usually massively increase // the area unnecessarily (except for SVG that applies clipping, since // that's the pre-existing behavior, and breaks pre-rendering otherwise). // FIXME(bug 1770704): This check most likely wants to be removed or check // for specific frame types at least. return !overflowClipAxes.isEmpty();
} returntrue;
}();
if (shouldIncludeBounds) { for (constauto otype : AllOverflowTypes()) {
nsRect& o = aOverflowAreas.Overflow(otype);
o = o.UnionEdges(bounds);
}
}
// If we clip our children, clear accumulated overflow area in the affected // dimension(s). The children are actually clipped to the padding-box, but // since the overflow area should include the entire border-box, just set it // to the border-box size here. if (!overflowClipAxes.isEmpty()) {
aOverflowAreas.ApplyClipping(
bounds, overflowClipAxes,
OverflowClipMargin(overflowClipAxes, /* aAllowNegative = */ false));
}
// Nothing in here should affect scrollable overflow.
aOverflowAreas.InkOverflow() =
ComputeEffectsRect(this, aOverflowAreas.InkOverflow(), aNewSize);
// Absolute position clipping const nsStyleEffects* effects = StyleEffects();
Maybe<nsRect> clipPropClipRect = GetClipPropClipRect(disp, effects, aNewSize); if (clipPropClipRect) { for (constauto otype : AllOverflowTypes()) {
nsRect& o = aOverflowAreas.Overflow(otype);
o.IntersectRect(o, *clipPropClipRect);
}
}
/* If we're transformed, transform the overflow rect by the current
* transformation. */ if (hasTransform) {
SetProperty(nsIFrame::PreTransformOverflowAreasProperty(), new OverflowAreas(aOverflowAreas));
if (Combines3DTransformWithAncestors()) { /* If we're a preserve-3d leaf frame, then our pre-transform overflow *shouldbecorrect.Ourpost-transformoverflowisemptythough,because *weonlycontributetotheoverflowareaofthepreserve-3drootframe. *Ifwe'reanintermediateframethenthepre-transformoverflowshould *containallournon-preserve-3dchildren,whichiswhatwewant.Again *wehavenopost-transformoverflow.
*/
aOverflowAreas.SetAllTo(nsRect());
} else {
TransformReferenceBox refBox(this); for (constauto otype : AllOverflowTypes()) {
nsRect& o = aOverflowAreas.Overflow(otype); // If the overflow is empty, it can still have a non-zero length in one // axis. Transforming such axis-bound rect can cause the resulting rect // to be non-empty, e.g. by rotating the rect. if (!o.IsEmpty()) {
o = nsDisplayTransform::TransformRect(o, this, refBox);
}
}
/* If we're the root of the 3d context, then we want to include the *overflowareasofalltheparticipants.Thiswon'thavehappenedyetas *thecodeabovesettheiroverflowareatoempty.Manuallycollectthese *overflowareasnow.
*/ if (Extend3DContext(disp, effects)) {
ComputePreserve3DChildrenOverflow(aOverflowAreas);
}
}
} else {
RemoveProperty(nsIFrame::PreTransformOverflowAreasProperty());
}
/* Revert the size change in case some caller is depending on this. */
SetSize(oldSize, false);
if (anyOverflowChanged) {
SVGObserverUtils::InvalidateDirectRenderingObservers(this); if (nsBlockFrame* block = do_QueryFrame(this)) { // NOTE(emilio): we need to use BeforeReflow::Yes, because we want to // invalidate in cases where we _used_ to have an overflow marker and no // longer do. if (TextOverflow::CanHaveOverflowMarkers(
block, TextOverflow::BeforeReflow::Yes)) {
DiscardDisplayItems(this, [](nsDisplayItem* aItem) { return aItem->GetType() == DisplayItemType::TYPE_TEXT_OVERFLOW;
});
SchedulePaint(PAINT_DEFAULT);
}
}
} return anyOverflowChanged;
}
bool nsIFrame::RecomputePerspectiveChildrenOverflow( const nsIFrame* aStartFrame) { bool changed = false; for (constauto& childList : ChildLists()) { for (nsIFrame* child : childList.mList) { if (!child->FrameMaintainsOverflow()) { continue; // frame does not maintain overflow rects
} if (child->HasPerspective()) {
OverflowAreas* initialOverflow =
child->GetProperty(nsIFrame::InitialOverflowProperty()); const nsRect bounds(nsPoint(), child->GetSize());
OverflowAreas overflow; if (initialOverflow) {
overflow = *initialOverflow;
} else {
overflow.SetAllTo(bounds);
}
changed |= child->FinishAndStoreOverflow(overflow, bounds.Size());
} elseif (child->GetContent() == aStartFrame->GetContent() ||
child->GetClosestFlattenedTreeAncestorPrimaryFrame() ==
aStartFrame) { // If a frame is using perspective, then the size used to compute // perspective-origin is the size of the frame belonging to its parent // style. We must find any descendant frames using our size // (by recursing into frames that have the same containing block) // to update their overflow rects too. if (child->RecomputePerspectiveChildrenOverflow(aStartFrame)) {
changed = true; // Update the overflow for the child to take any changes from its // children into account.
child->UpdateOverflow();
}
}
}
} return changed;
}
void nsIFrame::ComputePreserve3DChildrenOverflow(
OverflowAreas& aOverflowAreas) { // Find all descendants that participate in the 3d context, and include their // overflow. These descendants have an empty overflow, so won't have been // included in the normal overflow calculation. Any children that don't // participate have normal overflow, so will have been included already.
nsRect childVisual;
nsRect childScrollable; for (constauto& childList : ChildLists()) { for (nsIFrame* child : childList.mList) { // If this child participates in the 3d context, then take the // pre-transform region (which contains all descendants that aren't // participating in the 3d context) and transform it into the 3d context // root coordinate space. if (child->Combines3DTransformWithAncestors()) {
OverflowAreas childOverflow = child->GetOverflowAreasRelativeToSelf();
TransformReferenceBox refBox(child); for (constauto otype : AllOverflowTypes()) {
nsRect& o = childOverflow.Overflow(otype);
o = nsDisplayTransform::TransformRect(o, child, refBox);
}
aOverflowAreas.UnionWith(childOverflow);
// If this child also extends the 3d context, then recurse into it // looking for more participants. if (child->Extend3DContext()) {
child->ComputePreserve3DChildrenOverflow(aOverflowAreas);
}
}
}
}
}
bool nsIFrame::IsScrollAnchor(ScrollAnchorContainer** aOutContainer) { if (!mInScrollAnchorChain) { returnfalse;
}
nsIFrame* f = this;
// FIXME(emilio, bug 1629280): We should find a non-null anchor if we have the // flag set, but bug 1629280 makes it so that we cannot really assert it / // make this just a `while (true)`, and uncomment the below assertion. while (auto* container = ScrollAnchorContainer::FindFor(f)) { // MOZ_ASSERT(f->IsInScrollAnchorChain()); if (nsIFrame* anchor = container->AnchorNode()) { if (anchor != this) { returnfalse;
} if (aOutContainer) {
*aOutContainer = container;
} returntrue;
}
/** *Thisfunctiontakesaframethatispartofablock-in-inlinesplit, *and_if_thatframeisananonymousblockcreatedbyanibsplitit *returnstheblock'sprecedinginline.Thisisneededbecausethe *splitinline'sstyleistheparentoftheanonymousblock'sstyle. * *IfaFrameisnotananonymousblock,nullisreturned.
*/ static nsIFrame* GetIBSplitSiblingForAnonymousBlock(const nsIFrame* aFrame) {
MOZ_ASSERT(aFrame, "Must have a non-null frame!");
NS_ASSERTION(aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT), "GetIBSplitSibling should only be called on ib-split frames");
if (aFrame->Style()->GetPseudoType() !=
PseudoStyleType::MozBlockInsideInlineWrapper) { // it's not an anonymous block return nullptr;
}
// Find the first continuation of the frame. (Ugh. This ends up // being O(N^2) when it is called O(N) times.)
aFrame = aFrame->FirstContinuation();
// For a table caption we want the _inner_ table frame (unless it's anonymous) // as the style parent. if (aFrame->IsTableCaption()) {
MOZ_ASSERT(parent->IsTableWrapperFrame());
nsTableFrame* innerTable = static_cast<const nsTableWrapperFrame*>(parent)->InnerTableFrame(); if (!innerTable->Style()->IsAnonBox()) { return innerTable;
}
}
// Table wrappers are always anon boxes; if we're in here for an outer // table, that actually means its the _inner_ table that wants to // know its parent. So get the pseudo of the inner in that case. auto pseudo = aFrame->Style()->GetPseudoType(); if (pseudo == PseudoStyleType::MozTableWrapper) {
MOZ_ASSERT(aFrame->IsTableWrapperFrame());
nsTableFrame* innerTable = static_cast<const nsTableWrapperFrame*>(aFrame)->InnerTableFrame();
pseudo = innerTable->Style()->GetPseudoType();
}
// Prevent a NAC pseudo-element from inheriting from its NAC parent, and // inherit from the NAC generator element instead. (We exclude element-backed // pseudos from this check, since they're not NAC.) if (pseudo != PseudoStyleType::NotPseudo &&
!PseudoStyle::IsElementBackedPseudo(pseudo)) {
MOZ_ASSERT(aFrame->GetContent());
Element* element = Element::FromNode(aFrame->GetContent()); // Make sure to only do the fixup for anonymous content pseudos (i.e. avoid // fixup for ::first-line and such). if (element && !element->IsRootOfNativeAnonymousSubtree() &&
element->GetPseudoElementType() == aFrame->Style()->GetPseudoType()) { while (parent->GetContent() &&
!parent->GetContent()->IsRootOfNativeAnonymousSubtree()) {
parent = parent->GetInFlowParent();
}
parent = parent->GetInFlowParent();
}
}
/* static */
nsIFrame* nsIFrame::CorrectStyleParentFrame(nsIFrame* aProspectiveParent,
PseudoStyleType aChildPseudo) {
MOZ_ASSERT(aProspectiveParent, "Must have a prospective parent");
if (aChildPseudo < PseudoStyleType::NotPseudo) { // Non-inheriting anon boxes have no style parent frame at all. if (PseudoStyle::IsNonInheritingAnonBox(aChildPseudo)) { return nullptr;
}
// Other anon boxes are parented to their actual parent already, except // for non-elements. Those should not be treated as an anon box. if (PseudoStyle::IsAnonBox(aChildPseudo) &&
!PseudoStyle::IsNonElement(aChildPseudo)) {
NS_ASSERTION(aChildPseudo != PseudoStyleType::MozBlockInsideInlineWrapper, "Should have dealt with kids that have " "NS_FRAME_PART_OF_IBSPLIT elsewhere"); return aProspectiveParent;
}
}
// Otherwise, walk up out of all anon boxes. For placeholder frames, walk out // of all pseudo-elements as well. Otherwise ReparentComputedStyle could // cause style data to be out of sync with the frame tree.
nsIFrame* parent = aProspectiveParent; do { if (parent->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) {
nsIFrame* sibling = GetIBSplitSiblingForAnonymousBlock(parent);
if (sibling) { // |parent| was a block in an {ib} split; use the inline as // |the style parent.
parent = sibling;
}
}
if (!parent->Style()->IsPseudoOrAnonBox()) { return parent;
}
if (!parent->Style()->IsAnonBox() && aChildPseudo != PseudoStyleType::MAX) { // nsPlaceholderFrame passes in PseudoStyleType::MAX for // aChildPseudo (even though that's not a valid pseudo-type) just to // trigger this behavior of walking up to the nearest non-pseudo // ancestor. return parent;
}
parent = parent->GetInFlowParent();
} while (parent);
if (aProspectiveParent->Style()->GetPseudoType() ==
PseudoStyleType::MozViewportScroll) { // aProspectiveParent is the scrollframe for a viewport // and the kids are the anonymous scrollbars return aProspectiveParent;
}
// We can get here if the root element is absolutely positioned. // We can't test for this very accurately, but it can only happen // when the prospective parent is a canvas frame.
NS_ASSERTION(aProspectiveParent->IsCanvasFrame(), "Should have found a parent before this"); return nullptr;
}
// Handle display:contents and the root frame, when there's no parent frame // to inherit from. if (MOZ_LIKELY(mContent)) {
Element* parentElement = mContent->GetFlattenedTreeParentElement(); if (MOZ_LIKELY(parentElement)) { auto pseudo = Style()->GetPseudoType(); if (pseudo == PseudoStyleType::NotPseudo || !mContent->IsElement() ||
(!PseudoStyle::IsAnonBox(pseudo) && // Ensure that we don't return the display:contents style // of the parent content for pseudos that have the same content // as their primary frame (like -moz-list-bullets do):
IsPrimaryFrame()) || /* if next is true then it's really a request for the table frame's
parent context, see nsTable[Outer]Frame::GetParentComputedStyle. */
pseudo == PseudoStyleType::MozTableWrapper) { // In some edge cases involving display: contents, we may end up here // for something that's pending to be reframed. In this case we return // the wrong style from here (because we've already lost track of it!), // but it's not a big deal as we're going to be reframed anyway. if (MOZ_LIKELY(parentElement->HasServoData()) &&
Servo_Element_IsDisplayContents(parentElement)) {
RefPtr<ComputedStyle> style =
ServoStyleSet::ResolveServoStyle(*parentElement); // NOTE(emilio): we return a weak reference because the element also // holds the style context alive. This is a bit silly (we could've // returned a weak ref directly), but it's probably not worth // optimizing, given this function has just one caller which is rare, // and this path is rare itself. return style;
}
}
} else { if (Style()->GetPseudoType() == PseudoStyleType::NotPseudo) { // We're a frame for the root. We have no style parent. return nullptr;
}
}
}
if (!HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) { /* *Ifthisframeisananonymousblockcreatedwhenaninlinewithablock *insideitgotsplit,thentheparentstyleisonitsprecedinginline.We *cangettoitusingGetIBSplitSiblingForAnonymousBlock.
*/ if (HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) {
nsIFrame* ibSplitSibling = GetIBSplitSiblingForAnonymousBlock(this); if (ibSplitSibling) { return (*aProviderFrame = ibSplitSibling)->Style();
}
}
// If this frame is one of the blocks that split an inline, we must // return the "special" inline parent, i.e., the parent that this // frame would have if we didn't mangle the frame structure.
*aProviderFrame = GetCorrectedParent(this); return *aProviderFrame ? (*aProviderFrame)->Style() : nullptr;
}
// We're an out-of-flow frame. For out-of-flow frames, we must // resolve underneath the placeholder's parent. The placeholder is // reached from the first-in-flow.
nsPlaceholderFrame* placeholder = FirstInFlow()->GetPlaceholderFrame(); if (!placeholder) {
MOZ_ASSERT_UNREACHABLE("no placeholder frame for out-of-flow frame");
*aProviderFrame = GetCorrectedParent(this); return *aProviderFrame ? (*aProviderFrame)->Style() : nullptr;
} return placeholder->GetParentComputedStyleForOutOfFlow(aProviderFrame);
}
void nsIFrame::GetLastLeaf(nsIFrame** aFrame) { if (!aFrame || !*aFrame) { return;
} for (nsIFrame* maybeLastLeaf = (*aFrame)->PrincipalChildList().LastChild();
maybeLastLeaf;) {
nsIFrame* lastChildNotInSubTree = nullptr; for (nsIFrame* child = maybeLastLeaf; child;
child = child->GetPrevSibling()) { // ignore anonymous elements, e.g. mozTableAdd* mozTableRemove* // see bug 278197 comment #12 #13 for details if (!child->ContentIsRootOfNativeAnonymousSubtree()) {
lastChildNotInSubTree = child; break;
}
} if (!lastChildNotInSubTree) { return;
}
*aFrame = lastChildNotInSubTree;
maybeLastLeaf = lastChildNotInSubTree->PrincipalChildList().LastChild();
}
}
void nsIFrame::GetFirstLeaf(nsIFrame** aFrame) { if (!aFrame || !*aFrame) { return;
}
nsIFrame* child = *aFrame; while (true) {
child = child->PrincipalChildList().FirstChild(); if (!child) { return; // nothing to do
}
*aFrame = child;
}
}
bool nsIFrame::IsFocusableDueToScrollFrame() { if (!IsScrollContainerFrame()) { if (nsFieldSetFrame* fieldset = do_QueryFrame(this)) { // TODO: Do we have similar special-cases like this where we can have // anonymous scrollable boxes hanging off a primary frame? if (nsIFrame* inner = fieldset->GetInner()) { return inner->IsFocusableDueToScrollFrame();
}
} returnfalse;
} if (!mContent->IsHTMLElement()) { returnfalse;
} if (Style()->IsPseudoElement()) { returnfalse;
} if (mContent->IsRootOfNativeAnonymousSubtree()) { returnfalse;
} if (!mContent->GetParent()) { returnfalse;
} if (mContent->AsElement()->HasAttr(nsGkAtoms::tabindex)) { returnfalse;
} // Elements with scrollable view are focusable with script & tabbable // Otherwise you couldn't scroll them with keyboard, which is an accessibility // issue (e.g. Section 508 rules) However, we don't make them to be focusable // with the mouse, because the extra focus outlines are considered // unnecessarily ugly. When clicked on, the selection position within the // element will be enough to make them keyboard scrollable. auto* scrollContainer = static_cast<ScrollContainerFrame*>(this); if (scrollContainer->GetScrollStyles().IsHiddenInBothDirections()) { returnfalse;
} if (scrollContainer->GetScrollRangeForUserInputEvents().IsEqualEdges(
nsRect())) { returnfalse;
} returntrue;
}
Focusable nsIFrame::IsFocusable(IsFocusableFlags aFlags) { // cannot focus content in print preview mode. Only the root can be focused, // but that's handled elsewhere. if (PresContext()->Type() == nsPresContext::eContext_PrintPreview) { return {};
}
if (!mContent || !mContent->IsElement()) { return {};
}
if (!(aFlags & IsFocusableFlags::IgnoreVisibility) &&
!IsVisibleConsideringAncestors()) { return {};
}
Focusable focusable; if (auto* xul = nsXULElement::FromNode(mContent)) { // As a legacy special-case, -moz-user-focus controls focusability and // tabability of XUL elements in some circumstances (which default to // -moz-user-focus: ignore). auto focusability = xul->GetXULFocusability(aFlags);
focusable.mFocusable =
focusability.mForcedFocusable.valueOr(uf == StyleUserFocus::Normal); if (focusable) {
focusable.mTabIndex = focusability.mForcedTabIndexIfFocusable.valueOr(0);
}
} else {
focusable = mContent->IsFocusableWithoutStyle(aFlags);
}
if (focusable) { return focusable;
}
// If we're focusing with the mouse we never focus scroll areas. if (!(aFlags & IsFocusableFlags::WithMouse) &&
IsFocusableDueToScrollFrame()) { return {true, 0};
}
// FIXME(emilio): some callers rely on somewhat broken return values // (focusable = false, but non-negative tab-index) from // IsFocusableWithoutStyle (for image maps in particular). return focusable;
}
static StyleAlignmentBaseline ConvertSVGDominantBaselineToAlignmentBaseline(
StyleDominantBaseline aDominantBaseline) { // Most of these are approximate mappings. switch (aDominantBaseline) { case StyleDominantBaseline::Hanging: case StyleDominantBaseline::TextTop: return StyleAlignmentBaseline::TextTop; case StyleDominantBaseline::TextBottom: case StyleDominantBaseline::Ideographic: return StyleAlignmentBaseline::TextBottom; case StyleDominantBaseline::Central: case StyleDominantBaseline::Middle: case StyleDominantBaseline::Mathematical: return StyleAlignmentBaseline::Middle; case StyleDominantBaseline::Auto: case StyleDominantBaseline::Alphabetic: return StyleAlignmentBaseline::Baseline; default:
MOZ_ASSERT_UNREACHABLE("unexpected aDominantBaseline value"); return StyleAlignmentBaseline::Baseline;
}
}
StyleDominantBaseline nsIFrame::DominantBaseline() const { auto dominantBaseline = StyleVisibility()->mDominantBaseline; if (dominantBaseline != StyleDominantBaseline::Auto) { return dominantBaseline;
} // https://drafts.csswg.org/css-inline-3/#alignment-baseline-property // > Equivalent to alphabetic in horizontal writing modes and // > in vertical writing modes when text-orientation is sideways. // > Equivalent to central in vertical writing modes when // > text-orientation is mixed or upright. return GetWritingMode().IsCentralBaseline()
? StyleDominantBaseline::Central
: StyleDominantBaseline::Alphabetic;
}
StyleAlignmentBaseline nsIFrame::AlignmentBaseline() const { if (IsInSVGTextSubtree()) { // TODO Bug 2010721 - Implement alignment-baseline directly for SVG // text elements instead of only approximating from dominant-baseline. auto dominantBaseline = StyleVisibility()->mDominantBaseline; return ConvertSVGDominantBaselineToAlignmentBaseline(dominantBaseline);
}
if (IsTableCellFrame()) { // The alignment-baseline / vertical-align property on a table cell is used // to align its children (see nsTableCellFrame::GetTableCellAlignment), but // does not affect the alignment of the cell itself within its row. return StyleAlignmentBaseline::Baseline;
}
if (StyleDisplay()->mAlignmentBaseline == StyleAlignmentBaseline::Baseline) { // The CSS Inline Layout specification says `alignment-baseline: baseline` // uses the dominant baseline choice of the parent. auto dominantBaseline =
GetParent() ? GetParent()->DominantBaseline() : DominantBaseline(); switch (dominantBaseline) { case StyleDominantBaseline::TextBottom: return StyleAlignmentBaseline::TextBottom; case StyleDominantBaseline::Alphabetic: return StyleAlignmentBaseline::Alphabetic; case StyleDominantBaseline::Ideographic: return StyleAlignmentBaseline::Ideographic; case StyleDominantBaseline::Middle: return StyleAlignmentBaseline::Middle; case StyleDominantBaseline::Central: return StyleAlignmentBaseline::Central; case StyleDominantBaseline::Mathematical: return StyleAlignmentBaseline::Mathematical; case StyleDominantBaseline::Hanging: return StyleAlignmentBaseline::Hanging; case StyleDominantBaseline::TextTop: return StyleAlignmentBaseline::TextTop; case StyleDominantBaseline::Auto:
MOZ_ASSERT_UNREACHABLE("Auto is resolved in DominantBaseline()"); break;
}
}
return StyleDisplay()->mAlignmentBaseline;
}
void nsIFrame::UpdateStyleOfChildAnonBox(nsIFrame* aChildFrame,
ServoRestyleState& aRestyleState) { #ifdef DEBUG
nsIFrame* parent = aChildFrame->GetInFlowParent(); if (aChildFrame->IsTableFrame()) {
parent = parent->GetParent();
} if (parent->IsLineFrame()) {
parent = parent->GetParent();
}
MOZ_ASSERT(nsLayoutUtils::FirstContinuationOrIBSplitSibling(parent) == this, "This should only be used for children!"); #endif// DEBUG
MOZ_ASSERT(!GetContent() || !aChildFrame->GetContent() ||
aChildFrame->GetContent() == GetContent(), "What content node is it a frame for?");
MOZ_ASSERT(!aChildFrame->GetPrevContinuation(), "Only first continuations should end up here");
// We could force the caller to pass in the pseudo, since some callers know it // statically... But this API is a bit nicer. auto pseudo = aChildFrame->Style()->GetPseudoType();
MOZ_ASSERT(PseudoStyle::IsAnonBox(pseudo), "Child is not an anon box?");
MOZ_ASSERT(!PseudoStyle::IsNonInheritingAnonBox(pseudo), "Why did the caller bother calling us?");
// Anon boxes inherit from their parent; that's us.
RefPtr<ComputedStyle> newContext =
aRestyleState.StyleSet().ResolveInheritingAnonymousBoxStyle(pseudo,
Style());
// Now that we've updated the style on aChildFrame, check whether it itself // has anon boxes to deal with.
ServoRestyleState childrenState(*aChildFrame, aRestyleState, childHint,
ServoRestyleState::CanUseHandledHints::Yes);
aChildFrame->UpdateStyleOfOwnedAnonBoxes(childrenState);
// Assuming anon boxes don't have ::backdrop associated with them... if that // ever changes, we'd need to handle that here, like we do in // RestyleManager::ProcessPostTraversal
// We do need to handle block pseudo-elements here, though. Especially list // bullets. if (nsBlockFrame* block = do_QueryFrame(aChildFrame)) {
block->UpdatePseudoElementStyles(childrenState);
}
}
/* static */
nsChangeHint nsIFrame::UpdateStyleOfOwnedChildFrame(
nsIFrame* aChildFrame, ComputedStyle* aNewComputedStyle,
ServoRestyleState& aRestyleState, const Maybe<ComputedStyle*>& aContinuationComputedStyle) { // Figure out whether we have an actual change. It's important that we do // this, for several reasons: // // 1) Even if all the child's changes are due to properties it inherits from // us, it's possible that no one ever asked us for those style structs and // hence changes to them aren't reflected in the changes handled at all. // // 2) Content can change stylesheets that change the styles of pseudos, and // extensions can add/remove stylesheets that change the styles of // anonymous boxes directly.
uint32_t equalStructs; // Not used, actually.
nsChangeHint childHint = aChildFrame->Style()->CalcStyleDifference(
*aNewComputedStyle, &equalStructs);
// If aChildFrame is out of flow, then aRestyleState's "changes handled by the // parent" doesn't apply to it, because it may have some other parent in the // frame tree. if (!aChildFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
childHint = NS_RemoveSubsumedHints(
childHint, aRestyleState.ChangesHandledFor(aChildFrame));
} if (childHint) { if (childHint & nsChangeHint_ReconstructFrame) { // If we generate a reconstruct here, remove any non-reconstruct hints we // may have already generated for this content.
aRestyleState.ChangeList().PopChangesForContent(
aChildFrame->GetContent());
}
aRestyleState.ChangeList().AppendChange(
aChildFrame, aChildFrame->GetContent(), childHint);
}
if (aFrame->TrackingVisibility()) { // We assume all frames in popups are visible, so this decrement balances // out the increment in AddInPopupStateBitToDescendants above.
aFrame->DecApproximateVisibleCount();
} for (constauto& childList : aFrame->CrossDocChildLists()) { for (nsIFrame* child : childList.mList) {
RemoveInPopupStateBitFromDescendants(child);
}
}
}
void nsIFrame::SetParent(nsContainerFrame* aParent) { // If our parent is a wrapper anon box, our new parent should be too. We // _can_ change parent if our parent is a wrapper anon box, because some // wrapper anon boxes can have continuations.
MOZ_ASSERT_IF(ParentIsWrapperAnonBox(),
aParent->Style()->IsInheritingAnonBox());
// Note that the current mParent may already be destroyed at this point.
mParent = aParent;
MOZ_ASSERT(!mParent || PresShell() == mParent->PresShell());
nsFrameState flagsToPropagateSameDoc =
GetStateBits() & (NS_FRAME_CONTAINS_RELATIVE_BSIZE |
NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE); if (flagsToPropagateSameDoc) { for (nsIFrame* f = aParent; f; f = f->GetParent()) { if (f->HasAllStateBits(flagsToPropagateSameDoc)) { break;
}
f->AddStateBits(flagsToPropagateSameDoc);
}
}
if (HasInvalidFrameInSubtree()) { for (nsIFrame* f = aParent;
f && !f->HasAnyStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT |
NS_FRAME_IS_NONDISPLAY);
f = nsLayoutUtils::GetCrossDocParentFrameInProcess(f)) {
f->AddStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT);
}
}
if (aParent->HasAnyStateBits(NS_FRAME_IN_POPUP)) {
AddInPopupStateBitToDescendants(this);
} else {
RemoveInPopupStateBitFromDescendants(this);
}
// If our new parent only has invalid children, then we just invalidate // ourselves too. This is probably faster than clearing the flag all // the way up the frame tree. if (aParent->HasAnyStateBits(NS_FRAME_ALL_DESCENDANTS_NEED_PAINT)) {
InvalidateFrame();
} else {
SchedulePaint();
}
}
bool nsIFrame::IsStackingContext(const nsStyleDisplay* aStyleDisplay, const nsStyleEffects* aStyleEffects) { // Properties that influence the output of this function should be handled in // change_bits_for_longhand as well. if (HasOpacity(aStyleDisplay, aStyleEffects, nullptr)) { returntrue;
} if (IsTransformed()) { returntrue;
} auto willChange = aStyleDisplay->mWillChange.bits; if (aStyleDisplay->IsContainPaint() || aStyleDisplay->IsContainLayout() ||
willChange & StyleWillChangeBits::CONTAIN) { if (SupportsContainLayoutAndPaint()) { returntrue;
}
}
if (ForcesStackingContextForViewTransition()) { returntrue;
}
// strictly speaking, 'perspective' doesn't require visual atomicity, // but the spec says it acts like the rest of these if (aStyleDisplay->HasPerspectiveStyle() ||
willChange & StyleWillChangeBits::PERSPECTIVE) { if (SupportsCSSTransforms()) { returntrue;
}
} if (!StylePosition()->mZIndex.IsAuto() ||
willChange & StyleWillChangeBits::Z_INDEX) { if (ZIndexApplies()) { returntrue;
}
} // Elements captured in a view transition during a view transition or whose // view-transition-name computed value is not none (at any time) form a s // https://drafts.csswg.org/css-view-transitions-1/#named-and-transitioning return aStyleEffects->mMixBlendMode != StyleBlend::Normal ||
SVGIntegrationUtils::UsingEffectsForFrame(this) ||
aStyleDisplay->IsPositionForcingStackingContext() ||
aStyleDisplay->mIsolation != StyleIsolation::Auto ||
willChange & StyleWillChangeBits::STACKING_CONTEXT_UNCONDITIONAL;
}
staticbool IsFrameRectScrolledOutOfView(const nsIFrame* aTarget, const nsRect& aTargetRect, const nsIFrame* aParent) { // The ancestor frame we are checking if it clips out aTargetRect relative to // aTarget.
nsIFrame* clipParent = nullptr;
// find the first scrollable frame or root frame if we are in a fixed pos // subtree for (nsIFrame* f = const_cast<nsIFrame*>(aParent); f;
f = nsLayoutUtils::GetCrossDocParentFrameInProcess(f)) {
ScrollContainerFrame* scrollContainerFrame = do_QueryFrame(f); if (scrollContainerFrame) {
clipParent = f; break;
} if (f->StyleDisplay()->mPosition == StylePositionProperty::Fixed &&
nsLayoutUtils::IsReallyFixedPos(f)) {
clipParent = f->GetParent(); break;
}
}
if (!clipParent) { // Even if we couldn't find the nearest scrollable frame, it might mean we // are in an out-of-process iframe, try to see if |aTarget| frame is // scrolled out of view in an scrollable frame in a cross-process ancestor // document. return nsLayoutUtils::FrameRectIsScrolledOutOfViewInCrossProcess(
aTarget, aTargetRect);
}
nsRect clipRect = clipParent->InkOverflowRectRelativeToSelf(); // We consider that the target is scrolled out if the scrollable (or root) // frame is empty. if (clipRect.IsEmpty()) { returntrue;
}
if (transformedRect.IsEmpty()) { // If the transformed rect is empty it represents a line or a point that we // should check is outside the the scrollable rect. if (transformedRect.x > clipRect.XMost() ||
transformedRect.y > clipRect.YMost() ||
clipRect.x > transformedRect.XMost() ||
clipRect.y > transformedRect.YMost()) { returntrue;
}
} elseif (!transformedRect.Intersects(clipRect)) { returntrue;
}
nsIFrame* parent = clipParent->GetParent(); if (!parent) { returnfalse;
}
gfx::Matrix result2d; if (!matrix.CanDraw2D(&result2d)) { // FIXME: It would be preferable to reject non-2D transforms at parse time.
NS_WARNING( "-moz-window-transform does not describe a 2D transform, " "but only 2d transforms are supported"); return gfx::Matrix();
}
return result2d;
}
void nsIFrame::DoUpdateStyleOfOwnedAnonBoxes(ServoRestyleState& aRestyleState) { // As a special case, we check for {ib}-split block frames here, rather // than have an nsInlineFrame::AppendDirectlyOwnedAnonBoxes implementation // that returns them. // // (If we did handle them in AppendDirectlyOwnedAnonBoxes, we would have to // return *all* of the in-flow {ib}-split block frames, not just the first // one. For restyling, we really just need the first in flow, and the other // user of the AppendOwnedAnonBoxes API, AllChildIterator, doesn't need to // know about them at all, since these block frames never create NAC. So we // avoid any unncessary hashtable lookups for the {ib}-split frames by calling // UpdateStyleOfOwnedAnonBoxesForIBSplit directly here.) if (IsInlineFrame()) { if (HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) { static_cast<nsInlineFrame*>(this)->UpdateStyleOfOwnedAnonBoxesForIBSplit(
aRestyleState);
} return;
}
/* virtual */ void nsIFrame::AppendDirectlyOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult) {
MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_OWNS_ANON_BOXES));
MOZ_ASSERT_UNREACHABLE( "Subclasses that have directly owned anonymous boxes should override " "this method!");
}
void nsIFrame::DoAppendOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult) {
size_t i = aResult.Length();
AppendDirectlyOwnedAnonBoxes(aResult);
// After appending the directly owned anonymous boxes of this frame to // aResult above, we need to check each of them to see if they own // any anonymous boxes themselves. Note that we keep progressing // through aResult, looking for additional entries in aResult from these // subsequent AppendDirectlyOwnedAnonBoxes calls. (Thus we can't // use a ranged for loop here.)
while (i < aResult.Length()) {
nsIFrame* f = aResult[i].mAnonBoxFrame; if (f->HasAnyStateBits(NS_FRAME_OWNS_ANON_BOXES)) {
f->AppendDirectlyOwnedAnonBoxes(aResult);
}
++i;
}
}
// We don't do this for Gecko because this stuff is stored in the nsPresArena // and so measured elsewhere. if (!aSizes.mState.HaveSeenPtr(mComputedStyle)) {
mComputedStyle->AddSizeOfIncludingThis(aSizes,
&aSizes.mLayoutComputedValuesNonDom);
}
for (constauto& childList : ChildLists()) { for (const nsIFrame* f : childList.mList) {
f->AddSizeOfExcludingThisForTree(aSizes);
}
}
}
ScrollContainerFrame* scrollContainerFrame =
nsLayoutUtils::GetScrollContainerFrameFor(this); if (scrollContainerFrame) { // If this frame is the scrolled frame of a scroll container frame, then we // need to pick up the area corresponding to the overflow rect as well. // Otherwise the parts of the overflow that are not occupied by descendants // get skipped and the APZ code sends touch events to the content underneath // instead. See https://bugzilla.mozilla.org/show_bug.cgi?id=1127773#c15.
area = ScrollableOverflowRect();
} else {
area = GetRectRelativeToSelf();
}
if (!area.IsEmpty()) { return area + aBuilder->ToReferenceFrame(this);
}
return area;
}
CompositorHitTestInfo nsIFrame::GetCompositorHitTestInfo(
nsDisplayListBuilder* aBuilder) {
CompositorHitTestInfo result = CompositorHitTestInvisibleToHit; if (Style()->PointerEvents() == StylePointerEvents::None) { return result;
} return GetCompositorHitTestInfoWithoutPointerEvents(aBuilder);
}
CompositorHitTestInfo nsIFrame::GetCompositorHitTestInfoWithoutPointerEvents(
nsDisplayListBuilder* aBuilder) {
CompositorHitTestInfo result = CompositorHitTestInvisibleToHit;
if (aBuilder->IsInsidePointerEventsNoneDoc() ||
aBuilder->IsInViewTransitionCapture()) { // Somewhere up the parent document chain is a subdocument with pointer- // events:none set on it, or we're getting captured in a view transition. return result;
} if (!GetParent()) {
MOZ_ASSERT(IsViewportFrame()); // Viewport frames are never event targets, other frames, like canvas // frames, are the event targets for any regions viewport frames may cover. return result;
} if (!StyleVisibility()->IsVisible()) { return result;
}
// Anything that didn't match the above conditions is visible to hit-testing.
result = CompositorHitTestFlags::eVisibleToHitTest;
SVGUtils::MaskUsage maskUsage = SVGUtils::DetermineMaskUsage(this, false); if (maskUsage.UsingMaskOrClipPath()) { // If WebRender is enabled, simple clip-paths can be converted into WR // clips that WR knows how to hit-test against, so we don't need to mark // it as an irregular area. if (!maskUsage.IsSimpleClipShape()) {
result += CompositorHitTestFlags::eIrregularArea;
}
}
if (aBuilder->IsBuildingNonLayerizedScrollbar()) { // Scrollbars may be painted into a layer below the actual layer they will // scroll, and therefore wheel events may be dispatched to the outer frame // instead of the intended scrollframe. To address this, we force a d-t-c // region on scrollbar frames that won't be placed in their own layer. See // bug 1213324 for details.
result += CompositorHitTestFlags::eInactiveScrollframe;
} elseif (aBuilder->GetAncestorHasApzAwareEventHandler()) {
result += CompositorHitTestFlags::eApzAwareListeners;
} elseif (IsRangeFrame()) { // Range frames handle touch events directly without having a touch listener // so we need to let APZ know that this area cares about events.
result += CompositorHitTestFlags::eApzAwareListeners;
}
if (aBuilder->IsTouchEventPrefEnabledDoc()) { // Inherit the touch-action flags from the parent, if there is one. We do // this because of how the touch-action on a frame combines the touch-action // from ancestor DOM elements. Refer to the documentation in // TouchActionHelper.cpp for details; this code is meant to be equivalent to // that code, but woven into the top-down recursive display list building // process.
CompositorHitTestInfo inheritedTouchAction =
aBuilder->GetInheritedCompositorHitTestInfo() &
CompositorHitTestTouchActionMask;
nsIFrame* touchActionFrame = this; if (ScrollContainerFrame* scrollContainerFrame =
nsLayoutUtils::GetScrollContainerFrameFor(this)) {
ScrollStyles ss = scrollContainerFrame->GetScrollStyles(); if (ss.mVertical != StyleOverflow::Hidden ||
ss.mHorizontal != StyleOverflow::Hidden) {
touchActionFrame = scrollContainerFrame; // On scrollframes, stop inheriting the pan-x and pan-y flags; instead, // reset them back to zero to allow panning on the scrollframe unless we // encounter an element that disables it that's inside the scrollframe. // This is equivalent to the |considerPanning| variable in // TouchActionHelper.cpp, but for a top-down traversal.
CompositorHitTestInfo panMask(
CompositorHitTestFlags::eTouchActionPanXDisabled,
CompositorHitTestFlags::eTouchActionPanYDisabled);
inheritedTouchAction -= panMask;
}
}
result += inheritedTouchAction;
const StyleTouchAction touchAction = touchActionFrame->UsedTouchAction(); // The CSS allows the syntax auto | none | [pan-x || pan-y] | manipulation // so we can eliminate some combinations of things. if (touchAction == StyleTouchAction::AUTO) { // nothing to do
} elseif (touchAction & StyleTouchAction::MANIPULATION) {
result += CompositorHitTestFlags::eTouchActionAnimatingZoomDisabled;
} else { // This path handles the cases none | [pan-x || pan-y || pinch-zoom] so // double-tap is disabled in here. if (!(touchAction & StyleTouchAction::PINCH_ZOOM)) {
result += CompositorHitTestFlags::eTouchActionPinchZoomDisabled;
}
result += CompositorHitTestFlags::eTouchActionAnimatingZoomDisabled;
if (!(touchAction & StyleTouchAction::PAN_X)) {
result += CompositorHitTestFlags::eTouchActionPanXDisabled;
} if (!(touchAction & StyleTouchAction::PAN_Y)) {
result += CompositorHitTestFlags::eTouchActionPanYDisabled;
} if (touchAction & StyleTouchAction::NONE) { // all the touch-action disabling flags will already have been set above
MOZ_ASSERT(result.contains(CompositorHitTestTouchActionMask));
}
}
}
const Maybe<ScrollDirection> scrollDirection =
aBuilder->GetCurrentScrollbarDirection(); if (scrollDirection.isSome()) { if (GetContent()->IsXULElement(nsGkAtoms::thumb)) { constbool thumbGetsLayer = aBuilder->GetCurrentScrollbarTarget() !=
layers::ScrollableLayerGuid::NULL_SCROLL_ID; if (thumbGetsLayer) {
result += CompositorHitTestFlags::eScrollbarThumb;
} else {
result += CompositorHitTestFlags::eInactiveScrollframe;
}
}
if (*scrollDirection == ScrollDirection::eVertical) {
result += CompositorHitTestFlags::eScrollbarVertical;
}
// includes the ScrollbarFrame, SliderFrame, anything else that // might be inside the xul:scrollbar
result += CompositorHitTestFlags::eScrollbar;
}
return result;
}
// Returns true if we can guarantee there is no visible descendants. staticbool HasNoVisibleDescendants(const nsIFrame* aFrame) { for (constauto& childList : aFrame->ChildLists()) { for (nsIFrame* f : childList.mList) { if (nsPlaceholderFrame::GetRealFrameFor(f)
->IsVisibleOrMayHaveVisibleDescendants()) { returnfalse;
}
}
} returntrue;
}
if (IsScrollContainerOrSubclass()) { // Scrollers deal with overflow on their own. return {};
}
// 'contain:paint', which we handle as 'overflow:clip' here. 'contain:paint' // should prevent all means of escaping that clipping (e.g. because it forms a // fixed-pos containing block). if (aDisp->IsContainPaint() && SupportsContainLayoutAndPaint()) { return kPhysicalAxesBoth;
}
// and overflow: scrollable that we should interpret as clip if (aDisp->IsScrollableOverflow()) { // REVIEW: these are the frame types that set up clipping.
LayoutFrameType type = Type(); switch (type) { case LayoutFrameType::CheckboxRadio: case LayoutFrameType::ComboboxControl: case LayoutFrameType::Progress: case LayoutFrameType::Range: case LayoutFrameType::SubDocument: case LayoutFrameType::SVGForeignObject: case LayoutFrameType::SVGInnerSVG: case LayoutFrameType::SVGOuterSVG: case LayoutFrameType::SVGSymbol: case LayoutFrameType::Image: case LayoutFrameType::HTMLVideo: case LayoutFrameType::TableCell: return kPhysicalAxesBoth; case LayoutFrameType::Table: // Tables, for legacy reasons only clip when hidden in both directions, // and treat all other scrollable overflow values as `visible`. // This is (somewhat, since other browsers make this change at // computed-value time, see bug 1918789) interoperable css2-era // behavior. We might be able to change this, but not today. // See layout/reftests/table-overflow/bug785684-x.html return aDisp->mOverflowX == StyleOverflow::Hidden &&
aDisp->mOverflowY == StyleOverflow::Hidden
? kPhysicalAxesBoth
: PhysicalAxes(); case LayoutFrameType::TextInput: // It has an anonymous scroll container frame that handles any overflow. return PhysicalAxes(); default: break;
} if (IsSuppressedScrollableBlockForPrint()) { return kPhysicalAxesBoth;
}
}
if (aDisp->mOverflowX == StyleOverflow::Clip ||
aDisp->mOverflowY == StyleOverflow::Clip) { // FIXME: we could use GetViewportScrollStylesOverrideElement() here instead // if that worked correctly in a print context. (see bug 1654667) constauto* element = Element::FromNodeOrNull(GetContent()); if (!element ||
!PresContext()->ElementWouldPropagateScrollStyles(*element)) {
PhysicalAxes axes; if (aDisp->mOverflowX == StyleOverflow::Clip) {
axes += PhysicalAxis::Horizontal;
} if (aDisp->mOverflowY == StyleOverflow::Clip) {
axes += PhysicalAxis::Vertical;
} return axes;
}
}
return PhysicalAxes();
}
bool nsIFrame::IsSuppressedScrollableBlockForPrint() const { // This condition needs to match the suppressScrollFrame logic in the frame // constructor. if (!PresContext()->IsPaginated() || !IsBlockFrame() ||
!StyleDisplay()->IsScrollableOverflow() ||
!StyleDisplay()->IsBlockOutsideStyle() ||
mContent->IsInNativeAnonymousSubtree()) { returnfalse;
} if (auto* element = Element::FromNode(mContent);
element && PresContext()->ElementWouldPropagateScrollStyles(*element)) { returnfalse;
} returntrue;
}
PhysicalAxes nsIFrame::GetAnchorPosCompensatingForScroll() const { if (!HasAnchorPosReference()) { return {};
} constauto* prop = GetProperty(AnchorPosReferences()); if (!prop) { return {};
}
return prop->CompensatingForScrollAxes();
}
bool nsIFrame::HasUnreflowedContainerQueryAncestor() const { // If this frame has done the first reflow, its ancestors are guaranteed to // have as well. if (!HasAnyStateBits(NS_FRAME_FIRST_REFLOW) ||
!PresContext()->HasContainerQueryFrames()) { returnfalse;
} for (nsIFrame* cur = GetInFlowParent(); cur; cur = cur->GetInFlowParent()) { if (!cur->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) { // Done first reflow from this ancestor up, including query containers. returnfalse;
} if (cur->StyleDisplay()->IsQueryContainer()) { returntrue;
}
} // No query container from this frame up to root. returnfalse;
}
bool nsIFrame::ShouldBreakBetween(const nsStyleDisplay* aDisplay, const StyleBreakBetween aBreakBetween, const BreakType aBreakType) const { constbool shouldBreakBetween = [&] { switch (aBreakBetween) { case StyleBreakBetween::Always: returntrue; case StyleBreakBetween::Auto: case StyleBreakBetween::Avoid: returnfalse; case StyleBreakBetween::Page: case StyleBreakBetween::Left: case StyleBreakBetween::Right: return aBreakType == BreakType::Page;
}
MOZ_ASSERT_UNREACHABLE("Unknown break-between value!"); returnfalse;
}();
if (!shouldBreakBetween) { returnfalse;
} if (IsAbsolutelyPositioned(aDisplay)) { // 'break-before' and 'break-after' properties does not apply to // absolutely-positioned boxes. returnfalse;
} returntrue;
}
void nsIFrame::VerifyDirtyBitSet(const nsFrameList& aFrameList) { for (nsIFrame* f : aFrameList) {
NS_ASSERTION(f->HasAnyStateBits(NS_FRAME_IS_DIRTY), "dirty bit not set");
}
}
// Validation of SideIsVertical. # define CASE(side, result) \
static_assert(SideIsVertical(side) == result, "SideIsVertical is wrong") CASE(eSideTop, false); CASE(eSideRight, true); CASE(eSideBottom, false); CASE(eSideLeft, true); # undef CASE
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen1.225Angebot
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-09-28)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.