/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/** * Note: the log module is created during library initialization which * means that you cannot perform logging before then.
*/
mozilla::LazyLogModule nsIFrame::sFrameLogModule("frame");
nsAbsoluteContainingBlock* nsIFrame::GetAbsoluteContainingBlock() const {
NS_ASSERTION(IsAbsoluteContainer(), "The frame is not marked as an abspos container correctly");
nsAbsoluteContainingBlock* 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 nsAbsoluteContainingBlock(GetAbsoluteListID()));
}
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 (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");
}
staticbool IsFontSizeInflationContainer(nsIFrame* aFrame, const nsStyleDisplay* aStyleDisplay) { /* * Font size inflation is built around the idea that we're inflating * the fonts for a pan-and-zoom UI so that when the user scales up a * block or other container to fill the width of the device, the fonts * will be readable. To do this, we need to pick what counts as a * container. * * From a code perspective, the only hard requirement is that frames * that are line participants (nsIFrame::IsLineParticipant) are never * containers, since line layout assumes that the inflation is consistent * within a line. * * This is not an imposition, since we obviously want a bunch of text * (possibly with inline elements) flowing within a block to count the * block (or higher) as its container. * * We also want form controls, including the text in the anonymous * content inside of them, to match each other and the text next to * them, so they and their anonymous content should also not be a * container. * * However, because we can't reliably compute sizes across XUL during * reflow, any XUL frame with a XUL parent is always a container. * * There are contexts where it would be nice if some blocks didn't * count as a container, so that, for example, an indented quotation * didn't end up with a smaller font size. However, it's hard to * distinguish these situations where we really do want the indented * thing to count as a container, so we don't try, and blocks are * always containers.
*/
// The root frame should always be an inflation container. if (!aFrame->GetParent()) { returntrue;
}
nsIContent* content = aFrame->GetContent(); if (content && content->IsInNativeAnonymousSubtree()) { // Native anonymous content shouldn't be a font inflation root, // except for the canvas custom content container.
nsCanvasFrame* canvas = aFrame->PresShell()->GetCanvasFrame(); return canvas && canvas->GetCustomContentContainer() == content;
}
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.
// Copy other bits in nsIFrame from prev-in-flow.
mHasColumnSpanSiblings = aPrevInFlow->HasColumnSpanSiblings();
} 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_INDEPENDENT_SELECTION |
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);
}
}
constbool wasQueryContainer = oldDisp && oldDisp->IsQueryContainer(); constbool isQueryContainer = disp->IsQueryContainer(); if (wasQueryContainer != isQueryContainer) { auto* pc = PresContext(); if (isQueryContainer) {
pc->RegisterContainerQueryFrame(this);
} else {
pc->UnregisterContainerQueryFrame(this);
}
}
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();
}
HandleLastRememberedSize();
}
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::GetStickyScrollContainerForFrame(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);
}
}
// 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);
}
nsView* view = GetView(); if (view) {
view->SetFrame(nullptr);
view->Destroy();
}
// 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;
}
if (!nsLayoutUtils::AreRetainedDisplayListsEnabled()) { // Retained display lists are disabled, no need to update // RetainedDisplayListData. return;
}
for (nsDisplayItem* i : DisplayItems()) { if (i->GetDependentFrame() == this && !i->HasDeletedFrame()) {
i->Frame()->MarkNeedsDisplayItemRebuild();
}
i->RemoveFrame(this);
}
DisplayItems().Clear();
nsAutoString name; #ifdef DEBUG_FRAME_DUMP if (DL_LOG_TEST(LogLevel::Debug)) {
GetFrameName(name);
} #endif
DL_LOGV("Removing display item data for frame %p (%s)", this,
NS_ConvertUTF16toUTF8(name).get());
auto* data = builder->Data(); if (MayHaveWillChangeBudget()) { // Keep the frame in list, so it can be removed from the will-change budget.
data->Flags(this) = RetainedDisplayListData::FrameFlag::HadWillChange;
} else {
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->StyleImageLoader(); // 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);
}
if (handleStickyChange && !HasAnyStateBits(NS_FRAME_IS_NONDISPLAY) &&
!GetPrevInFlow()) { // Note that we only add first continuations, but we really only // want to add first continuation-or-ib-split-siblings. But since we don't // yet know if we're a later part of a block-in-inline split, we'll just // add later members of a block-in-inline split here, and then // StickyScrollContainer will remove them later. if (auto* ssc =
StickyScrollContainer::GetStickyScrollContainerForFrame(this)) { if (disp->mPosition == StylePositionProperty::Sticky) {
ssc->AddFrame(this);
} else {
ssc->RemoveFrame(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);
}
void nsIFrame::HandleLastRememberedSize() {
MOZ_ASSERT(IsPrimaryFrame()); // Storing a last remembered size requires contain-intrinsic-size. if (!StaticPrefs::layout_css_contain_intrinsic_size_enabled()) { return;
} auto* element = Element::FromNodeOrNull(mContent); if (!element) { return;
} const WritingMode wm = GetWritingMode(); const nsStylePosition* stylePos = StylePosition(); bool canRememberBSize = stylePos->ContainIntrinsicBSize(wm).HasAuto(); bool canRememberISize = stylePos->ContainIntrinsicISize(wm).HasAuto(); if (!canRememberBSize) {
element->RemoveLastRememberedBSize();
} if (!canRememberISize) {
element->RemoveLastRememberedISize();
} if ((canRememberBSize || canRememberISize) && !HidesContent()) { bool isNonReplacedInline = IsLineParticipant() && !IsReplaced(); if (!isNonReplacedInline) {
PresContext()->Document()->ObserveForLastRememberedSize(*element); return;
}
}
PresContext()->Document()->UnobserveForLastRememberedSize(*element);
}
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED void nsIFrame::AssertNewStyleIsSane(ComputedStyle& aNewStyle) {
MOZ_DIAGNOSTIC_ASSERT(
aNewStyle.GetPseudoType() == mComputedStyle->GetPseudoType() || // ::first-line continuations are weird, this should probably be fixed via // bug 1465474.
(mComputedStyle->GetPseudoType() == PseudoStyleType::firstLine &&
aNewStyle.GetPseudoType() == PseudoStyleType::mozLineFrame) || // ::first-letter continuations are broken, in particular floating ones, // see bug 1490281. The construction code tries to fix this up after the // fact, then restyling undoes it...
(mComputedStyle->GetPseudoType() == PseudoStyleType::mozText &&
aNewStyle.GetPseudoType() == PseudoStyleType::firstLetterContinuation) ||
(mComputedStyle->GetPseudoType() ==
PseudoStyleType::firstLetterContinuation &&
aNewStyle.GetPseudoType() == PseudoStyleType::mozText));
} #endif
void nsIFrame::ReparentFrameViewTo(nsViewManager* aViewManager,
nsView* aNewParentView) { if (HasView()) { if (IsMenuPopupFrame()) { // This view must be parented by the root view, don't reparent it. return;
}
nsView* view = GetView();
aViewManager->RemoveChild(view);
// The view will remember the Z-order and other attributes that have been // set on it.
nsView* insertBefore =
nsLayoutUtils::FindSiblingViewFor(aNewParentView, this);
aViewManager->InsertChild(aNewParentView, view, insertBefore,
insertBefore != nullptr);
} elseif (HasAnyStateBits(NS_FRAME_HAS_CHILD_WITH_VIEW)) { for (constauto& childList : ChildLists()) { // Iterate the child frames, and check each child frame to see if it has // a view for (nsIFrame* child : childList.mList) {
child->ReparentFrameViewTo(aViewManager, aNewParentView);
}
}
}
}
void nsIFrame::SyncFrameViewProperties(nsView* aView) { if (!aView) {
aView = GetView(); if (!aView) { return;
}
}
nsViewManager* vm = aView->GetViewManager();
// Make sure visibility is correct. This only affects nsSubDocumentFrame. if (!SupportsVisibilityHidden()) { // See if the view should be hidden or visible
ComputedStyle* sc = Style();
vm->SetViewVisibility(aView, sc->StyleVisibility()->IsVisible()
? ViewVisibility::Show
: ViewVisibility::Hide);
}
}
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()) {
uint32_t hc1 = SideToHalfCorner(side, false, true);
uint32_t 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;
}
void nsIFrame::AdjustBorderRadii(nscoord aRadii[8], const nsMargin& aOffsets) { auto AdjustOffset = [](const uint32_t aRadius, const nscoord aOffset) { // Implement the cubic formula to adjust offset when aOffset > 0 and // aRadius / aOffset < 1. // https://drafts.csswg.org/css-shapes/#valdef-shape-box-margin-box if (aOffset > 0) { constdouble ratio = aRadius / double(aOffset); if (ratio < 1.0) { return nscoord(aOffset * (1.0 + std::pow(ratio - 1, 3)));
}
} return aOffset;
};
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. for (constauto corner : mozilla::AllPhysicalHalfCorners()) {
aRadii[corner] = 0;
} returnfalse;
}
constauto& radii = StyleBorder()->mBorderRadius; constbool hasRadii =
ComputeBorderRadii(radii, 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;
}
nscoord nsIFrame::SynthesizeFallbackBaseline(
WritingMode aWM, BaselineSharingGroup aBaselineGroup) const { constauto margin = GetLogicalUsedMargin(aWM);
NS_ASSERTION(!IsSubtreeDirty(), "frame must not be dirty"); if (aWM.IsCentralBaseline()) { return (BSize(aWM) + GetLogicalUsedMargin(aWM).BEnd(aWM)) / 2;
} // Baseline for inverted line content is the top (block-start) margin edge, // as the frame is in effect "flipped" for alignment purposes. if (aWM.IsLineInverted()) { constauto marginStart = margin.BStart(aWM); return aBaselineGroup == BaselineSharingGroup::First
? -marginStart
: BSize(aWM) + marginStart;
} // Otherwise, the bottom margin edge, per CSS2.1's definition of the // 'baseline' value of 'vertical-align'. constauto marginEnd = margin.BEnd(aWM); return aBaselineGroup == BaselineSharingGroup::First ? BSize(aWM) + marginEnd
: -marginEnd;
}
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;
}
RefPtr<ComputedStyle> pseudoStyle =
PresContext()->StyleSet()->ProbePseudoElementStyle(
*element, PseudoStyleType::selection, nullptr, Style()); 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);
}
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& width = pos.GetWidth(); constauto& height = pos.GetHeight(); if (!width.IsLengthPercentage() || width.HasPercent() ||
!height.IsLengthPercentage() || height.HasPercent() ||
IsIntrinsicKeyword(pos.GetMinWidth()) ||
IsIntrinsicKeyword(pos.GetMaxWidth()) ||
IsIntrinsicKeyword(pos.GetMinHeight()) ||
IsIntrinsicKeyword(pos.GetMaxHeight()) ||
((pos.GetMinWidth().IsAuto() || pos.GetMinHeight().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;
}
returntrue;
}
/******************************************************** * Refreshes each content's frame
*********************************************************/
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));
}
/** * If the CSS 'overflow' property applies to this frame, and is not * handled by constructing a dedicated nsHTML/XULScrollFrame, set up clipping * for that overflow in aBuilder->ClipState() to clip all containing-block * descendants.
*/ staticvoid ApplyOverflowClipping(
nsDisplayListBuilder* aBuilder, const nsIFrame* aFrame,
PhysicalAxes aClipAxes,
DisplayListClipState::AutoClipMultiple& aClipState) { // 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(aFrame->ShouldApplyOverflowClipping(aFrame->StyleDisplay()) ==
aClipAxes);
nsRect clipRect; bool haveRadii = false;
nscoord radii[8]; auto* disp = aFrame->StyleDisplay(); // Only deflate the padding if we clip to the content-box in that axis. auto wm = aFrame->GetWritingMode(); bool cbH = (wm.IsVertical() ? disp->mOverflowClipBoxBlock
: disp->mOverflowClipBoxInline) ==
StyleOverflowClipBox::ContentBox; bool cbV = (wm.IsVertical() ? disp->mOverflowClipBoxInline
: disp->mOverflowClipBoxBlock) ==
StyleOverflowClipBox::ContentBox;
nsRect rect(nsPoint(0, 0), aFrame->GetSize());
rect.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 = aFrame->InkOverflowRect();
rect.x = o.x;
rect.width = o.width;
} if (MOZ_UNLIKELY(!aClipAxes.contains(PhysicalAxis::Vertical))) { // See the note above.
nsRect o = aFrame->InkOverflowRect();
rect.y = o.y;
rect.height = o.height;
}
clipRect = rect + aBuilder->ToReferenceFrame(aFrame);
haveRadii = aFrame->GetBoxBorderRadii(radii, boxMargin);
aClipState.ClipContainingBlockDescendantsExtra(clipRect,
haveRadii ? radii : nullptr);
}
/** * Returns whether a display item that gets created with the builder's current * state will have a scrolled clip, i.e. a clip that is scrolled by a scroll * frame which does not move the item itself.
*/ staticbool BuilderHasScrolledClip(nsDisplayListBuilder* aBuilder) { const DisplayItemClipChain* currentClip =
aBuilder->ClipState().GetCurrentCombinedClipChain(aBuilder); if (!currentClip) { returnfalse;
}
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->GetCompositorHitTestInfo(aBuilder);
aBuilder->SetCompositorHitTestInfo(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();
}
if (aBuilder->IsReusingStackingContextItems() &&
TryToReuseStackingContextItem(aBuilder, aList, this)) { if (aCreatedContainerItem) {
*aCreatedContainerItem = true;
} return;
}
if (HasAnyStateBits(NS_FRAME_TOO_DEEP_IN_FRAME_TREE)) { return;
}
constauto& style = *Style(); const nsStyleDisplay* disp = style.StyleDisplay(); const nsStyleEffects* effects = style.StyleEffects();
EffectSet* effectSetForOpacity =
EffectSet::GetForFrame(this, nsCSSPropertyIDSet::OpacityProperties()); // We can stop right away if this is a zero-opacity stacking context and // we're painting, and we're not animating opacity. bool needHitTestInfo = aBuilder->BuildCompositorHitTestInfo() &&
Style()->PointerEvents() != StylePointerEvents::None; bool opacityItemForEventsOnly = false; if (effects->IsTransparent() && aBuilder->IsForPainting() &&
!(disp->mWillChange.bits & StyleWillChangeBits::OPACITY) &&
!nsLayoutUtils::HasAnimationOfPropertySet( this, nsCSSPropertyIDSet::OpacityProperties(), effectSetForOpacity)) { if (needHitTestInfo) {
opacityItemForEventsOnly = true;
} else { return;
}
}
if (aBuilder->IsForPainting() && disp->mWillChange.bits) {
aBuilder->AddToWillChangeBudget(this, GetSize());
}
// 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);
Maybe<nsDisplayListBuilder::AutoPreserves3DContext> autoPreserves3DContext; if (extend3DContext && !combines3DTransformWithAncestors) { // Start a new preserves3d context to keep informations on // nsDisplayListBuilder.
autoPreserves3DContext.emplace(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);
}
}
// reset blend mode so we can keep track if this stacking context needs have // a nsDisplayBlendContainer. Set the blend mode back when the routine exits // so we keep track if the parent stacking context needs a container too.
AutoSaveRestoreContainsBlendMode autoRestoreBlendMode(*aBuilder);
aBuilder->SetContainsBlendMode(false);
// NOTE: When changing this condition make sure to tweak ScrollContainerFrame // as well. bool usingBackdropFilter = effects->HasBackdropFilters() &&
IsVisibleForPainting() &&
!style.IsRootElementStyle();
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;
}
}
}
// 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,
OwnLayerIfNeeded,
BlendMode,
FixedPosition,
OwnLayerForTransformWithRoundedClip,
Perspective,
Transform,
SeparatorTransforms,
Opacity,
Filter,
BlendContainer
};
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 (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);
}
// 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);
}
// 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), false); 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;
// 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));
createdContainer = true;
}
// 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, usingBackdropFilter);
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(aBuilder, this);
resultList.AppendNewToTop<nsDisplayOpacity>(
aBuilder, this, &resultList, containerItemASR, opacityItemForEventsOnly,
needsActiveOpacityLayer, usingBackdropFilter);
createdContainer = true;
}
// 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. if (isTransformed && 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;
}
resultList.AppendToTop(&participants);
}
if (isTransformed) {
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;
}
// If we have sticky positioning, wrap it in a sticky position item. if (useFixedPosition) { 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());
resultList.AppendNewToTop<nsDisplayFixedPosition>(
aBuilder, this, &resultList, fixedASR, containerItemASR);
createdContainer = true;
} elseif (useStickyPosition) { // 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* stickyASR = 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() == stickyASR) {
aBuilder->GetFilterASR()
->mScrollContainerFrame->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 (useBlendMode) {
DisplayListClipState::AutoSaveRestore blendModeClipState(aBuilder);
resultList.AppendNewToTop<nsDisplayBlendMode>(aBuilder, this, &resultList,
effects->mMixBlendMode,
containerItemASR, false);
createdContainer = true;
}
if (aBuilder->IsReusingStackingContextItems()) { if (resultList.IsEmpty()) { return;
}
// 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.
/** * Check if a frame should be visited for building display list.
*/ staticbool DescendIntoChild(nsDisplayListBuilder* aBuilder, const nsIFrame* aChild, const nsRect& aVisible, const nsRect& aDirty) { if (aChild->HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO)) { returntrue;
}
// 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() &&
(!aFrame->IsTextFrame() && aFrame->IsLeaf())) { returntrue;
} // The placeholder frame should have the same content as the OOF frame. if (aBuilder->GetSelectedFramesOnly() &&
(aFrame->IsLeaf() && !aFrame->IsSelected())) { returntrue;
} staticconst nsFrameState skipFlags =
(NS_FRAME_TOO_DEEP_IN_FRAME_TREE | NS_FRAME_IS_NONDISPLAY); if (aFrame->HasAnyStateBits(skipFlags)) { returntrue;
} return aFrame->StyleUIReset()->mMozSubtreeHiddenOnlyVisually;
}
// 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 (StaticPrefs::print_save_as_pdf_links_enabled() &&
aBuilder->IsForPrinting()) {
linkifier.emplace(aBuilder, childOrOutOfFlow, aLists.Content());
linkifier->MaybeAppendLink(aBuilder, childOrOutOfFlow);
}
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 (aBuilder->IsForPainting()) {
aBuilder->ClearWillChangeBudgetStatus(child);
}
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 (aBuilder->IsForPainting()) {
aBuilder->ClearWillChangeBudgetStatus(child);
}
// If 'child' is a pushed float 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_FLOAT | NS_FRAME_TOO_DEEP_IN_FRAME_TREE |
NS_FRAME_IS_NONDISPLAY); if (child->HasAnyStateBits(skipFlags) || nsLayoutUtils::IsPopup(child)) { 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;
}
const nsStyleDisplay* ourDisp = StyleDisplay(); // Don't paint our children if the theme object is a leaf. if (IsThemed(ourDisp) && !PresContext()->Theme()->WidgetIsContainer(
ourDisp->EffectiveAppearance())) { return;
}
// 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);
clipState.SetClipChainForContainingBlockDescendants(
savedOutOfFlowData->mContainingBlockClipChain);
asrSetter.SetCurrentActiveScrolledRoot(
savedOutOfFlowData->mContainingBlockActiveScrolledRoot);
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;
}
// 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);
}
if (target) {
RefPtr<AsyncEventDispatcher> asyncDispatcher = new 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 buttion 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;
// Get the limiting node to stop parent frame search
nsIContent* limiter = aFrameSelection->GetLimiter();
// If our content node is an ancestor of the limiting node, // we should stop the search right now. if (limiter && limiter->IsInclusiveDescendantOf(GetContent())) { return NS_OK;
}
// We don't initiate row/col selection from here now, // but we may in future // bool selectColumn = false; // bool selectRow = false;
while (frame) { // Check for a table cell by querying to a known CellFrame interface
nsITableCellLayout* cellElement = do_QueryFrame(frame); if (cellElement) {
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;
} else { // If not a cell, check for table // This will happen when starting frame is the table or child of a table, // such as a row (we were inbetween cells or in table border)
nsTableWrapperFrame* tableFrame = do_QueryFrame(frame); if (tableFrame) {
foundTable = true; // TODO: How can we select row when along left table edge // or select column when along top edge? break;
} else {
frame = frame->GetParent(); // Stop if we have hit the selection's limiting content node if (frame && frame->GetContent() == limiter) { break;
}
}
}
} // We aren't in a cell or table if (!foundCell && !foundTable) { return NS_OK;
}
nsIContent* tableOrCellContent = frame->GetContent(); if (!tableOrCellContent) { return NS_ERROR_FAILURE;
}
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.
if (aFrame->IsTextInputFrame() || IsEditingHost(aFrame)) { // We don't implement 'contain' itself, but we make 'text' behave as // 'contain' for contenteditable and <input> / <textarea> elements anyway so // this is ok. return StyleUserSelect::Text;
}
auto style = aFrame->Style()->UserSelect(); if (style != StyleUserSelect::Auto) { return style;
}
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 if (!aPresContext->EventStateManager()->EventStatusOK(aMouseEvent)) { 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 style allows selection // if not, don't tell selection the mouse event even occurred.
StyleUserSelect selectStyle; // check for select: none if (!IsSelectable(&selectStyle)) { 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 =
selectStyle == StyleUserSelect::Text ? GetConstFrameSelection()
: presShell->ConstFrameSelection();
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(0);
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;
}
/** * Multiple Mouse Press -- line or paragraph selection -- for the frame. * Wouldn't it be nice if this didn't have to be hardwired into Frame code?
*/
NS_IMETHODIMP
nsIFrame::HandleMultiplePress(nsPresContext* aPresContext,
WidgetGUIEvent* aEvent,
nsEventStatus* aEventStatus, bool aControlHeld) {
NS_ENSURE_ARG_POINTER(aEvent);
NS_ENSURE_ARG_POINTER(aEventStatus);
// 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;
}
PeekOffsetOptions peekOffsetOptions{PeekOffsetOption::StopAtScroller}; if (aJumpLines) {
peekOffsetOptions += PeekOffsetOption::JumpLines;
}
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);
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);
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) { // 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. // NOTE: DetermineDisplaySelection() returns SELECTION_OFF for // non-selectable frames. if (DetermineDisplaySelection() == nsISelectionController::SELECTION_OFF) { 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);
/** * This static method handles part of the nsIFrame::HandleRelease in a way * which doesn't rely on the nsFrame object to stay alive.
*/
MOZ_CAN_RUN_SCRIPT_BOUNDARY static nsresult HandleFrameSelection(
nsFrameSelection* aFrameSelection, nsIFrame::ContentOffsets& aOffsets, bool aHandleTableSel, int32_t aContentOffsetForTableSel,
TableSelectionMode aTargetForTableSel,
nsIContent* aParentContentForTableSel, WidgetGUIEvent* aEvent, const nsEventStatus* aEventStatus) { if (!aFrameSelection) { return NS_OK;
}
nsresult rv = NS_OK;
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->DetermineDisplaySelection() !=
nsISelectionController::SELECTION_OFF) {
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!
// Retrieve the content offsets of a frame static FrameContentRange GetRangeForFrame(const nsIFrame* aFrame) {
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);
}
// 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()) { 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 and list 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() || aFrame->IsListControlFrame()) {
MOZ_ASSERT(aFrame->HasAnyStateBits(NS_FRAME_INDEPENDENT_SELECTION)); returnfalse;
}
// Failure in this assertion means a new type of frame forms the root of an // NS_FRAME_INDEPENDENT_SELECTION subtree. In such case, the condition above // should be changed to handle it.
MOZ_ASSERT_IF(
aFrame->HasAnyStateBits(NS_FRAME_INDEPENDENT_SELECTION),
aFrame->GetParent()->HasAnyStateBits(NS_FRAME_INDEPENDENT_SELECTION));
// 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* frame = aLine->mFirstChild;
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 (int32_t n = aLine->GetChildCount(); n;
--n, frame = frame->GetNextSibling()) { // 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 other replaced elements other // than images? Or even all other frames? We only get there when we didn't find // selectable children... At least one XUL test fails if we make this apply to // XUL labels. Also, editable images need _not_ to use the frame edge, see // below. staticbool UseFrameEdge(nsIFrame* aFrame) { if (aFrame->IsFlexOrGridContainer() || aFrame->IsTableFrame()) { returntrue;
} const nsImageFrame* image = do_QueryFrame(aFrame); if (image && !aFrame->GetContent()->IsEditable()) { // Editable images 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();
}
}
// If the correct offset is at one end of a frame, use offset-based // calculation method if (closest.frameEdge) {
ContentOffsets offsets;
FrameContentRange range = GetRangeForFrame(closest.frame);
offsets.content = range.content; if (closest.afterFrame) {
offsets.offset = range.end;
} else {
offsets.offset = range.start;
}
offsets.secondaryOffset = offsets.offset;
offsets.associate = offsets.offset == range.start
? CaretAssociationHint::After
: CaretAssociationHint::Before; return offsets;
}
// 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;
}
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& aspectRatio = StylePosition()->mAspectRatio; // 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 if (!aspectRatio.BehavesAsAuto()) { // Non-auto. Return the preferred aspect ratio from the aspect-ratio style. return aspectRatio.ratio.AsRatio().ToLayoutRatio(UseBoxSizing::Yes);
}
// The rest of the cases are when aspect-ratio has 'auto'. if (auto intrinsicRatio = GetIntrinsicRatio()) { return intrinsicRatio;
}
if (aspectRatio.HasRatio()) { // If it's a degenerate ratio, this returns 0. Just the same as the auto // case. return aspectRatio.ratio.AsRatio().ToLayoutRatio(UseBoxSizing::No);
}
staticbool ShouldApplyAutomaticMinimumOnInlineAxis(
WritingMode aWM, const nsStyleDisplay* aDisplay, const nsStylePosition* aPosition) { // Apply the automatic minimum size for aspect ratio: // Note: The replaced elements shouldn't be here, so we only check the scroll // container. // https://drafts.csswg.org/css-sizing-4/#aspect-ratio-minimum return !aDisplay->IsScrollableOverflow() && aPosition->MinISize(aWM).IsAuto();
}
constauto& aspectRatio = aSizeOverrides.mAspectRatio
? *aSizeOverrides.mAspectRatio
: GetAspectRatio(); constauto& styleISize = aSizeOverrides.mStyleISize
? *aSizeOverrides.mStyleISize
: stylePos->ISize(aWM); // 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 = [&] { constauto& styleBSizeConsideringOverrides =
(aSizeOverrides.mStyleBSize) ? *aSizeOverrides.mStyleBSize
: stylePos->BSize(aWM); 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); return StyleSize::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(aRenderingContext, 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) &&
!alignCB->IsMasonry(isOrthogonal ? LogicalAxis::Block
: LogicalAxis::Inline)) { auto inlineAxisAlignment =
isOrthogonal ? StylePosition()->UsedAlignSelf(alignCB->Style())._0
: StylePosition()->UsedJustifySelf(alignCB->Style())._0;
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); constauto& maxBSizeCoord = stylePos->MaxBSize(aWM); 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);
nscoord maxISize = NS_UNCONSTRAINEDSIZE; if (!maxISizeCoord.IsNone() && !shouldIgnoreMinMaxISize) {
maxISize = ComputeISizeValue(aRenderingContext, 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(
aRenderingContext, Some(aCBSize.ConvertTo(GetWritingMode(), aWM)),
Some(LogicalSize(aWM, NS_UNCONSTRAINEDSIZE, bSizeAsPercentageBasis)
.ConvertTo(GetWritingMode(), aWM))); constauto& minISizeCoord = stylePos->MinISize(aWM);
nscoord minISize; if (!minISizeCoord.IsAuto() && !shouldIgnoreMinMaxISize) {
minISize = ComputeISizeValue(aRenderingContext, 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, 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(isOrthogonal ? LogicalAxis::Inline
: 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)) { auto blockAxisAlignment =
isOrthogonal ? StylePosition()->UsedJustifySelf(alignCB->Style())._0
: StylePosition()->UsedAlignSelf(alignCB->Style())._0;
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(
gfxContext* aRenderingContext, 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& styleISize = aSizeOverrides.mStyleISize
? *aSizeOverrides.mStyleISize
: stylePos->ISize(aWM); constauto& styleBSize = aSizeOverrides.mStyleBSize
? *aSizeOverrides.mStyleBSize
: stylePos->BSize(aWM); constauto positionProperty = StyleDisplay()->mPosition; constauto iStartOffsetIsAuto =
stylePos
->GetAnchorResolvedInset(LogicalSide::IStart, aWM, positionProperty)
.IsAuto(); constauto iEndOffsetIsAuto =
stylePos->GetAnchorResolvedInset(LogicalSide::IEnd, aWM, positionProperty)
.IsAuto(); constauto bStartOffsetIsAuto =
stylePos
->GetAnchorResolvedInset(LogicalSide::BStart, aWM, positionProperty)
.IsAuto(); constauto bEndOffsetIsAuto =
stylePos->GetAnchorResolvedInset(LogicalSide::BEnd, aWM, positionProperty)
.IsAuto(); constauto boxSizingAdjust = stylePos->mBoxSizing == StyleBoxSizing::Border
? aBorderPadding
: LogicalSize(aWM); auto shouldStretch = [](StyleSelfAlignment 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 &= ~StyleSelfAlignment::FLAG_BITS;
if (aAlignment == StyleSelfAlignment::STRETCH) { returntrue;
}
if (aAlignment == StyleSelfAlignment::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 constauto* parent = GetParent(); constauto parentWM = parent->GetWritingMode(); // Self alignment properties translate `auto` to normal for this purpose. // https://drafts.csswg.org/css-align-3/#valdef-justify-self-auto constauto inlineAlignSelf = parentWM.IsOrthogonalTo(aWM)
? stylePos->UsedAlignSelf(nullptr)._0
: stylePos->UsedJustifySelf(nullptr)._0; constauto blockAlignSelf = parentWM.IsOrthogonalTo(aWM)
? stylePos->UsedJustifySelf(nullptr)._0
: stylePos->UsedAlignSelf(nullptr)._0; constauto iShouldStretch = shouldStretch(
inlineAlignSelf, this, iStartOffsetIsAuto, iEndOffsetIsAuto); constauto bShouldStretch =
shouldStretch(blockAlignSelf, 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();
} return Some(ComputeISizeValueFromAspectRatio(
aWM, aCBSize, aContentEdgeToBoxSizing, aStyleBSize.AsLengthPercentage(),
aAspectRatio));
}();
constauto* stylePos = StylePosition(); const nscoord bSize = ComputeBSizeValueAsPercentageBasis(
aStyleBSize, stylePos->MinBSize(aWM), stylePos->MaxBSize(aWM),
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()); if (bsize.HasPercent()) {
aReflowInput->mPercentBSizeObserver->NotifyPercentBSize(*aReflowInput);
}
}
// Let the absolutely positioned container reflow any absolutely positioned // child frames that need to be reflowed
// The containing block for the abs pos kids is formed by our padding edge.
nsMargin usedBorder = GetUsedBorder();
nscoord containingBlockWidth =
std::max(0, aDesiredSize.Width() - usedBorder.LeftRight());
nscoord containingBlockHeight =
std::max(0, aDesiredSize.Height() - usedBorder.TopBottom());
nsContainerFrame* container = do_QueryFrame(this);
NS_ASSERTION(container, "Abs-pos children only supported on container frames for now");
nsRect containingBlock(0, 0, containingBlockWidth, containingBlockHeight);
AbsPosReflowFlags flags =
AbsPosReflowFlags::CBWidthAndHeightChanged; // XXX could be optimized if (aConstrainBSize) {
flags |= AbsPosReflowFlags::ConstrainHeight;
}
absoluteContainer->Reflow(container, aPresContext, aReflowInput, aStatus,
containingBlock, flags,
&aDesiredSize.mOverflowAreas);
}
}
/* virtual */ bool nsIFrame::CanContinueTextRun() const { // By default, a frame will *not* allow a text run to be continued // through it. returnfalse;
}
bool nsIFrame::IsContentDisabled() const { // FIXME(emilio): Doing this via CSS means callers must ensure the style is up // to date, and they don't! if (StyleUI()->UserInput() == StyleUserInput::None) { returntrue;
}
auto* element = nsGenericHTMLElement::FromNodeOrNull(GetContent()); return element && element->IsDisabled();
}
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."
RefPtr<AsyncEventDispatcher> asyncDispatcher = new 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;
}
#ifdef DEBUG
LayoutFrameType frameType = Type();
NS_ASSERTION(frameType == LayoutFrameType::SubDocument ||
frameType == LayoutFrameType::Viewport ||
frameType == LayoutFrameType::MenuPopup, "Only specific frame types can have an nsView"); #endif
// Store the view on the frame.
SetViewInternal(aView);
// Set the frame state bit that says the frame has a view
AddStateBits(NS_FRAME_HAS_VIEW);
// Let all of the ancestors know they have a descendant with a view. for (nsIFrame* f = GetParent();
f && !f->HasAnyStateBits(NS_FRAME_HAS_CHILD_WITH_VIEW);
f = f->GetParent()) {
f->AddStateBits(NS_FRAME_HAS_CHILD_WITH_VIEW);
}
} else {
MOZ_ASSERT_UNREACHABLE("Destroying a view while the frame is alive?");
RemoveStateBits(NS_FRAME_HAS_VIEW);
SetViewInternal(nullptr);
}
}
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;
}
// Returns the offset from this frame to the closest geometric parent that // has a view. Also returns the containing view or null in case of error void nsIFrame::GetOffsetFromView(nsPoint& aOffset, nsView** aView) const {
MOZ_ASSERT(nullptr != aView, "null OUT parameter pointer");
nsIFrame* frame = const_cast<nsIFrame*>(this);
*aView = nullptr;
aOffset.MoveTo(0, 0); do {
aOffset += frame->GetPosition();
frame = frame->GetParent();
} while (frame && !frame->HasView());
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/translate matrix that will apply our current transform, then * shift us to our parent.
*/ 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 * coordinates to our parent.
*/ 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 * tree until we either hit the root frame or something that may be * transformed. We'll then change coordinates into that frame, since we're * guaranteed that nothing in-between can be transformed. First, however, * we have to check to see if we have a parent. If not, we'll set the * outparam to null (indicating that there's nothing left) and will hand back * the identity matrix.
*/ 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()->ScheduleViewManagerFlush();
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()) {
SetOrUpdateRectValuedProperty(aFrame, 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)) {
SetOrUpdateRectValuedProperty(aFrame, 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())) { if (nsView* view = GetView()) { // Make sure the frame's view is properly sized.
nsViewManager* vm = view->GetViewManager();
vm->ResizeView(view, overflowAreas.InkOverflow());
}
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::Meter: 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: // Buttons (GfxButtonControl / HTMLButtonControl) don't have this // shrinking behavior. (Note that color inputs do, even though they // inherit from button, so we can't use do_QueryFrame here.) 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. return !IsLineParticipant() && !IsBlockWrapper() && !IsSubgrid() && // Table rows are not containing blocks either
!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::scrolledContent) {
f = f->GetParent();
} return f;
}
bool nsIFrame::CachedIsEmpty() {
MOZ_ASSERT(!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;
}
nsIFrame::GetLastLeaf(&lastFrame);
if (aPos->mDirection == eDirNext) {
nearStoppingFrame = firstFrame;
farStoppingFrame = lastFrame;
} else {
nearStoppingFrame = lastFrame;
farStoppingFrame = firstFrame;
}
}
nsPoint offset;
nsView* view; // used for call of get offset from view
aBlockFrame->GetOffsetFromView(offset, &view);
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 if so // then call into its lines if (resultFrame->CanProvideLineIterator()) {
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(nullptr)) { returnfalse;
} if (aPos->mAncestorLimiter &&
!aOffsets.content->IsInclusiveDescendantOf(
aPos->mAncestorLimiter)) { returnfalse;
} if (forceInEditableRegion && !aOffsets.content->IsEditable()) { returnfalse;
} returntrue;
};
nsIFrame* storeOldResultFrame = resultFrame; while (!found) {
nsPoint point;
nsRect tempRect = resultFrame->GetRect();
nsPoint offset;
nsView* view; // used for call of get offset from view
resultFrame->GetOffsetFromView(offset, &view); if (!view) { return NS_ERROR_FAILURE;
} if (resultFrame->GetWritingMode().IsVertical()) {
point.y = aPos->mDesiredCaretPos.y;
point.x = tempRect.width + offset.x;
} else {
point.y = tempRect.height + offset.y;
point.x = aPos->mDesiredCaretPos.x;
}
if (!resultFrame->HasView()) {
nsView* view;
nsPoint offset;
resultFrame->GetOffsetFromView(offset, &view);
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;
nsView* view;
nsPoint offset;
resultFrame->GetOffsetFromView(offset, &view);
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 ((aFrame->IsBlockOutside() &&
!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;
}
// 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
aPos->mResultContent = frame->GetContent(); 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);
}(); 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: Jump and stop right there * Next, saw inline: just stop * Next, no inline: Jump and consume whitespaces
*/ 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 * when we hit a frame which doesn't have line iterator, we need to * drill down and find a child with the line iterator to prevent the * crawling process to prematurely finish. Note that this is only sound if * we're guaranteed to not have multiple children implementing * LineIterator. * * So far known cases are: * 1) table wrapper (drill down into table row group) * 2) table cell (drill down into its only anon child)
*/ 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);
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 IsSelectable =
[aOptions, nativeAnonymousSubtreeContent](const nsIFrame* aFrame) { if (!aFrame->IsSelectable(nullptr)) { returnfalse;
} // If the new frame is in a native anonymous subtree, we should treat // it as not selectable unless the frame and found frame are in same // subtree. if (aFrame->GetClosestNativeAnonymousSubtreeRoot() !=
nativeAnonymousSubtreeContent) { 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() && IsSelectable(current)) { if (!current->IsBrFrame()) {
result.mIgnoredBrFrame = true;
} break;
}
} if (result.mIgnoredBrFrame) { continue;
}
}
selectable = IsSelectable(traversedFrame); if (!selectable) { if (traversedFrame->IsSelectable(nullptr)) {
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;
}
nsView* nsIFrame::GetClosestView(nsPoint* aOffset) const {
nsPoint offset(0, 0); for (const nsIFrame* f = this; f; f = f->GetParent()) { if (f->HasView()) { if (aOffset) {
*aOffset = offset;
} return f->GetView();
}
offset += f->GetPosition();
}
MOZ_ASSERT_UNREACHABLE("No view on any parent? How did that happen?"); return nullptr;
}
/* 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;
}
}
enumclass ApplyTransform : bool { No, Yes };
/** * Compute the outline inner rect (so without outline-width and outline-offset) * of aFrame, maybe iterating over its descendants, in aFrame's coordinate space * or its post-transform coordinate space (depending on aApplyTransform).
*/ static nsRect ComputeOutlineInnerRect(
nsIFrame* aFrame, ApplyTransform aApplyTransform, bool& aOutValid, const nsSize* aSizeOverride = nullptr, const OverflowAreas* aOverflowOverride = nullptr) { const nsRect bounds(nsPoint(0, 0),
aSizeOverride ? *aSizeOverride : aFrame->GetSize());
// 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); if (overflowClipAxes == kPhysicalAxesBoth && overflowClipMargin == nsSize()) { return u;
}
// Iterate over all children except pop-up, absolutely-positioned, // float, and overflow ones. const FrameChildListIDs skip = {
FrameChildListID::Absolute, FrameChildListID::Fixed,
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 {
SetOrUpdateRectValuedProperty(aFrame, nsIFrame::OutlineInnerRectProperty(),
innerRect);
}
nsRect bounds(nsPoint(0, 0), 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 (ChildrenHavePerspective(disp) && sizeChanged) {
RecomputePerspectiveChildrenOverflow(this);
if (overflowClipAxes != kPhysicalAxesBoth) {
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));
}
// 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 * should be correct. Our post-transform overflow is empty though, because * we only contribute to the overflow area of the preserve-3d root frame. * If we're an intermediate frame then the pre-transform overflow should * contain all our non-preserve-3d children, which is what we want. Again * we have no post-transform overflow.
*/
aOverflowAreas.SetAllTo(nsRect());
} else {
TransformReferenceBox refBox(this); for (constauto otype : AllOverflowTypes()) {
nsRect& o = aOverflowAreas.Overflow(otype);
o = nsDisplayTransform::TransformRect(o, this, refBox);
}
/* If we're the root of the 3d context, then we want to include the * overflow areas of all the participants. This won't have happened yet as * the code above set their overflow area to empty. Manually collect these * overflow areas now.
*/ 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;
}
void nsIFrame::RecomputePerspectiveChildrenOverflow( const nsIFrame* aStartFrame) { for (constauto& childList : ChildLists()) { for (nsIFrame* child : childList.mList) { if (!child->FrameMaintainsOverflow()) { continue; // frame does not maintain overflow rects
} if (child->HasPerspective()) {
OverflowAreas* overflow =
child->GetProperty(nsIFrame::InitialOverflowProperty());
nsRect bounds(nsPoint(0, 0), child->GetSize()); if (overflow) {
OverflowAreas overflowCopy = *overflow;
child->FinishAndStoreOverflow(overflowCopy, bounds.Size());
} else {
OverflowAreas boundsOverflow;
boundsOverflow.SetAllTo(bounds);
child->FinishAndStoreOverflow(boundsOverflow, 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.
child->RecomputePerspectiveChildrenOverflow(aStartFrame);
}
}
}
}
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;
}
/** * This function takes a frame that is part of a block-in-inline split, * and _if_ that frame is an anonymous block created by an ib split it * returns the block's preceding inline. This is needed because the * split inline's style is the parent of the anonymous block's style. * * If aFrame is not an anonymous block, null is returned.
*/ 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();
/* * Now look up the nsGkAtoms::IBSplitPrevSibling * property.
*/
nsIFrame* ibSplitSibling =
aFrame->GetProperty(nsIFrame::IBSplitPrevSibling());
NS_ASSERTION(ibSplitSibling, "Broken frame tree?"); return ibSplitSibling;
}
/** * Get the parent, corrected for the mangled frame tree resulting from * having a block within an inline. The result only differs from the * result of |GetParent| when |GetParent| returns an anonymous block * that was created for an element that was 'display: inline' because * that element contained a block. * * Also skip anonymous scrolled-content parents; inherit directly from the * outer scroll frame. * * Also skip NAC parents if the child frame is NAC.
*/ static nsIFrame* GetCorrectedParent(const nsIFrame* aFrame) {
nsIFrame* parent = aFrame->GetParent(); if (!parent) { return nullptr;
}
// For a table caption we want the _inner_ table frame (unless it's anonymous) // as the style parent. if (aFrame->IsTableCaption()) {
nsIFrame* innerTable = parent->PrincipalChildList().FirstChild(); 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::tableWrapper) {
pseudo =
aFrame->PrincipalChildList().FirstChild()->Style()->GetPseudoType();
}
// Prevent a NAC pseudo-element from inheriting from its NAC parent, and // inherit from the NAC generator element instead. if (pseudo != PseudoStyleType::NotPseudo) {
MOZ_ASSERT(aFrame->GetContent());
Element* element = Element::FromNode(aFrame->GetContent()); // Make sure to avoid doing the fixup for non-element-backed pseudos like // ::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) &&
!nsCSSAnonBoxes::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::viewportScroll) { // 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::tableWrapper) { // 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)) { /* * If this frame is an anonymous block created when an inline with a block * inside it got split, then the parent style is on its preceding inline. We * can get to it using GetIBSplitSiblingForAnonymousBlock.
*/ 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 || // Don't enter into native anoymous subtree from the root like <input> or // <textarea>.
(*aFrame)->ContentIsRootOfNativeAnonymousSubtree()) { 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 (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->GetScrollRange().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;
}
/** * @return true if this text frame ends with a newline character which is * treated as preformatted. It should return false if this is not a text frame.
*/ bool nsIFrame::HasSignificantTerminalNewline() const { returnfalse; }
static StyleVerticalAlignKeyword ConvertSVGDominantBaselineToVerticalAlign(
StyleDominantBaseline aDominantBaseline) { // Most of these are approximate mappings. switch (aDominantBaseline) { case StyleDominantBaseline::Hanging: case StyleDominantBaseline::TextBeforeEdge: return StyleVerticalAlignKeyword::TextTop; case StyleDominantBaseline::TextAfterEdge: case StyleDominantBaseline::Ideographic: return StyleVerticalAlignKeyword::TextBottom; case StyleDominantBaseline::Central: case StyleDominantBaseline::Middle: case StyleDominantBaseline::Mathematical: return StyleVerticalAlignKeyword::Middle; case StyleDominantBaseline::Auto: case StyleDominantBaseline::Alphabetic: return StyleVerticalAlignKeyword::Baseline; default:
MOZ_ASSERT_UNREACHABLE("unexpected aDominantBaseline value"); return StyleVerticalAlignKeyword::Baseline;
}
}
constauto& verticalAlign = StyleDisplay()->mVerticalAlign; if (verticalAlign.IsKeyword()) { return Some(verticalAlign.AsKeyword());
}
return Nothing();
}
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);
}
}
// 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());
if (HasAnyStateBits(NS_FRAME_HAS_VIEW | NS_FRAME_HAS_CHILD_WITH_VIEW)) { for (nsIFrame* f = aParent;
f && !f->HasAnyStateBits(NS_FRAME_HAS_CHILD_WITH_VIEW);
f = f->GetParent()) {
f->AddStateBits(NS_FRAME_HAS_CHILD_WITH_VIEW);
}
}
if (HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)) { for (nsIFrame* f = aParent; f; f = f->GetParent()) { if (f->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)) { break;
}
f->AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
}
}
if (HasAnyStateBits(NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE)) { for (nsIFrame* f = aParent; f; f = f->GetParent()) { if (f->HasAnyStateBits(
NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE)) { break;
}
f->AddStateBits(NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE);
}
}
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;
}
} // 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;
}
} 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);
}
// And our additional styles.
int32_t index = 0; while (auto* extra = GetAdditionalComputedStyle(index++)) { if (!aSizes.mState.HaveSeenPtr(extra)) {
extra->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 (aBuilder->IsInsidePointerEventsNoneDoc()) { // Somewhere up the parent document chain is a subdocument with pointer- // events:none set on it. 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 (Style()->PointerEvents() == StylePointerEvents::None) { 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->GetCompositorHitTestInfo() & 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;
}
// 'contain:paint', which we handle as 'overflow:clip' here. Except for // scrollframes we don't need contain:paint to add any clipping, because // the scrollable frame will already clip overflowing content, and because // 'contain:paint' should prevent all means of escaping that clipping // (e.g. because it forms a fixed-pos containing block). if (aDisp->IsContainPaint() && !IsScrollContainerFrame() &&
SupportsContainLayoutAndPaint()) { return kPhysicalAxesBoth;
}
// and overflow:hidden that we should interpret as clip if (aDisp->mOverflowX == StyleOverflow::Hidden &&
aDisp->mOverflowY == StyleOverflow::Hidden) { // REVIEW: these are the frame types that set up clipping.
LayoutFrameType type = Type(); switch (type) { case LayoutFrameType::CheckboxRadio: case LayoutFrameType::ComboboxControl: case LayoutFrameType::HTMLButtonControl: case LayoutFrameType::ListControl: case LayoutFrameType::Meter: case LayoutFrameType::Progress: case LayoutFrameType::Range: case LayoutFrameType::SubDocument: case LayoutFrameType::SVGForeignObject: case LayoutFrameType::SVGInnerSVG: case LayoutFrameType::SVGOuterSVG: case LayoutFrameType::SVGSymbol: case LayoutFrameType::Table: case LayoutFrameType::TableCell: return kPhysicalAxesBoth; case LayoutFrameType::TextInput: // It has an anonymous scroll container frame that handles any overflow. return PhysicalAxes(); default: break;
}
}
// clip overflow:clip, except for nsListControlFrame which is // a ScrollContainerFrame sub-class. if (MOZ_UNLIKELY((aDisp->mOverflowX == mozilla::StyleOverflow::Clip ||
aDisp->mOverflowY == mozilla::StyleOverflow::Clip) &&
!IsListControlFrame())) { // 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 == mozilla::StyleOverflow::Clip) {
axes += PhysicalAxis::Horizontal;
} if (aDisp->mOverflowY == mozilla::StyleOverflow::Clip) {
axes += PhysicalAxis::Vertical;
} return axes;
}
}
if (HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) { 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;
}
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 ReflowInput::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 == ReflowInput::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
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.