Bug 1760439 [wpt PR 33220] - Implement FedCM permission delegates in content_shell...
[gecko.git] / layout / generic / nsIFrame.cpp
blobc894897db4e0b7100832257a831280e24b7e5f6b
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 /* base class of all rendering objects */
9 #include "nsIFrame.h"
11 #include <stdarg.h>
12 #include <algorithm>
14 #include "gfx2DGlue.h"
15 #include "gfxUtils.h"
16 #include "mozilla/Attributes.h"
17 #include "mozilla/ComputedStyle.h"
18 #include "mozilla/DebugOnly.h"
19 #include "mozilla/DisplayPortUtils.h"
20 #include "mozilla/dom/DocumentInlines.h"
21 #include "mozilla/dom/AncestorIterator.h"
22 #include "mozilla/dom/ElementInlines.h"
23 #include "mozilla/dom/ImageTracker.h"
24 #include "mozilla/dom/Selection.h"
25 #include "mozilla/gfx/2D.h"
26 #include "mozilla/gfx/gfxVars.h"
27 #include "mozilla/gfx/PathHelpers.h"
28 #include "mozilla/intl/BidiEmbeddingLevel.h"
29 #include "mozilla/Maybe.h"
30 #include "mozilla/PresShell.h"
31 #include "mozilla/PresShellInlines.h"
32 #include "mozilla/ResultExtensions.h"
33 #include "mozilla/Sprintf.h"
34 #include "mozilla/StaticAnalysisFunctions.h"
35 #include "mozilla/StaticPrefs_layout.h"
36 #include "mozilla/StaticPrefs_print.h"
37 #include "mozilla/SVGMaskFrame.h"
38 #include "mozilla/SVGObserverUtils.h"
39 #include "mozilla/SVGTextFrame.h"
40 #include "mozilla/SVGIntegrationUtils.h"
41 #include "mozilla/SVGUtils.h"
42 #include "mozilla/ToString.h"
43 #include "mozilla/ViewportUtils.h"
45 #include "nsCOMPtr.h"
46 #include "nsFieldSetFrame.h"
47 #include "nsFlexContainerFrame.h"
48 #include "nsFrameList.h"
49 #include "nsPlaceholderFrame.h"
50 #include "nsIBaseWindow.h"
51 #include "nsIContent.h"
52 #include "nsIContentInlines.h"
53 #include "nsContentUtils.h"
54 #include "nsCSSFrameConstructor.h"
55 #include "nsCSSProps.h"
56 #include "nsCSSPseudoElements.h"
57 #include "nsCSSRendering.h"
58 #include "nsAtom.h"
59 #include "nsString.h"
60 #include "nsReadableUtils.h"
61 #include "nsTableWrapperFrame.h"
62 #include "nsView.h"
63 #include "nsViewManager.h"
64 #include "nsIScrollableFrame.h"
65 #include "nsPresContext.h"
66 #include "nsPresContextInlines.h"
67 #include "nsStyleConsts.h"
68 #include "mozilla/Logging.h"
69 #include "nsLayoutUtils.h"
70 #include "LayoutLogging.h"
71 #include "mozilla/RestyleManager.h"
72 #include "nsImageFrame.h"
73 #include "nsInlineFrame.h"
74 #include "nsFrameSelection.h"
75 #include "nsGkAtoms.h"
76 #include "nsGridContainerFrame.h"
77 #include "nsCSSAnonBoxes.h"
78 #include "nsCanvasFrame.h"
80 #include "nsFieldSetFrame.h"
81 #include "nsFrameTraversal.h"
82 #include "nsRange.h"
83 #include "nsITextControlFrame.h"
84 #include "nsNameSpaceManager.h"
85 #include "nsIPercentBSizeObserver.h"
86 #include "nsStyleStructInlines.h"
88 #include "nsBidiPresUtils.h"
89 #include "RubyUtils.h"
90 #include "TextOverflow.h"
91 #include "nsAnimationManager.h"
93 // For triple-click pref
94 #include "imgIRequest.h"
95 #include "nsError.h"
96 #include "nsContainerFrame.h"
97 #include "nsBoxLayoutState.h"
98 #include "nsBlockFrame.h"
99 #include "nsDisplayList.h"
100 #include "nsChangeHint.h"
101 #include "nsDeckFrame.h"
102 #include "nsSubDocumentFrame.h"
103 #include "RetainedDisplayListBuilder.h"
105 #include "gfxContext.h"
106 #include "nsAbsoluteContainingBlock.h"
107 #include "StickyScrollContainer.h"
108 #include "nsFontInflationData.h"
109 #include "nsRegion.h"
110 #include "nsIFrameInlines.h"
111 #include "nsStyleChangeList.h"
112 #include "nsWindowSizes.h"
114 #ifdef ACCESSIBILITY
115 # include "nsAccessibilityService.h"
116 #endif
118 #include "mozilla/AsyncEventDispatcher.h"
119 #include "mozilla/CSSClipPathInstance.h"
120 #include "mozilla/EffectCompositor.h"
121 #include "mozilla/EffectSet.h"
122 #include "mozilla/EventListenerManager.h"
123 #include "mozilla/EventStateManager.h"
124 #include "mozilla/EventStates.h"
125 #include "mozilla/Preferences.h"
126 #include "mozilla/LookAndFeel.h"
127 #include "mozilla/MouseEvents.h"
128 #include "mozilla/ServoStyleSet.h"
129 #include "mozilla/ServoStyleSetInlines.h"
130 #include "mozilla/css/ImageLoader.h"
131 #include "mozilla/dom/HTMLBodyElement.h"
132 #include "mozilla/dom/SVGPathData.h"
133 #include "mozilla/dom/TouchEvent.h"
134 #include "mozilla/gfx/Tools.h"
135 #include "mozilla/layers/WebRenderUserData.h"
136 #include "mozilla/layout/ScrollAnchorContainer.h"
137 #include "nsPrintfCString.h"
138 #include "ActiveLayerTracker.h"
140 #include "nsITheme.h"
142 using namespace mozilla;
143 using namespace mozilla::css;
144 using namespace mozilla::dom;
145 using namespace mozilla::gfx;
146 using namespace mozilla::layers;
147 using namespace mozilla::layout;
148 typedef nsAbsoluteContainingBlock::AbsPosReflowFlags AbsPosReflowFlags;
149 using nsStyleTransformMatrix::TransformReferenceBox;
151 const mozilla::LayoutFrameType nsIFrame::sLayoutFrameTypes[
152 #define FRAME_ID(...) 1 +
153 #define ABSTRACT_FRAME_ID(...)
154 #include "mozilla/FrameIdList.h"
155 #undef FRAME_ID
156 #undef ABSTRACT_FRAME_ID
157 0] = {
158 #define FRAME_ID(class_, type_, ...) mozilla::LayoutFrameType::type_,
159 #define ABSTRACT_FRAME_ID(...)
160 #include "mozilla/FrameIdList.h"
161 #undef FRAME_ID
162 #undef ABSTRACT_FRAME_ID
165 const nsIFrame::FrameClassBits nsIFrame::sFrameClassBits[
166 #define FRAME_ID(...) 1 +
167 #define ABSTRACT_FRAME_ID(...)
168 #include "mozilla/FrameIdList.h"
169 #undef FRAME_ID
170 #undef ABSTRACT_FRAME_ID
171 0] = {
172 #define Leaf eFrameClassBitsLeaf
173 #define NotLeaf eFrameClassBitsNone
174 #define DynamicLeaf eFrameClassBitsDynamicLeaf
175 #define FRAME_ID(class_, type_, leaf_, ...) leaf_,
176 #define ABSTRACT_FRAME_ID(...)
177 #include "mozilla/FrameIdList.h"
178 #undef Leaf
179 #undef NotLeaf
180 #undef DynamicLeaf
181 #undef FRAME_ID
182 #undef ABSTRACT_FRAME_ID
185 // Struct containing cached metrics for box-wrapped frames.
186 struct nsBoxLayoutMetrics {
187 nsSize mPrefSize;
188 nsSize mMinSize;
189 nsSize mMaxSize;
191 nsSize mBlockMinSize;
192 nsSize mBlockPrefSize;
193 nscoord mBlockAscent;
195 nscoord mFlex;
196 nscoord mAscent;
198 nsSize mLastSize;
201 struct nsContentAndOffset {
202 nsIContent* mContent = nullptr;
203 int32_t mOffset = 0;
206 // Some Misc #defines
207 #define SELECTION_DEBUG 0
208 #define FORCE_SELECTION_UPDATE 1
209 #define CALC_DEBUG 0
211 #include "nsILineIterator.h"
212 #include "prenv.h"
214 NS_DECLARE_FRAME_PROPERTY_DELETABLE(BoxMetricsProperty, nsBoxLayoutMetrics)
216 static void InitBoxMetrics(nsIFrame* aFrame, bool aClear) {
217 if (aClear) {
218 aFrame->RemoveProperty(BoxMetricsProperty());
221 nsBoxLayoutMetrics* metrics = new nsBoxLayoutMetrics();
222 aFrame->SetProperty(BoxMetricsProperty(), metrics);
224 aFrame->nsIFrame::MarkIntrinsicISizesDirty();
225 metrics->mBlockAscent = 0;
226 metrics->mLastSize.SizeTo(0, 0);
229 // Utility function to set a nsRect-valued property table entry on aFrame,
230 // reusing the existing storage if the property happens to be already set.
231 template <typename T>
232 static void SetOrUpdateRectValuedProperty(
233 nsIFrame* aFrame, FrameProperties::Descriptor<T> aProperty,
234 const nsRect& aNewValue) {
235 bool found;
236 nsRect* rectStorage = aFrame->GetProperty(aProperty, &found);
237 if (!found) {
238 rectStorage = new nsRect(aNewValue);
239 aFrame->AddProperty(aProperty, rectStorage);
240 } else {
241 *rectStorage = aNewValue;
245 static bool IsXULBoxWrapped(const nsIFrame* aFrame) {
246 return aFrame->GetParent() && aFrame->GetParent()->IsXULBoxFrame() &&
247 !aFrame->IsXULBoxFrame();
250 void nsReflowStatus::UpdateTruncated(const ReflowInput& aReflowInput,
251 const ReflowOutput& aMetrics) {
252 const WritingMode containerWM = aMetrics.GetWritingMode();
253 if (aReflowInput.GetWritingMode().IsOrthogonalTo(containerWM)) {
254 // Orthogonal flows are always reflowed with an unconstrained dimension,
255 // so should never end up truncated (see ReflowInput::Init()).
256 mTruncated = false;
257 } else if (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
258 aReflowInput.AvailableBSize() < aMetrics.BSize(containerWM) &&
259 !aReflowInput.mFlags.mIsTopOfPage) {
260 mTruncated = true;
261 } else {
262 mTruncated = false;
266 /* static */
267 void nsIFrame::DestroyAnonymousContent(
268 nsPresContext* aPresContext, already_AddRefed<nsIContent>&& aContent) {
269 if (nsCOMPtr<nsIContent> content = aContent) {
270 aPresContext->EventStateManager()->NativeAnonymousContentRemoved(content);
271 aPresContext->PresShell()->NativeAnonymousContentRemoved(content);
272 content->UnbindFromTree();
276 // Formerly the nsIFrameDebug interface
278 std::ostream& operator<<(std::ostream& aStream, const nsReflowStatus& aStatus) {
279 char complete = 'Y';
280 if (aStatus.IsIncomplete()) {
281 complete = 'N';
282 } else if (aStatus.IsOverflowIncomplete()) {
283 complete = 'O';
286 char brk = 'N';
287 if (aStatus.IsInlineBreakBefore()) {
288 brk = 'B';
289 } else if (aStatus.IsInlineBreakAfter()) {
290 brk = 'A';
293 aStream << "["
294 << "Complete=" << complete << ","
295 << "NIF=" << (aStatus.NextInFlowNeedsReflow() ? 'Y' : 'N') << ","
296 << "Truncated=" << (aStatus.IsTruncated() ? 'Y' : 'N') << ","
297 << "Break=" << brk << ","
298 << "FirstLetter=" << (aStatus.FirstLetterComplete() ? 'Y' : 'N')
299 << "]";
300 return aStream;
303 #ifdef DEBUG
304 static bool gShowFrameBorders = false;
306 void nsIFrame::ShowFrameBorders(bool aEnable) { gShowFrameBorders = aEnable; }
308 bool nsIFrame::GetShowFrameBorders() { return gShowFrameBorders; }
310 static bool gShowEventTargetFrameBorder = false;
312 void nsIFrame::ShowEventTargetFrameBorder(bool aEnable) {
313 gShowEventTargetFrameBorder = aEnable;
316 bool nsIFrame::GetShowEventTargetFrameBorder() {
317 return gShowEventTargetFrameBorder;
321 * Note: the log module is created during library initialization which
322 * means that you cannot perform logging before then.
324 mozilla::LazyLogModule nsIFrame::sFrameLogModule("frame");
326 #endif
328 NS_DECLARE_FRAME_PROPERTY_DELETABLE(AbsoluteContainingBlockProperty,
329 nsAbsoluteContainingBlock)
331 bool nsIFrame::HasAbsolutelyPositionedChildren() const {
332 return IsAbsoluteContainer() &&
333 GetAbsoluteContainingBlock()->HasAbsoluteFrames();
336 nsAbsoluteContainingBlock* nsIFrame::GetAbsoluteContainingBlock() const {
337 NS_ASSERTION(IsAbsoluteContainer(),
338 "The frame is not marked as an abspos container correctly");
339 nsAbsoluteContainingBlock* absCB =
340 GetProperty(AbsoluteContainingBlockProperty());
341 NS_ASSERTION(absCB,
342 "The frame is marked as an abspos container but doesn't have "
343 "the property");
344 return absCB;
347 void nsIFrame::MarkAsAbsoluteContainingBlock() {
348 MOZ_ASSERT(HasAnyStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN));
349 NS_ASSERTION(!GetProperty(AbsoluteContainingBlockProperty()),
350 "Already has an abs-pos containing block property?");
351 NS_ASSERTION(!HasAnyStateBits(NS_FRAME_HAS_ABSPOS_CHILDREN),
352 "Already has NS_FRAME_HAS_ABSPOS_CHILDREN state bit?");
353 AddStateBits(NS_FRAME_HAS_ABSPOS_CHILDREN);
354 SetProperty(AbsoluteContainingBlockProperty(),
355 new nsAbsoluteContainingBlock(GetAbsoluteListID()));
358 void nsIFrame::MarkAsNotAbsoluteContainingBlock() {
359 NS_ASSERTION(!HasAbsolutelyPositionedChildren(), "Think of the children!");
360 NS_ASSERTION(GetProperty(AbsoluteContainingBlockProperty()),
361 "Should have an abs-pos containing block property");
362 NS_ASSERTION(HasAnyStateBits(NS_FRAME_HAS_ABSPOS_CHILDREN),
363 "Should have NS_FRAME_HAS_ABSPOS_CHILDREN state bit");
364 MOZ_ASSERT(HasAnyStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN));
365 RemoveStateBits(NS_FRAME_HAS_ABSPOS_CHILDREN);
366 RemoveProperty(AbsoluteContainingBlockProperty());
369 bool nsIFrame::CheckAndClearPaintedState() {
370 bool result = HasAnyStateBits(NS_FRAME_PAINTED_THEBES);
371 RemoveStateBits(NS_FRAME_PAINTED_THEBES);
373 for (const auto& childList : ChildLists()) {
374 for (nsIFrame* child : childList.mList) {
375 if (child->CheckAndClearPaintedState()) {
376 result = true;
380 return result;
383 bool nsIFrame::CheckAndClearDisplayListState() {
384 bool result = BuiltDisplayList();
385 SetBuiltDisplayList(false);
387 for (const auto& childList : ChildLists()) {
388 for (nsIFrame* child : childList.mList) {
389 if (child->CheckAndClearDisplayListState()) {
390 result = true;
394 return result;
397 bool nsIFrame::IsVisibleConsideringAncestors(uint32_t aFlags) const {
398 if (!StyleVisibility()->IsVisible()) {
399 return false;
402 if (PresShell()->IsUnderHiddenEmbedderElement()) {
403 return false;
406 const nsIFrame* frame = this;
407 while (frame) {
408 nsView* view = frame->GetView();
409 if (view && view->GetVisibility() == nsViewVisibility_kHide) return false;
411 nsIFrame* parent = frame->GetParent();
412 nsDeckFrame* deck = do_QueryFrame(parent);
413 if (deck) {
414 if (deck->GetSelectedBox() != frame) return false;
417 if (parent) {
418 frame = parent;
419 } else {
420 parent = nsLayoutUtils::GetCrossDocParentFrameInProcess(frame);
421 if (!parent) break;
423 if ((aFlags & nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY) == 0 &&
424 parent->PresContext()->IsChrome() &&
425 !frame->PresContext()->IsChrome()) {
426 break;
429 frame = parent;
433 return true;
436 void nsIFrame::FindCloserFrameForSelection(
437 const nsPoint& aPoint, FrameWithDistance* aCurrentBestFrame) {
438 if (nsLayoutUtils::PointIsCloserToRect(aPoint, mRect,
439 aCurrentBestFrame->mXDistance,
440 aCurrentBestFrame->mYDistance)) {
441 aCurrentBestFrame->mFrame = this;
445 void nsIFrame::ContentStatesChanged(mozilla::EventStates aStates) {}
447 void WeakFrame::Clear(mozilla::PresShell* aPresShell) {
448 if (aPresShell) {
449 aPresShell->RemoveWeakFrame(this);
451 mFrame = nullptr;
454 AutoWeakFrame::AutoWeakFrame(const WeakFrame& aOther)
455 : mPrev(nullptr), mFrame(nullptr) {
456 Init(aOther.GetFrame());
459 void AutoWeakFrame::Clear(mozilla::PresShell* aPresShell) {
460 if (aPresShell) {
461 aPresShell->RemoveAutoWeakFrame(this);
463 mFrame = nullptr;
464 mPrev = nullptr;
467 AutoWeakFrame::~AutoWeakFrame() {
468 Clear(mFrame ? mFrame->PresContext()->GetPresShell() : nullptr);
471 void AutoWeakFrame::Init(nsIFrame* aFrame) {
472 Clear(mFrame ? mFrame->PresContext()->GetPresShell() : nullptr);
473 mFrame = aFrame;
474 if (mFrame) {
475 mozilla::PresShell* presShell = mFrame->PresContext()->GetPresShell();
476 NS_WARNING_ASSERTION(presShell, "Null PresShell in AutoWeakFrame!");
477 if (presShell) {
478 presShell->AddAutoWeakFrame(this);
479 } else {
480 mFrame = nullptr;
485 void WeakFrame::Init(nsIFrame* aFrame) {
486 Clear(mFrame ? mFrame->PresContext()->GetPresShell() : nullptr);
487 mFrame = aFrame;
488 if (mFrame) {
489 mozilla::PresShell* presShell = mFrame->PresContext()->GetPresShell();
490 MOZ_ASSERT(presShell, "Null PresShell in WeakFrame!");
491 if (presShell) {
492 presShell->AddWeakFrame(this);
493 } else {
494 mFrame = nullptr;
499 nsIFrame* NS_NewEmptyFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
500 return new (aPresShell) nsIFrame(aStyle, aPresShell->GetPresContext());
503 nsIFrame::~nsIFrame() {
504 MOZ_COUNT_DTOR(nsIFrame);
506 MOZ_ASSERT(GetVisibility() != Visibility::ApproximatelyVisible,
507 "Visible nsFrame is being destroyed");
510 NS_IMPL_FRAMEARENA_HELPERS(nsIFrame)
512 // Dummy operator delete. Will never be called, but must be defined
513 // to satisfy some C++ ABIs.
514 void nsIFrame::operator delete(void*, size_t) {
515 MOZ_CRASH("nsIFrame::operator delete should never be called");
518 NS_QUERYFRAME_HEAD(nsIFrame)
519 NS_QUERYFRAME_ENTRY(nsIFrame)
520 NS_QUERYFRAME_TAIL_INHERITANCE_ROOT
522 /////////////////////////////////////////////////////////////////////////////
523 // nsIFrame
525 static bool IsFontSizeInflationContainer(nsIFrame* aFrame,
526 const nsStyleDisplay* aStyleDisplay) {
528 * Font size inflation is built around the idea that we're inflating
529 * the fonts for a pan-and-zoom UI so that when the user scales up a
530 * block or other container to fill the width of the device, the fonts
531 * will be readable. To do this, we need to pick what counts as a
532 * container.
534 * From a code perspective, the only hard requirement is that frames
535 * that are line participants
536 * (nsIFrame::IsFrameOfType(nsIFrame::eLineParticipant)) are never
537 * containers, since line layout assumes that the inflation is
538 * consistent within a line.
540 * This is not an imposition, since we obviously want a bunch of text
541 * (possibly with inline elements) flowing within a block to count the
542 * block (or higher) as its container.
544 * We also want form controls, including the text in the anonymous
545 * content inside of them, to match each other and the text next to
546 * them, so they and their anonymous content should also not be a
547 * container.
549 * However, because we can't reliably compute sizes across XUL during
550 * reflow, any XUL frame with a XUL parent is always a container.
552 * There are contexts where it would be nice if some blocks didn't
553 * count as a container, so that, for example, an indented quotation
554 * didn't end up with a smaller font size. However, it's hard to
555 * distinguish these situations where we really do want the indented
556 * thing to count as a container, so we don't try, and blocks are
557 * always containers.
560 // The root frame should always be an inflation container.
561 if (!aFrame->GetParent()) {
562 return true;
565 nsIContent* content = aFrame->GetContent();
566 if (content && content->IsInNativeAnonymousSubtree()) {
567 // Native anonymous content shouldn't be a font inflation root,
568 // except for the canvas custom content container.
569 nsCanvasFrame* canvas = aFrame->PresShell()->GetCanvasFrame();
570 return canvas && canvas->GetCustomContentContainer() == content;
573 LayoutFrameType frameType = aFrame->Type();
574 bool isInline =
575 (nsStyleDisplay::IsInlineFlow(aFrame->GetDisplay()) ||
576 RubyUtils::IsRubyBox(frameType) ||
577 (aStyleDisplay->IsFloatingStyle() &&
578 frameType == LayoutFrameType::Letter) ||
579 // Given multiple frames for the same node, only the
580 // outer one should be considered a container.
581 // (Important, e.g., for nsSelectsAreaFrame.)
582 (aFrame->GetParent()->GetContent() == content) ||
583 (content &&
584 // Form controls shouldn't become inflation containers.
585 (content->IsAnyOfHTMLElements(
586 nsGkAtoms::option, nsGkAtoms::optgroup, nsGkAtoms::select,
587 nsGkAtoms::input, nsGkAtoms::button, nsGkAtoms::textarea)))) &&
588 !(aFrame->IsXULBoxFrame() && aFrame->GetParent()->IsXULBoxFrame());
589 NS_ASSERTION(!aFrame->IsFrameOfType(nsIFrame::eLineParticipant) || isInline ||
590 // br frames and mathml frames report being line
591 // participants even when their position or display is
592 // set
593 aFrame->IsBrFrame() ||
594 aFrame->IsFrameOfType(nsIFrame::eMathML),
595 "line participants must not be containers");
596 return !isInline;
599 static void MaybeScheduleReflowSVGNonDisplayText(nsIFrame* aFrame) {
600 if (!SVGUtils::IsInSVGTextSubtree(aFrame)) {
601 return;
604 // We need to ensure that any non-display SVGTextFrames get reflowed when a
605 // child text frame gets new style. Thus we need to schedule a reflow in
606 // |DidSetComputedStyle|. We also need to call it from |DestroyFrom|,
607 // because otherwise we won't get notified when style changes to
608 // "display:none".
609 SVGTextFrame* svgTextFrame = static_cast<SVGTextFrame*>(
610 nsLayoutUtils::GetClosestFrameOfType(aFrame, LayoutFrameType::SVGText));
611 nsIFrame* anonBlock = svgTextFrame->PrincipalChildList().FirstChild();
613 // Note that we must check NS_FRAME_FIRST_REFLOW on our SVGTextFrame's
614 // anonymous block frame rather than our aFrame, since NS_FRAME_FIRST_REFLOW
615 // may be set on us if we're a new frame that has been inserted after the
616 // document's first reflow. (In which case this DidSetComputedStyle call may
617 // be happening under frame construction under a Reflow() call.)
618 if (!anonBlock || anonBlock->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
619 return;
622 if (!svgTextFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY) ||
623 svgTextFrame->HasAnyStateBits(NS_STATE_SVG_TEXT_IN_REFLOW)) {
624 return;
627 svgTextFrame->ScheduleReflowSVGNonDisplayText(IntrinsicDirty::StyleChange);
630 bool nsIFrame::IsPrimaryFrameOfRootOrBodyElement() const {
631 if (!IsPrimaryFrame()) {
632 return false;
634 nsIContent* content = GetContent();
635 Document* document = content->OwnerDoc();
636 return content == document->GetRootElement() ||
637 content == document->GetBodyElement();
640 bool nsIFrame::IsRenderedLegend() const {
641 if (auto* parent = GetParent(); parent && parent->IsFieldSetFrame()) {
642 return static_cast<nsFieldSetFrame*>(parent)->GetLegend() == this;
644 return false;
647 void nsIFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
648 nsIFrame* aPrevInFlow) {
649 MOZ_ASSERT(nsQueryFrame::FrameIID(mClass) == GetFrameId());
650 MOZ_ASSERT(!mContent, "Double-initing a frame?");
651 NS_ASSERTION(IsFrameOfType(eDEBUGAllFrames) && !IsFrameOfType(eDEBUGNoFrames),
652 "IsFrameOfType implementation that doesn't call base class");
654 mContent = aContent;
655 mParent = aParent;
656 MOZ_DIAGNOSTIC_ASSERT(!mParent || PresShell() == mParent->PresShell());
658 if (aPrevInFlow) {
659 mWritingMode = aPrevInFlow->GetWritingMode();
661 // Copy some state bits from prev-in-flow (the bits that should apply
662 // throughout a continuation chain). The bits are sorted according to their
663 // order in nsFrameStateBits.h.
665 // clang-format off
666 AddStateBits(aPrevInFlow->GetStateBits() &
667 (NS_FRAME_GENERATED_CONTENT |
668 NS_FRAME_OUT_OF_FLOW |
669 NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN |
670 NS_FRAME_INDEPENDENT_SELECTION |
671 NS_FRAME_PART_OF_IBSPLIT |
672 NS_FRAME_MAY_BE_TRANSFORMED |
673 NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR));
674 // clang-format on
676 // Copy other bits in nsIFrame from prev-in-flow.
677 mHasColumnSpanSiblings = aPrevInFlow->HasColumnSpanSiblings();
678 } else {
679 PresContext()->ConstructedFrame();
682 if (GetParent()) {
683 if (MOZ_UNLIKELY(mContent == PresContext()->Document()->GetRootElement() &&
684 mContent == GetParent()->GetContent())) {
685 // Our content is the root element and we have the same content as our
686 // parent. That is, we are the internal anonymous frame of the root
687 // element. Copy the used mWritingMode from our parent because
688 // mDocElementContainingBlock gets its mWritingMode from <body>.
689 mWritingMode = GetParent()->GetWritingMode();
692 // Copy some state bits from our parent (the bits that should apply
693 // recursively throughout a subtree). The bits are sorted according to their
694 // order in nsFrameStateBits.h.
696 // clang-format off
697 AddStateBits(GetParent()->GetStateBits() &
698 (NS_FRAME_GENERATED_CONTENT |
699 NS_FRAME_INDEPENDENT_SELECTION |
700 NS_FRAME_IS_SVG_TEXT |
701 NS_FRAME_IN_POPUP |
702 NS_FRAME_IS_NONDISPLAY));
703 // clang-format on
705 if (HasAnyStateBits(NS_FRAME_IN_POPUP) && TrackingVisibility()) {
706 // Assume all frames in popups are visible.
707 IncApproximateVisibleCount();
710 if (aPrevInFlow) {
711 mMayHaveOpacityAnimation = aPrevInFlow->MayHaveOpacityAnimation();
712 mMayHaveTransformAnimation = aPrevInFlow->MayHaveTransformAnimation();
713 } else if (mContent) {
714 // It's fine to fetch the EffectSet for the style frame here because in the
715 // following code we take care of the case where animations may target
716 // a different frame.
717 EffectSet* effectSet = EffectSet::GetEffectSetForStyleFrame(this);
718 if (effectSet) {
719 mMayHaveOpacityAnimation = effectSet->MayHaveOpacityAnimation();
721 if (effectSet->MayHaveTransformAnimation()) {
722 // If we are the inner table frame for display:table content, then
723 // transform animations should go on our parent frame (the table wrapper
724 // frame).
726 // We do this when initializing the child frame (table inner frame),
727 // because when initializng the table wrapper frame, we don't yet have
728 // access to its children so we can't tell if we have transform
729 // animations or not.
730 if (IsFrameOfType(eSupportsCSSTransforms)) {
731 mMayHaveTransformAnimation = true;
732 AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED);
733 } else if (aParent && nsLayoutUtils::GetStyleFrame(aParent) == this) {
734 MOZ_ASSERT(
735 aParent->IsFrameOfType(eSupportsCSSTransforms),
736 "Style frames that don't support transforms should have parents"
737 " that do");
738 aParent->mMayHaveTransformAnimation = true;
739 aParent->AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED);
745 const nsStyleDisplay* disp = StyleDisplay();
746 if (disp->HasTransform(this)) {
747 // If 'transform' dynamically changes, RestyleManager takes care of
748 // updating this bit.
749 AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED);
752 if (disp->IsContainLayout() && disp->IsContainSize() &&
753 // All frames that support contain:layout also support contain:size.
754 IsFrameOfType(eSupportsContainLayoutAndPaint) && !IsTableWrapperFrame()) {
755 // In general, frames that have contain:layout+size can be reflow roots.
756 // (One exception: table-wrapper frames don't work well as reflow roots,
757 // because their inner-table ReflowInput init path tries to reuse & deref
758 // the wrapper's containing block's reflow input, which may be null if we
759 // initiate reflow from the table-wrapper itself.)
761 // Changes to `contain` force frame reconstructions, so this bit can be set
762 // for the whole lifetime of this frame.
763 AddStateBits(NS_FRAME_REFLOW_ROOT);
766 if (nsLayoutUtils::FontSizeInflationEnabled(PresContext()) ||
767 !GetParent()
768 #ifdef DEBUG
769 // We have assertions that check inflation invariants even when
770 // font size inflation is not enabled.
771 || true
772 #endif
774 if (IsFontSizeInflationContainer(this, disp)) {
775 AddStateBits(NS_FRAME_FONT_INFLATION_CONTAINER);
776 if (!GetParent() ||
777 // I'd use NS_FRAME_OUT_OF_FLOW, but it's not set yet.
778 disp->IsFloating(this) || disp->IsAbsolutelyPositioned(this) ||
779 GetParent()->IsFlexContainerFrame() ||
780 GetParent()->IsGridContainerFrame()) {
781 AddStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT);
784 NS_ASSERTION(
785 GetParent() || HasAnyStateBits(NS_FRAME_FONT_INFLATION_CONTAINER),
786 "root frame should always be a container");
789 if (PresShell()->AssumeAllFramesVisible() && TrackingVisibility()) {
790 IncApproximateVisibleCount();
793 DidSetComputedStyle(nullptr);
795 if (::IsXULBoxWrapped(this)) ::InitBoxMetrics(this, false);
797 // For a newly created frame, we need to update this frame's visibility state.
798 // Usually we update the state when the frame is restyled and has a
799 // VisibilityChange change hint but we don't generate any change hints for
800 // newly created frames.
801 // Note: We don't need to do this for placeholders since placeholders have
802 // different styles so that the styles don't have visibility:hidden even if
803 // the parent has visibility:hidden style. We also don't need to update the
804 // state when creating continuations because its visibility is the same as its
805 // prev-in-flow, and the animation code cares only primary frames.
806 if (!IsPlaceholderFrame() && !aPrevInFlow) {
807 UpdateVisibleDescendantsState();
811 void nsIFrame::DestroyFrom(nsIFrame* aDestructRoot,
812 PostDestroyData& aPostDestroyData) {
813 NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
814 "destroy called on frame while scripts not blocked");
815 NS_ASSERTION(!GetNextSibling() && !GetPrevSibling(),
816 "Frames should be removed before destruction.");
817 NS_ASSERTION(aDestructRoot, "Must specify destruct root");
818 MOZ_ASSERT(!HasAbsolutelyPositionedChildren());
819 MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT),
820 "NS_FRAME_PART_OF_IBSPLIT set on non-nsContainerFrame?");
822 MaybeScheduleReflowSVGNonDisplayText(this);
824 SVGObserverUtils::InvalidateDirectRenderingObservers(this);
826 if (StyleDisplay()->mPosition == StylePositionProperty::Sticky) {
827 StickyScrollContainer* ssc =
828 StickyScrollContainer::GetStickyScrollContainerForFrame(this);
829 if (ssc) {
830 ssc->RemoveFrame(this);
834 nsPresContext* presContext = PresContext();
835 mozilla::PresShell* presShell = presContext->GetPresShell();
836 if (mState & NS_FRAME_OUT_OF_FLOW) {
837 nsPlaceholderFrame* placeholder = GetPlaceholderFrame();
838 NS_ASSERTION(
839 !placeholder || (aDestructRoot != this),
840 "Don't call Destroy() on OOFs, call Destroy() on the placeholder.");
841 NS_ASSERTION(!placeholder || nsLayoutUtils::IsProperAncestorFrame(
842 aDestructRoot, placeholder),
843 "Placeholder relationship should have been torn down already; "
844 "this might mean we have a stray placeholder in the tree.");
845 if (placeholder) {
846 placeholder->SetOutOfFlowFrame(nullptr);
850 if (IsPrimaryFrame()) {
851 // This needs to happen before we clear our Properties() table.
852 ActiveLayerTracker::TransferActivityToContent(this, mContent);
855 ScrollAnchorContainer* anchor = nullptr;
856 if (IsScrollAnchor(&anchor)) {
857 anchor->InvalidateAnchor();
860 if (HasCSSAnimations() || HasCSSTransitions() ||
861 // It's fine to look up the style frame here since if we're destroying the
862 // frames for display:table content we should be destroying both wrapper
863 // and inner frame.
864 EffectSet::GetEffectSetForStyleFrame(this)) {
865 // If no new frame for this element is created by the end of the
866 // restyling process, stop animations and transitions for this frame
867 RestyleManager::AnimationsWithDestroyedFrame* adf =
868 presContext->RestyleManager()->GetAnimationsWithDestroyedFrame();
869 // AnimationsWithDestroyedFrame only lives during the restyling process.
870 if (adf) {
871 adf->Put(mContent, mComputedStyle);
875 // Disable visibility tracking. Note that we have to do this before we clear
876 // frame properties and lose track of whether we were previously visible.
877 // XXX(seth): It'd be ideal to assert that we're already marked nonvisible
878 // here, but it's unfortunately tricky to guarantee in the face of things like
879 // frame reconstruction induced by style changes.
880 DisableVisibilityTracking();
882 // Ensure that we're not in the approximately visible list anymore.
883 PresContext()->GetPresShell()->RemoveFrameFromApproximatelyVisibleList(this);
885 presShell->NotifyDestroyingFrame(this);
887 if (mState & NS_FRAME_EXTERNAL_REFERENCE) {
888 presShell->ClearFrameRefs(this);
891 nsView* view = GetView();
892 if (view) {
893 view->SetFrame(nullptr);
894 view->Destroy();
897 // Make sure that our deleted frame can't be returned from GetPrimaryFrame()
898 if (IsPrimaryFrame()) {
899 mContent->SetPrimaryFrame(nullptr);
901 // Pass the root of a generated content subtree (e.g. ::after/::before) to
902 // aPostDestroyData to unbind it after frame destruction is done.
903 if (HasAnyStateBits(NS_FRAME_GENERATED_CONTENT) &&
904 mContent->IsRootOfNativeAnonymousSubtree()) {
905 aPostDestroyData.AddAnonymousContent(mContent.forget());
909 // Remove all properties attached to the frame, to ensure any property
910 // destructors that need the frame pointer are handled properly.
911 RemoveAllProperties();
913 // Must retrieve the object ID before calling destructors, so the
914 // vtable is still valid.
916 // Note to future tweakers: having the method that returns the
917 // object size call the destructor will not avoid an indirect call;
918 // the compiler cannot devirtualize the call to the destructor even
919 // if it's from a method defined in the same class.
921 nsQueryFrame::FrameIID id = GetFrameId();
922 this->~nsIFrame();
924 #ifdef DEBUG
926 nsIFrame* rootFrame = presShell->GetRootFrame();
927 MOZ_ASSERT(rootFrame);
928 if (this != rootFrame) {
929 const RetainedDisplayListData* data =
930 GetRetainedDisplayListData(rootFrame);
932 const bool inModifiedList = data && data->IsModified(this);
934 if (inModifiedList) {
935 DL_LOG(LogLevel::Warning, "Frame %p found in modified list", this);
938 MOZ_ASSERT(!inModifiedList,
939 "A dtor added this frame to modified frames list!");
942 #endif
944 // Now that we're totally cleaned out, we need to add ourselves to
945 // the presshell's recycler.
946 presShell->FreeFrame(id, this);
949 std::pair<int32_t, int32_t> nsIFrame::GetOffsets() const {
950 return std::make_pair(0, 0);
953 static void CompareLayers(
954 const nsStyleImageLayers* aFirstLayers,
955 const nsStyleImageLayers* aSecondLayers,
956 const std::function<void(imgRequestProxy* aReq)>& aCallback) {
957 NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, (*aFirstLayers)) {
958 const auto& image = aFirstLayers->mLayers[i].mImage;
959 if (!image.IsImageRequestType() || !image.IsResolved()) {
960 continue;
963 // aCallback is called when the style image in aFirstLayers is thought to
964 // be different with the corresponded one in aSecondLayers
965 if (!aSecondLayers || i >= aSecondLayers->mImageCount ||
966 (!aSecondLayers->mLayers[i].mImage.IsResolved() ||
967 image.GetImageRequest() !=
968 aSecondLayers->mLayers[i].mImage.GetImageRequest())) {
969 if (imgRequestProxy* req = image.GetImageRequest()) {
970 aCallback(req);
976 static void AddAndRemoveImageAssociations(
977 ImageLoader& aImageLoader, nsIFrame* aFrame,
978 const nsStyleImageLayers* aOldLayers,
979 const nsStyleImageLayers* aNewLayers) {
980 // If the old context had a background-image image, or mask-image image,
981 // and new context does not have the same image, clear the image load
982 // notifier (which keeps the image loading, if it still is) for the frame.
983 // We want to do this conservatively because some frames paint their
984 // backgrounds from some other frame's style data, and we don't want
985 // to clear those notifiers unless we have to. (They'll be reset
986 // when we paint, although we could miss a notification in that
987 // interval.)
988 if (aOldLayers && aFrame->HasImageRequest()) {
989 CompareLayers(aOldLayers, aNewLayers, [&](imgRequestProxy* aReq) {
990 aImageLoader.DisassociateRequestFromFrame(aReq, aFrame);
994 CompareLayers(aNewLayers, aOldLayers, [&](imgRequestProxy* aReq) {
995 aImageLoader.AssociateRequestToFrame(aReq, aFrame);
999 void nsIFrame::AddDisplayItem(nsDisplayItem* aItem) {
1000 MOZ_DIAGNOSTIC_ASSERT(!mDisplayItems.Contains(aItem));
1001 mDisplayItems.AppendElement(aItem);
1004 bool nsIFrame::RemoveDisplayItem(nsDisplayItem* aItem) {
1005 return mDisplayItems.RemoveElement(aItem);
1008 bool nsIFrame::HasDisplayItems() { return !mDisplayItems.IsEmpty(); }
1010 bool nsIFrame::HasDisplayItem(nsDisplayItem* aItem) {
1011 return mDisplayItems.Contains(aItem);
1014 bool nsIFrame::HasDisplayItem(uint32_t aKey) {
1015 for (nsDisplayItem* i : mDisplayItems) {
1016 if (i->GetPerFrameKey() == aKey) {
1017 return true;
1020 return false;
1023 template <typename Condition>
1024 static void DiscardDisplayItems(nsIFrame* aFrame, Condition aCondition) {
1025 for (nsDisplayItem* i : aFrame->DisplayItems()) {
1026 // Only discard items that are invalidated by this frame, as we're only
1027 // guaranteed to rebuild those items. Table background items are created by
1028 // the relevant table part, but have the cell frame as the primary frame,
1029 // and we don't want to remove them if this is the cell.
1030 if (aCondition(i) && i->FrameForInvalidation() == aFrame) {
1031 i->SetCantBeReused();
1036 static void DiscardOldItems(nsIFrame* aFrame) {
1037 DiscardDisplayItems(aFrame,
1038 [](nsDisplayItem* aItem) { return aItem->IsOldItem(); });
1041 void nsIFrame::RemoveDisplayItemDataForDeletion() {
1042 nsAutoString name;
1043 #ifdef DEBUG_FRAME_DUMP
1044 if (DL_LOG_TEST(LogLevel::Debug)) {
1045 GetFrameName(name);
1047 #endif
1048 DL_LOGV("Removing display item data for frame %p (%s)", this,
1049 NS_ConvertUTF16toUTF8(name).get());
1051 // Destroying a WebRenderUserDataTable can cause destruction of other objects
1052 // which can remove frame properties in their destructor. If we delete a frame
1053 // property it runs the destructor of the stored object in the middle of
1054 // updating the frame property table, so if the destruction of that object
1055 // causes another update to the frame property table it would leave the frame
1056 // property table in an inconsistent state. So we remove it from the table and
1057 // then destroy it. (bug 1530657)
1058 WebRenderUserDataTable* userDataTable =
1059 TakeProperty(WebRenderUserDataProperty::Key());
1060 if (userDataTable) {
1061 for (const auto& data : userDataTable->Values()) {
1062 data->RemoveFromTable();
1064 delete userDataTable;
1067 for (nsDisplayItem* i : DisplayItems()) {
1068 if (i->GetDependentFrame() == this && !i->HasDeletedFrame()) {
1069 i->Frame()->MarkNeedsDisplayItemRebuild();
1071 i->RemoveFrame(this);
1074 DisplayItems().Clear();
1076 if (!nsLayoutUtils::AreRetainedDisplayListsEnabled()) {
1077 // Retained display lists are disabled, no need to update
1078 // RetainedDisplayListData.
1079 return;
1082 nsIFrame* rootFrame = PresShell()->GetRootFrame();
1083 MOZ_ASSERT(rootFrame);
1085 RetainedDisplayListData* data = GetOrSetRetainedDisplayListData(rootFrame);
1087 const bool updateData = IsFrameModified() || HasOverrideDirtyRegion() ||
1088 MayHaveWillChangeBudget();
1090 if (!updateData) {
1091 // No RetainedDisplayListData to update.
1092 MOZ_DIAGNOSTIC_ASSERT(!data->IsModified(this),
1093 "Deleted frame is in modified frame list");
1094 return;
1097 if (MayHaveWillChangeBudget()) {
1098 // Keep the frame in list, so it can be removed from the will-change budget.
1099 data->Flags(this) = RetainedDisplayListData::FrameFlag::HadWillChange;
1100 return;
1103 if (IsFrameModified() || HasOverrideDirtyRegion()) {
1104 // Remove deleted frames from RetainedDisplayListData.
1105 DebugOnly<bool> removed = data->Remove(this);
1106 MOZ_ASSERT(removed,
1107 "Frame had flags set, but it was not found in DisplayListData!");
1111 void nsIFrame::MarkNeedsDisplayItemRebuild() {
1112 if (!nsLayoutUtils::AreRetainedDisplayListsEnabled() || IsFrameModified() ||
1113 HasAnyStateBits(NS_FRAME_IN_POPUP)) {
1114 // Skip frames that are already marked modified.
1115 return;
1118 if (Type() == LayoutFrameType::Placeholder) {
1119 nsIFrame* oof = static_cast<nsPlaceholderFrame*>(this)->GetOutOfFlowFrame();
1120 if (oof) {
1121 oof->MarkNeedsDisplayItemRebuild();
1123 // Do not mark placeholder frames modified.
1124 return;
1127 if (!nsLayoutUtils::DisplayRootHasRetainedDisplayListBuilder(this)) {
1128 return;
1131 nsIFrame* rootFrame = PresShell()->GetRootFrame();
1132 MOZ_ASSERT(rootFrame);
1134 if (rootFrame->IsFrameModified()) {
1135 return;
1138 nsAutoString name;
1139 #ifdef DEBUG_FRAME_DUMP
1140 if (DL_LOG_TEST(LogLevel::Debug)) {
1141 GetFrameName(name);
1143 #endif
1145 DL_LOGV("RDL - Rebuilding display items for frame %p (%s)", this,
1146 NS_ConvertUTF16toUTF8(name).get());
1148 RetainedDisplayListData* data = GetOrSetRetainedDisplayListData(rootFrame);
1149 if (data->ModifiedFramesCount() >
1150 StaticPrefs::layout_display_list_rebuild_frame_limit()) {
1151 // If the modified frames count is above the rebuild limit, mark the root
1152 // frame modified, and stop marking additional frames modified.
1153 data->AddModifiedFrame(rootFrame);
1154 rootFrame->SetFrameIsModified(true);
1155 return;
1158 data->AddModifiedFrame(this);
1159 SetFrameIsModified(true);
1161 MOZ_ASSERT(
1162 PresContext()->LayoutPhaseCount(nsLayoutPhase::DisplayListBuilding) == 0);
1164 // Hopefully this is cheap, but we could use a frame state bit to note
1165 // the presence of dependencies to speed it up.
1166 for (nsDisplayItem* i : DisplayItems()) {
1167 if (i->HasDeletedFrame() || i->Frame() == this) {
1168 // Ignore the items with deleted frames, and the items with |this| as
1169 // the primary frame.
1170 continue;
1173 if (i->GetDependentFrame() == this) {
1174 // For items with |this| as a dependent frame, mark the primary frame
1175 // for rebuild.
1176 i->Frame()->MarkNeedsDisplayItemRebuild();
1181 // Subclass hook for style post processing
1182 /* virtual */
1183 void nsIFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) {
1184 #ifdef ACCESSIBILITY
1185 // Don't notify for reconstructed frames here, since the frame is still being
1186 // constructed at this point and so LocalAccessible::GetFrame() will return
1187 // null. Style changes for reconstructed frames are handled in
1188 // DocAccessible::PruneOrInsertSubtree.
1189 if (aOldComputedStyle) {
1190 if (nsAccessibilityService* accService = GetAccService()) {
1191 accService->NotifyOfComputedStyleChange(PresShell(), mContent);
1194 #endif
1196 MaybeScheduleReflowSVGNonDisplayText(this);
1198 Document* doc = PresContext()->Document();
1199 ImageLoader* loader = doc->StyleImageLoader();
1200 // Continuing text frame doesn't initialize its continuation pointer before
1201 // reaching here for the first time, so we have to exclude text frames. This
1202 // doesn't affect correctness because text can't match selectors.
1204 // FIXME(emilio): We should consider fixing that.
1206 // TODO(emilio): Can we avoid doing some / all of the image stuff when
1207 // isNonTextFirstContinuation is false? We should consider doing this just for
1208 // primary frames and pseudos, but the first-line reparenting code makes it
1209 // all bad, should get around to bug 1465474 eventually :(
1210 const bool isNonText = !IsTextFrame();
1211 if (isNonText) {
1212 mComputedStyle->StartImageLoads(*doc, aOldComputedStyle);
1215 const nsStyleImageLayers* oldLayers =
1216 aOldComputedStyle ? &aOldComputedStyle->StyleBackground()->mImage
1217 : nullptr;
1218 const nsStyleImageLayers* newLayers = &StyleBackground()->mImage;
1219 AddAndRemoveImageAssociations(*loader, this, oldLayers, newLayers);
1221 oldLayers =
1222 aOldComputedStyle ? &aOldComputedStyle->StyleSVGReset()->mMask : nullptr;
1223 newLayers = &StyleSVGReset()->mMask;
1224 AddAndRemoveImageAssociations(*loader, this, oldLayers, newLayers);
1226 const nsStyleDisplay* disp = StyleDisplay();
1227 bool handleStickyChange = false;
1228 if (aOldComputedStyle) {
1229 // Detect style changes that should trigger a scroll anchor adjustment
1230 // suppression.
1231 // https://drafts.csswg.org/css-scroll-anchoring/#suppression-triggers
1232 bool needAnchorSuppression = false;
1234 // If we detect a change on margin, padding or border, we store the old
1235 // values on the frame itself between now and reflow, so if someone
1236 // calls GetUsed(Margin|Border|Padding)() before the next reflow, we
1237 // can give an accurate answer.
1238 // We don't want to set the property if one already exists.
1239 nsMargin oldValue(0, 0, 0, 0);
1240 nsMargin newValue(0, 0, 0, 0);
1241 const nsStyleMargin* oldMargin = aOldComputedStyle->StyleMargin();
1242 if (oldMargin->GetMargin(oldValue)) {
1243 if (!StyleMargin()->GetMargin(newValue) || oldValue != newValue) {
1244 if (!HasProperty(UsedMarginProperty())) {
1245 AddProperty(UsedMarginProperty(), new nsMargin(oldValue));
1247 needAnchorSuppression = true;
1251 const nsStylePadding* oldPadding = aOldComputedStyle->StylePadding();
1252 if (oldPadding->GetPadding(oldValue)) {
1253 if (!StylePadding()->GetPadding(newValue) || oldValue != newValue) {
1254 if (!HasProperty(UsedPaddingProperty())) {
1255 AddProperty(UsedPaddingProperty(), new nsMargin(oldValue));
1257 needAnchorSuppression = true;
1261 const nsStyleBorder* oldBorder = aOldComputedStyle->StyleBorder();
1262 oldValue = oldBorder->GetComputedBorder();
1263 newValue = StyleBorder()->GetComputedBorder();
1264 if (oldValue != newValue && !HasProperty(UsedBorderProperty())) {
1265 AddProperty(UsedBorderProperty(), new nsMargin(oldValue));
1268 const nsStyleDisplay* oldDisp = aOldComputedStyle->StyleDisplay();
1269 if (oldDisp->mOverflowAnchor != disp->mOverflowAnchor) {
1270 if (auto* container = ScrollAnchorContainer::FindFor(this)) {
1271 container->InvalidateAnchor();
1273 if (nsIScrollableFrame* scrollableFrame = do_QueryFrame(this)) {
1274 scrollableFrame->Anchor()->InvalidateAnchor();
1278 if (mInScrollAnchorChain) {
1279 const nsStylePosition* pos = StylePosition();
1280 const nsStylePosition* oldPos = aOldComputedStyle->StylePosition();
1281 if (!needAnchorSuppression &&
1282 (oldPos->mOffset != pos->mOffset || oldPos->mWidth != pos->mWidth ||
1283 oldPos->mMinWidth != pos->mMinWidth ||
1284 oldPos->mMaxWidth != pos->mMaxWidth ||
1285 oldPos->mHeight != pos->mHeight ||
1286 oldPos->mMinHeight != pos->mMinHeight ||
1287 oldPos->mMaxHeight != pos->mMaxHeight ||
1288 oldDisp->mPosition != disp->mPosition ||
1289 oldDisp->mTransform != disp->mTransform)) {
1290 needAnchorSuppression = true;
1293 if (needAnchorSuppression &&
1294 StaticPrefs::layout_css_scroll_anchoring_suppressions_enabled()) {
1295 ScrollAnchorContainer::FindFor(this)->SuppressAdjustments();
1299 if (disp->mPosition != oldDisp->mPosition) {
1300 if (!disp->IsRelativelyOrStickyPositionedStyle() &&
1301 oldDisp->IsRelativelyOrStickyPositionedStyle()) {
1302 RemoveProperty(NormalPositionProperty());
1305 handleStickyChange = disp->mPosition == StylePositionProperty::Sticky ||
1306 oldDisp->mPosition == StylePositionProperty::Sticky;
1308 } else { // !aOldComputedStyle
1309 handleStickyChange = disp->mPosition == StylePositionProperty::Sticky;
1312 if (handleStickyChange && !HasAnyStateBits(NS_FRAME_IS_NONDISPLAY) &&
1313 !GetPrevInFlow()) {
1314 // Note that we only add first continuations, but we really only
1315 // want to add first continuation-or-ib-split-siblings. But since we don't
1316 // yet know if we're a later part of a block-in-inline split, we'll just
1317 // add later members of a block-in-inline split here, and then
1318 // StickyScrollContainer will remove them later.
1319 if (auto* ssc =
1320 StickyScrollContainer::GetStickyScrollContainerForFrame(this)) {
1321 if (disp->mPosition == StylePositionProperty::Sticky) {
1322 ssc->AddFrame(this);
1323 } else {
1324 ssc->RemoveFrame(this);
1329 imgIRequest* oldBorderImage =
1330 aOldComputedStyle
1331 ? aOldComputedStyle->StyleBorder()->GetBorderImageRequest()
1332 : nullptr;
1333 imgIRequest* newBorderImage = StyleBorder()->GetBorderImageRequest();
1334 // FIXME (Bug 759996): The following is no longer true.
1335 // For border-images, we can't be as conservative (we need to set the
1336 // new loaders if there has been any change) since the CalcDifference
1337 // call depended on the result of GetComputedBorder() and that result
1338 // depends on whether the image has loaded, start the image load now
1339 // so that we'll get notified when it completes loading and can do a
1340 // restyle. Otherwise, the image might finish loading from the
1341 // network before we start listening to its notifications, and then
1342 // we'll never know that it's finished loading. Likewise, we want to
1343 // do this for freshly-created frames to prevent a similar race if the
1344 // image loads between reflow (which can depend on whether the image
1345 // is loaded) and paint. We also don't really care about any callers who try
1346 // to paint borders with a different style, because they won't have the
1347 // correct size for the border either.
1348 if (oldBorderImage != newBorderImage) {
1349 // stop and restart the image loading/notification
1350 if (oldBorderImage && HasImageRequest()) {
1351 RemoveProperty(CachedBorderImageDataProperty());
1352 loader->DisassociateRequestFromFrame(oldBorderImage, this);
1354 if (newBorderImage) {
1355 loader->AssociateRequestToFrame(newBorderImage, this);
1359 auto GetShapeImageRequest = [](const ComputedStyle* aStyle) -> imgIRequest* {
1360 if (!aStyle) {
1361 return nullptr;
1363 auto& shape = aStyle->StyleDisplay()->mShapeOutside;
1364 if (!shape.IsImage()) {
1365 return nullptr;
1367 return shape.AsImage().GetImageRequest();
1370 imgIRequest* oldShapeImage = GetShapeImageRequest(aOldComputedStyle);
1371 imgIRequest* newShapeImage = GetShapeImageRequest(Style());
1372 if (oldShapeImage != newShapeImage) {
1373 if (oldShapeImage && HasImageRequest()) {
1374 loader->DisassociateRequestFromFrame(oldShapeImage, this);
1376 if (newShapeImage) {
1377 loader->AssociateRequestToFrame(
1378 newShapeImage, this,
1379 ImageLoader::Flags::
1380 RequiresReflowOnFirstFrameCompleteAndLoadEventBlocking);
1384 // SVGObserverUtils::GetEffectProperties() asserts that we only invoke it with
1385 // the first continuation so we need to check that in advance.
1386 const bool isNonTextFirstContinuation = isNonText && !GetPrevContinuation();
1387 if (isNonTextFirstContinuation) {
1388 // Kick off loading of external SVG resources referenced from properties if
1389 // any. This currently includes filter, clip-path, and mask.
1390 SVGObserverUtils::InitiateResourceDocLoads(this);
1393 // If the page contains markup that overrides text direction, and
1394 // does not contain any characters that would activate the Unicode
1395 // bidi algorithm, we need to call |SetBidiEnabled| on the pres
1396 // context before reflow starts. See bug 115921.
1397 if (StyleVisibility()->mDirection == StyleDirection::Rtl) {
1398 PresContext()->SetBidiEnabled();
1401 // The following part is for caching offset-path:path(). We cache the
1402 // flatten gfx path, so we don't have to rebuild and re-flattern it at
1403 // each cycle if we have animations on offset-* with a fixed offset-path.
1404 const StyleOffsetPath* oldPath =
1405 aOldComputedStyle ? &aOldComputedStyle->StyleDisplay()->mOffsetPath
1406 : nullptr;
1407 const StyleOffsetPath& newPath = StyleDisplay()->mOffsetPath;
1408 if (!oldPath || *oldPath != newPath) {
1409 if (newPath.IsPath()) {
1410 // Here we only need to build a valid path for motion path, so
1411 // using the default values of stroke-width, stoke-linecap, and fill-rule
1412 // is fine for now because what we want is to get the point and its normal
1413 // vector along the path, instead of rendering it.
1414 RefPtr<gfx::PathBuilder> builder =
1415 gfxPlatform::GetPlatform()
1416 ->ScreenReferenceDrawTarget()
1417 ->CreatePathBuilder(gfx::FillRule::FILL_WINDING);
1418 RefPtr<gfx::Path> path =
1419 MotionPathUtils::BuildPath(newPath.AsPath(), builder);
1420 if (path) {
1421 // The newPath could be path('') (i.e. empty path), so its gfx path
1422 // could be nullptr, and so we only set property for a non-empty path.
1423 SetProperty(nsIFrame::OffsetPathCache(), path.forget().take());
1424 } else {
1425 // May have an old cached path, so we have to delete it.
1426 RemoveProperty(nsIFrame::OffsetPathCache());
1428 } else if (oldPath) {
1429 RemoveProperty(nsIFrame::OffsetPathCache());
1433 RemoveStateBits(NS_FRAME_SIMPLE_EVENT_REGIONS | NS_FRAME_SIMPLE_DISPLAYLIST);
1435 mMayHaveRoundedCorners = true;
1438 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
1439 void nsIFrame::AssertNewStyleIsSane(ComputedStyle& aNewStyle) {
1440 MOZ_DIAGNOSTIC_ASSERT(
1441 aNewStyle.GetPseudoType() == mComputedStyle->GetPseudoType() ||
1442 // ::first-line continuations are weird, this should probably be fixed via
1443 // bug 1465474.
1444 (mComputedStyle->GetPseudoType() == PseudoStyleType::firstLine &&
1445 aNewStyle.GetPseudoType() == PseudoStyleType::mozLineFrame) ||
1446 // ::first-letter continuations are broken, in particular floating ones,
1447 // see bug 1490281. The construction code tries to fix this up after the
1448 // fact, then restyling undoes it...
1449 (mComputedStyle->GetPseudoType() == PseudoStyleType::mozText &&
1450 aNewStyle.GetPseudoType() == PseudoStyleType::firstLetterContinuation) ||
1451 (mComputedStyle->GetPseudoType() ==
1452 PseudoStyleType::firstLetterContinuation &&
1453 aNewStyle.GetPseudoType() == PseudoStyleType::mozText));
1455 #endif
1457 void nsIFrame::ReparentFrameViewTo(nsViewManager* aViewManager,
1458 nsView* aNewParentView,
1459 nsView* aOldParentView) {
1460 if (HasView()) {
1461 if (IsMenuPopupFrame()) {
1462 // This view must be parented by the root view, don't reparent it.
1463 return;
1465 nsView* view = GetView();
1466 // Verify that the current parent view is what we think it is
1467 // nsView* parentView;
1468 // NS_ASSERTION(parentView == aOldParentView, "unexpected parent view");
1470 aViewManager->RemoveChild(view);
1472 // The view will remember the Z-order and other attributes that have been
1473 // set on it.
1474 nsView* insertBefore =
1475 nsLayoutUtils::FindSiblingViewFor(aNewParentView, this);
1476 aViewManager->InsertChild(aNewParentView, view, insertBefore,
1477 insertBefore != nullptr);
1478 } else if (HasAnyStateBits(NS_FRAME_HAS_CHILD_WITH_VIEW)) {
1479 for (const auto& childList : ChildLists()) {
1480 // Iterate the child frames, and check each child frame to see if it has
1481 // a view
1482 for (nsIFrame* child : childList.mList) {
1483 child->ReparentFrameViewTo(aViewManager, aNewParentView,
1484 aOldParentView);
1490 void nsIFrame::SyncFrameViewProperties(nsView* aView) {
1491 if (!aView) {
1492 aView = GetView();
1493 if (!aView) {
1494 return;
1498 nsViewManager* vm = aView->GetViewManager();
1500 // Make sure visibility is correct. This only affects nsSubDocumentFrame.
1501 if (!SupportsVisibilityHidden()) {
1502 // See if the view should be hidden or visible
1503 ComputedStyle* sc = Style();
1504 vm->SetViewVisibility(aView, sc->StyleVisibility()->IsVisible()
1505 ? nsViewVisibility_kShow
1506 : nsViewVisibility_kHide);
1509 const auto zIndex = ZIndex();
1510 const bool autoZIndex = !zIndex;
1511 vm->SetViewZIndex(aView, autoZIndex, zIndex.valueOr(0));
1514 void nsIFrame::CreateView() {
1515 MOZ_ASSERT(!HasView());
1517 nsView* parentView = GetParent()->GetClosestView();
1518 MOZ_ASSERT(parentView, "no parent with view");
1520 nsViewManager* viewManager = parentView->GetViewManager();
1521 MOZ_ASSERT(viewManager, "null view manager");
1523 nsView* view = viewManager->CreateView(GetRect(), parentView);
1524 SyncFrameViewProperties(view);
1526 nsView* insertBefore = nsLayoutUtils::FindSiblingViewFor(parentView, this);
1527 // we insert this view 'above' the insertBefore view, unless insertBefore is
1528 // null, in which case we want to call with aAbove == false to insert at the
1529 // beginning in document order
1530 viewManager->InsertChild(parentView, view, insertBefore,
1531 insertBefore != nullptr);
1533 // REVIEW: Don't create a widget for fixed-pos elements anymore.
1534 // ComputeRepaintRegionForCopy will calculate the right area to repaint
1535 // when we scroll.
1536 // Reparent views on any child frames (or their descendants) to this
1537 // view. We can just call ReparentFrameViewTo on this frame because
1538 // we know this frame has no view, so it will crawl the children. Also,
1539 // we know that any descendants with views must have 'parentView' as their
1540 // parent view.
1541 ReparentFrameViewTo(viewManager, view, parentView);
1543 // Remember our view
1544 SetView(view);
1546 NS_FRAME_LOG(NS_FRAME_TRACE_CALLS,
1547 ("nsIFrame::CreateView: frame=%p view=%p", this, view));
1550 // MSVC fails with link error "one or more multiply defined symbols found",
1551 // gcc fails with "hidden symbol `nsIFrame::kPrincipalList' isn't defined"
1552 // etc if they are not defined.
1553 #ifndef _MSC_VER
1554 // static nsIFrame constants; initialized in the header file.
1555 const nsIFrame::ChildListID nsIFrame::kPrincipalList;
1556 const nsIFrame::ChildListID nsIFrame::kAbsoluteList;
1557 const nsIFrame::ChildListID nsIFrame::kBulletList;
1558 const nsIFrame::ChildListID nsIFrame::kCaptionList;
1559 const nsIFrame::ChildListID nsIFrame::kColGroupList;
1560 const nsIFrame::ChildListID nsIFrame::kExcessOverflowContainersList;
1561 const nsIFrame::ChildListID nsIFrame::kFixedList;
1562 const nsIFrame::ChildListID nsIFrame::kFloatList;
1563 const nsIFrame::ChildListID nsIFrame::kOverflowContainersList;
1564 const nsIFrame::ChildListID nsIFrame::kOverflowList;
1565 const nsIFrame::ChildListID nsIFrame::kOverflowOutOfFlowList;
1566 const nsIFrame::ChildListID nsIFrame::kPopupList;
1567 const nsIFrame::ChildListID nsIFrame::kPushedFloatsList;
1568 const nsIFrame::ChildListID nsIFrame::kSelectPopupList;
1569 const nsIFrame::ChildListID nsIFrame::kNoReflowPrincipalList;
1570 #endif
1572 /* virtual */
1573 nsMargin nsIFrame::GetUsedMargin() const {
1574 nsMargin margin(0, 0, 0, 0);
1575 if (((mState & NS_FRAME_FIRST_REFLOW) && !(mState & NS_FRAME_IN_REFLOW)) ||
1576 SVGUtils::IsInSVGTextSubtree(this))
1577 return margin;
1579 nsMargin* m = GetProperty(UsedMarginProperty());
1580 if (m) {
1581 margin = *m;
1582 } else {
1583 if (!StyleMargin()->GetMargin(margin)) {
1584 // If we get here, our caller probably shouldn't be calling us...
1585 NS_ERROR(
1586 "Returning bogus 0-sized margin, because this margin "
1587 "depends on layout & isn't cached!");
1590 return margin;
1593 /* virtual */
1594 nsMargin nsIFrame::GetUsedBorder() const {
1595 nsMargin border(0, 0, 0, 0);
1596 if (((mState & NS_FRAME_FIRST_REFLOW) && !(mState & NS_FRAME_IN_REFLOW)) ||
1597 SVGUtils::IsInSVGTextSubtree(this))
1598 return border;
1600 // Theme methods don't use const-ness.
1601 nsIFrame* mutable_this = const_cast<nsIFrame*>(this);
1603 const nsStyleDisplay* disp = StyleDisplay();
1604 if (mutable_this->IsThemed(disp)) {
1605 nsPresContext* pc = PresContext();
1606 LayoutDeviceIntMargin widgetBorder = pc->Theme()->GetWidgetBorder(
1607 pc->DeviceContext(), mutable_this, disp->EffectiveAppearance());
1608 border =
1609 LayoutDevicePixel::ToAppUnits(widgetBorder, pc->AppUnitsPerDevPixel());
1610 return border;
1613 nsMargin* b = GetProperty(UsedBorderProperty());
1614 if (b) {
1615 border = *b;
1616 } else {
1617 border = StyleBorder()->GetComputedBorder();
1619 return border;
1622 /* virtual */
1623 nsMargin nsIFrame::GetUsedPadding() const {
1624 nsMargin padding(0, 0, 0, 0);
1625 if (((mState & NS_FRAME_FIRST_REFLOW) && !(mState & NS_FRAME_IN_REFLOW)) ||
1626 SVGUtils::IsInSVGTextSubtree(this))
1627 return padding;
1629 // Theme methods don't use const-ness.
1630 nsIFrame* mutable_this = const_cast<nsIFrame*>(this);
1632 const nsStyleDisplay* disp = StyleDisplay();
1633 if (mutable_this->IsThemed(disp)) {
1634 nsPresContext* pc = PresContext();
1635 LayoutDeviceIntMargin widgetPadding;
1636 if (pc->Theme()->GetWidgetPadding(pc->DeviceContext(), mutable_this,
1637 disp->EffectiveAppearance(),
1638 &widgetPadding)) {
1639 return LayoutDevicePixel::ToAppUnits(widgetPadding,
1640 pc->AppUnitsPerDevPixel());
1644 nsMargin* p = GetProperty(UsedPaddingProperty());
1645 if (p) {
1646 padding = *p;
1647 } else {
1648 if (!StylePadding()->GetPadding(padding)) {
1649 // If we get here, our caller probably shouldn't be calling us...
1650 NS_ERROR(
1651 "Returning bogus 0-sized padding, because this padding "
1652 "depends on layout & isn't cached!");
1655 return padding;
1658 nsIFrame::Sides nsIFrame::GetSkipSides() const {
1659 if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak ==
1660 StyleBoxDecorationBreak::Clone) &&
1661 !HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
1662 return Sides();
1665 // Convert the logical skip sides to physical sides using the frame's
1666 // writing mode
1667 WritingMode writingMode = GetWritingMode();
1668 LogicalSides logicalSkip = GetLogicalSkipSides();
1669 Sides skip;
1671 if (logicalSkip.BStart()) {
1672 if (writingMode.IsVertical()) {
1673 skip |= writingMode.IsVerticalLR() ? SideBits::eLeft : SideBits::eRight;
1674 } else {
1675 skip |= SideBits::eTop;
1679 if (logicalSkip.BEnd()) {
1680 if (writingMode.IsVertical()) {
1681 skip |= writingMode.IsVerticalLR() ? SideBits::eRight : SideBits::eLeft;
1682 } else {
1683 skip |= SideBits::eBottom;
1687 if (logicalSkip.IStart()) {
1688 if (writingMode.IsVertical()) {
1689 skip |= SideBits::eTop;
1690 } else {
1691 skip |= writingMode.IsBidiLTR() ? SideBits::eLeft : SideBits::eRight;
1695 if (logicalSkip.IEnd()) {
1696 if (writingMode.IsVertical()) {
1697 skip |= SideBits::eBottom;
1698 } else {
1699 skip |= writingMode.IsBidiLTR() ? SideBits::eRight : SideBits::eLeft;
1702 return skip;
1705 nsRect nsIFrame::GetPaddingRectRelativeToSelf() const {
1706 nsMargin border = GetUsedBorder().ApplySkipSides(GetSkipSides());
1707 nsRect r(0, 0, mRect.width, mRect.height);
1708 r.Deflate(border);
1709 return r;
1712 nsRect nsIFrame::GetPaddingRect() const {
1713 return GetPaddingRectRelativeToSelf() + GetPosition();
1716 WritingMode nsIFrame::WritingModeForLine(WritingMode aSelfWM,
1717 nsIFrame* aSubFrame) const {
1718 MOZ_ASSERT(aSelfWM == GetWritingMode());
1719 WritingMode writingMode = aSelfWM;
1721 if (StyleTextReset()->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_PLAINTEXT) {
1722 mozilla::intl::BidiEmbeddingLevel frameLevel =
1723 nsBidiPresUtils::GetFrameBaseLevel(aSubFrame);
1724 writingMode.SetDirectionFromBidiLevel(frameLevel);
1727 return writingMode;
1730 nsRect nsIFrame::GetMarginRect() const {
1731 return GetMarginRectRelativeToSelf() + GetPosition();
1734 nsRect nsIFrame::GetMarginRectRelativeToSelf() const {
1735 nsMargin m = GetUsedMargin().ApplySkipSides(GetSkipSides());
1736 nsRect r(0, 0, mRect.width, mRect.height);
1737 r.Inflate(m);
1738 return r;
1741 bool nsIFrame::IsTransformed() const {
1742 if (!HasAnyStateBits(NS_FRAME_MAY_BE_TRANSFORMED)) {
1743 MOZ_ASSERT(!IsCSSTransformed());
1744 MOZ_ASSERT(!IsSVGTransformed());
1745 return false;
1747 return IsCSSTransformed() || IsSVGTransformed();
1750 bool nsIFrame::IsCSSTransformed() const {
1751 return HasAnyStateBits(NS_FRAME_MAY_BE_TRANSFORMED) &&
1752 (StyleDisplay()->HasTransform(this) || HasAnimationOfTransform());
1755 bool nsIFrame::HasAnimationOfTransform() const {
1756 return IsPrimaryFrame() &&
1757 nsLayoutUtils::HasAnimationOfTransformAndMotionPath(this) &&
1758 IsFrameOfType(eSupportsCSSTransforms);
1761 bool nsIFrame::ChildrenHavePerspective(
1762 const nsStyleDisplay* aStyleDisplay) const {
1763 MOZ_ASSERT(aStyleDisplay == StyleDisplay());
1764 return aStyleDisplay->HasPerspective(this);
1767 bool nsIFrame::HasAnimationOfOpacity(EffectSet* aEffectSet) const {
1768 return ((nsLayoutUtils::IsPrimaryStyleFrame(this) ||
1769 nsLayoutUtils::FirstContinuationOrIBSplitSibling(this)
1770 ->IsPrimaryFrame()) &&
1771 nsLayoutUtils::HasAnimationOfPropertySet(
1772 this, nsCSSPropertyIDSet::OpacityProperties(), aEffectSet));
1775 bool nsIFrame::HasOpacityInternal(float aThreshold,
1776 const nsStyleDisplay* aStyleDisplay,
1777 const nsStyleEffects* aStyleEffects,
1778 EffectSet* aEffectSet) const {
1779 MOZ_ASSERT(0.0 <= aThreshold && aThreshold <= 1.0, "Invalid argument");
1780 if (aStyleEffects->mOpacity < aThreshold ||
1781 (aStyleDisplay->mWillChange.bits & StyleWillChangeBits::OPACITY)) {
1782 return true;
1785 if (!mMayHaveOpacityAnimation) {
1786 return false;
1789 return HasAnimationOfOpacity(aEffectSet);
1792 bool nsIFrame::IsSVGTransformed(gfx::Matrix* aOwnTransforms,
1793 gfx::Matrix* aFromParentTransforms) const {
1794 return false;
1797 bool nsIFrame::Extend3DContext(const nsStyleDisplay* aStyleDisplay,
1798 const nsStyleEffects* aStyleEffects,
1799 mozilla::EffectSet* aEffectSetForOpacity) const {
1800 if (!(mState & NS_FRAME_MAY_BE_TRANSFORMED)) {
1801 return false;
1803 const nsStyleDisplay* disp = StyleDisplayWithOptionalParam(aStyleDisplay);
1804 if (disp->mTransformStyle != StyleTransformStyle::Preserve3d ||
1805 !IsFrameOfType(nsIFrame::eSupportsCSSTransforms)) {
1806 return false;
1809 // If we're all scroll frame, then all descendants will be clipped, so we
1810 // can't preserve 3d.
1811 if (IsScrollFrame()) {
1812 return false;
1815 const nsStyleEffects* effects = StyleEffectsWithOptionalParam(aStyleEffects);
1816 if (HasOpacity(disp, effects, aEffectSetForOpacity)) {
1817 return false;
1820 return ShouldApplyOverflowClipping(disp) == PhysicalAxes::None &&
1821 !GetClipPropClipRect(disp, effects, GetSize()) &&
1822 !SVGIntegrationUtils::UsingEffectsForFrame(this) &&
1823 !effects->HasMixBlendMode() &&
1824 disp->mIsolation != StyleIsolation::Isolate;
1827 bool nsIFrame::Combines3DTransformWithAncestors() const {
1828 nsIFrame* parent = GetClosestFlattenedTreeAncestorPrimaryFrame();
1829 if (!parent || !parent->Extend3DContext()) {
1830 return false;
1832 return IsCSSTransformed() || BackfaceIsHidden();
1835 bool nsIFrame::In3DContextAndBackfaceIsHidden() const {
1836 // While both tests fail most of the time, test BackfaceIsHidden()
1837 // first since it's likely to fail faster.
1838 return BackfaceIsHidden() && Combines3DTransformWithAncestors();
1841 bool nsIFrame::HasPerspective() const {
1842 if (!IsCSSTransformed()) {
1843 return false;
1845 nsIFrame* parent = GetClosestFlattenedTreeAncestorPrimaryFrame();
1846 if (!parent) {
1847 return false;
1849 return parent->ChildrenHavePerspective();
1852 nsRect nsIFrame::GetContentRectRelativeToSelf() const {
1853 nsMargin bp = GetUsedBorderAndPadding().ApplySkipSides(GetSkipSides());
1854 nsRect r(0, 0, mRect.width, mRect.height);
1855 r.Deflate(bp);
1856 return r;
1859 nsRect nsIFrame::GetContentRect() const {
1860 return GetContentRectRelativeToSelf() + GetPosition();
1863 bool nsIFrame::ComputeBorderRadii(const BorderRadius& aBorderRadius,
1864 const nsSize& aFrameSize,
1865 const nsSize& aBorderArea, Sides aSkipSides,
1866 nscoord aRadii[8]) {
1867 // Percentages are relative to whichever side they're on.
1868 for (const auto i : mozilla::AllPhysicalHalfCorners()) {
1869 const LengthPercentage& c = aBorderRadius.Get(i);
1870 nscoord axis = HalfCornerIsX(i) ? aFrameSize.width : aFrameSize.height;
1871 aRadii[i] = std::max(0, c.Resolve(axis));
1874 if (aSkipSides.Top()) {
1875 aRadii[eCornerTopLeftX] = 0;
1876 aRadii[eCornerTopLeftY] = 0;
1877 aRadii[eCornerTopRightX] = 0;
1878 aRadii[eCornerTopRightY] = 0;
1881 if (aSkipSides.Right()) {
1882 aRadii[eCornerTopRightX] = 0;
1883 aRadii[eCornerTopRightY] = 0;
1884 aRadii[eCornerBottomRightX] = 0;
1885 aRadii[eCornerBottomRightY] = 0;
1888 if (aSkipSides.Bottom()) {
1889 aRadii[eCornerBottomRightX] = 0;
1890 aRadii[eCornerBottomRightY] = 0;
1891 aRadii[eCornerBottomLeftX] = 0;
1892 aRadii[eCornerBottomLeftY] = 0;
1895 if (aSkipSides.Left()) {
1896 aRadii[eCornerBottomLeftX] = 0;
1897 aRadii[eCornerBottomLeftY] = 0;
1898 aRadii[eCornerTopLeftX] = 0;
1899 aRadii[eCornerTopLeftY] = 0;
1902 // css3-background specifies this algorithm for reducing
1903 // corner radii when they are too big.
1904 bool haveRadius = false;
1905 double ratio = 1.0f;
1906 for (const auto side : mozilla::AllPhysicalSides()) {
1907 uint32_t hc1 = SideToHalfCorner(side, false, true);
1908 uint32_t hc2 = SideToHalfCorner(side, true, true);
1909 nscoord length =
1910 SideIsVertical(side) ? aBorderArea.height : aBorderArea.width;
1911 nscoord sum = aRadii[hc1] + aRadii[hc2];
1912 if (sum) {
1913 haveRadius = true;
1914 // avoid floating point division in the normal case
1915 if (length < sum) {
1916 ratio = std::min(ratio, double(length) / sum);
1920 if (ratio < 1.0) {
1921 for (const auto corner : mozilla::AllPhysicalHalfCorners()) {
1922 aRadii[corner] *= ratio;
1926 return haveRadius;
1929 /* static */
1930 void nsIFrame::InsetBorderRadii(nscoord aRadii[8], const nsMargin& aOffsets) {
1931 for (const auto side : mozilla::AllPhysicalSides()) {
1932 nscoord offset = aOffsets.Side(side);
1933 uint32_t hc1 = SideToHalfCorner(side, false, false);
1934 uint32_t hc2 = SideToHalfCorner(side, true, false);
1935 aRadii[hc1] = std::max(0, aRadii[hc1] - offset);
1936 aRadii[hc2] = std::max(0, aRadii[hc2] - offset);
1940 /* static */
1941 void nsIFrame::OutsetBorderRadii(nscoord aRadii[8], const nsMargin& aOffsets) {
1942 auto AdjustOffset = [](const uint32_t aRadius, const nscoord aOffset) {
1943 // Implement the cubic formula to adjust offset when aOffset > 0 and
1944 // aRadius / aOffset < 1.
1945 // https://drafts.csswg.org/css-shapes/#valdef-shape-box-margin-box
1946 if (aOffset > 0) {
1947 const double ratio = aRadius / double(aOffset);
1948 if (ratio < 1.0) {
1949 return nscoord(aOffset * (1.0 + std::pow(ratio - 1, 3)));
1952 return aOffset;
1955 for (const auto side : mozilla::AllPhysicalSides()) {
1956 const nscoord offset = aOffsets.Side(side);
1957 const uint32_t hc1 = SideToHalfCorner(side, false, false);
1958 const uint32_t hc2 = SideToHalfCorner(side, true, false);
1959 if (aRadii[hc1] > 0) {
1960 const nscoord offset1 = AdjustOffset(aRadii[hc1], offset);
1961 aRadii[hc1] = std::max(0, aRadii[hc1] + offset1);
1963 if (aRadii[hc2] > 0) {
1964 const nscoord offset2 = AdjustOffset(aRadii[hc2], offset);
1965 aRadii[hc2] = std::max(0, aRadii[hc2] + offset2);
1970 static inline bool RadiiAreDefinitelyZero(const BorderRadius& aBorderRadius) {
1971 for (const auto corner : mozilla::AllPhysicalHalfCorners()) {
1972 if (!aBorderRadius.Get(corner).IsDefinitelyZero()) {
1973 return false;
1976 return true;
1979 /* virtual */
1980 bool nsIFrame::GetBorderRadii(const nsSize& aFrameSize,
1981 const nsSize& aBorderArea, Sides aSkipSides,
1982 nscoord aRadii[8]) const {
1983 if (!mMayHaveRoundedCorners) {
1984 memset(aRadii, 0, sizeof(nscoord) * 8);
1985 return false;
1988 if (IsThemed()) {
1989 // When we're themed, the native theme code draws the border and
1990 // background, and therefore it doesn't make sense to tell other
1991 // code that's interested in border-radius that we have any radii.
1993 // In an ideal world, we might have a way for the them to tell us an
1994 // border radius, but since we don't, we're better off assuming
1995 // zero.
1996 for (const auto corner : mozilla::AllPhysicalHalfCorners()) {
1997 aRadii[corner] = 0;
1999 return false;
2002 const auto& radii = StyleBorder()->mBorderRadius;
2003 const bool hasRadii =
2004 ComputeBorderRadii(radii, aFrameSize, aBorderArea, aSkipSides, aRadii);
2005 if (!hasRadii) {
2006 // TODO(emilio): Maybe we can just remove this bit and do the
2007 // IsDefinitelyZero check unconditionally. That should still avoid most of
2008 // the work, though maybe not the cache miss of going through the style and
2009 // the border struct.
2010 const_cast<nsIFrame*>(this)->mMayHaveRoundedCorners =
2011 !RadiiAreDefinitelyZero(radii);
2013 return hasRadii;
2016 bool nsIFrame::GetBorderRadii(nscoord aRadii[8]) const {
2017 nsSize sz = GetSize();
2018 return GetBorderRadii(sz, sz, GetSkipSides(), aRadii);
2021 bool nsIFrame::GetMarginBoxBorderRadii(nscoord aRadii[8]) const {
2022 return GetBoxBorderRadii(aRadii, GetUsedMargin(), true);
2025 bool nsIFrame::GetPaddingBoxBorderRadii(nscoord aRadii[8]) const {
2026 return GetBoxBorderRadii(aRadii, GetUsedBorder(), false);
2029 bool nsIFrame::GetContentBoxBorderRadii(nscoord aRadii[8]) const {
2030 return GetBoxBorderRadii(aRadii, GetUsedBorderAndPadding(), false);
2033 bool nsIFrame::GetBoxBorderRadii(nscoord aRadii[8], nsMargin aOffset,
2034 bool aIsOutset) const {
2035 if (!GetBorderRadii(aRadii)) return false;
2036 if (aIsOutset) {
2037 OutsetBorderRadii(aRadii, aOffset);
2038 } else {
2039 InsetBorderRadii(aRadii, aOffset);
2041 for (const auto corner : mozilla::AllPhysicalHalfCorners()) {
2042 if (aRadii[corner]) return true;
2044 return false;
2047 bool nsIFrame::GetShapeBoxBorderRadii(nscoord aRadii[8]) const {
2048 using Tag = StyleShapeOutside::Tag;
2049 auto& shapeOutside = StyleDisplay()->mShapeOutside;
2050 auto box = StyleShapeBox::MarginBox;
2051 switch (shapeOutside.tag) {
2052 case Tag::Image:
2053 case Tag::None:
2054 return false;
2055 case Tag::Box:
2056 box = shapeOutside.AsBox();
2057 break;
2058 case Tag::Shape:
2059 box = shapeOutside.AsShape()._1;
2060 break;
2063 switch (box) {
2064 case StyleShapeBox::ContentBox:
2065 return GetContentBoxBorderRadii(aRadii);
2066 case StyleShapeBox::PaddingBox:
2067 return GetPaddingBoxBorderRadii(aRadii);
2068 case StyleShapeBox::BorderBox:
2069 return GetBorderRadii(aRadii);
2070 case StyleShapeBox::MarginBox:
2071 return GetMarginBoxBorderRadii(aRadii);
2072 default:
2073 MOZ_ASSERT_UNREACHABLE("Unexpected box value");
2074 return false;
2078 ComputedStyle* nsIFrame::GetAdditionalComputedStyle(int32_t aIndex) const {
2079 MOZ_ASSERT(aIndex >= 0, "invalid index number");
2080 return nullptr;
2083 void nsIFrame::SetAdditionalComputedStyle(int32_t aIndex,
2084 ComputedStyle* aComputedStyle) {
2085 MOZ_ASSERT(aIndex >= 0, "invalid index number");
2088 nscoord nsIFrame::GetLogicalBaseline(WritingMode aWritingMode) const {
2089 NS_ASSERTION(!IsSubtreeDirty(), "frame must not be dirty");
2090 // Baseline for inverted line content is the top (block-start) margin edge,
2091 // as the frame is in effect "flipped" for alignment purposes.
2092 if (aWritingMode.IsLineInverted()) {
2093 return -GetLogicalUsedMargin(aWritingMode).BStart(aWritingMode);
2095 // Otherwise, the bottom margin edge, per CSS2.1's definition of the
2096 // 'baseline' value of 'vertical-align'.
2097 return BSize(aWritingMode) +
2098 GetLogicalUsedMargin(aWritingMode).BEnd(aWritingMode);
2101 const nsFrameList& nsIFrame::GetChildList(ChildListID aListID) const {
2102 if (IsAbsoluteContainer() && aListID == GetAbsoluteListID()) {
2103 return GetAbsoluteContainingBlock()->GetChildList();
2104 } else {
2105 return nsFrameList::EmptyList();
2109 void nsIFrame::GetChildLists(nsTArray<ChildList>* aLists) const {
2110 if (IsAbsoluteContainer()) {
2111 nsFrameList absoluteList = GetAbsoluteContainingBlock()->GetChildList();
2112 absoluteList.AppendIfNonempty(aLists, GetAbsoluteListID());
2116 AutoTArray<nsIFrame::ChildList, 4> nsIFrame::CrossDocChildLists() {
2117 AutoTArray<ChildList, 4> childLists;
2118 nsSubDocumentFrame* subdocumentFrame = do_QueryFrame(this);
2119 if (subdocumentFrame) {
2120 // Descend into the subdocument
2121 nsIFrame* root = subdocumentFrame->GetSubdocumentRootFrame();
2122 if (root) {
2123 childLists.EmplaceBack(
2124 nsFrameList(root, nsLayoutUtils::GetLastSibling(root)),
2125 nsIFrame::kPrincipalList);
2129 GetChildLists(&childLists);
2130 return childLists;
2133 Visibility nsIFrame::GetVisibility() const {
2134 if (!HasAnyStateBits(NS_FRAME_VISIBILITY_IS_TRACKED)) {
2135 return Visibility::Untracked;
2138 bool isSet = false;
2139 uint32_t visibleCount = GetProperty(VisibilityStateProperty(), &isSet);
2141 MOZ_ASSERT(isSet,
2142 "Should have a VisibilityStateProperty value "
2143 "if NS_FRAME_VISIBILITY_IS_TRACKED is set");
2145 return visibleCount > 0 ? Visibility::ApproximatelyVisible
2146 : Visibility::ApproximatelyNonVisible;
2149 void nsIFrame::UpdateVisibilitySynchronously() {
2150 mozilla::PresShell* presShell = PresShell();
2151 if (!presShell) {
2152 return;
2155 if (presShell->AssumeAllFramesVisible()) {
2156 presShell->EnsureFrameInApproximatelyVisibleList(this);
2157 return;
2160 bool visible = StyleVisibility()->IsVisible();
2161 nsIFrame* f = GetParent();
2162 nsRect rect = GetRectRelativeToSelf();
2163 nsIFrame* rectFrame = this;
2164 while (f && visible) {
2165 nsIScrollableFrame* sf = do_QueryFrame(f);
2166 if (sf) {
2167 nsRect transformedRect =
2168 nsLayoutUtils::TransformFrameRectToAncestor(rectFrame, rect, f);
2169 if (!sf->IsRectNearlyVisible(transformedRect)) {
2170 visible = false;
2171 break;
2174 // In this code we're trying to synchronously update *approximate*
2175 // visibility. (In the future we may update precise visibility here as
2176 // well, which is why the method name does not contain 'approximate'.) The
2177 // IsRectNearlyVisible() check above tells us that the rect we're checking
2178 // is approximately visible within the scrollframe, but we still need to
2179 // ensure that, even if it was scrolled into view, it'd be visible when we
2180 // consider the rest of the document. To do that, we move transformedRect
2181 // to be contained in the scrollport as best we can (it might not fit) to
2182 // pretend that it was scrolled into view.
2183 rect = transformedRect.MoveInsideAndClamp(sf->GetScrollPortRect());
2184 rectFrame = f;
2186 nsIFrame* parent = f->GetParent();
2187 if (!parent) {
2188 parent = nsLayoutUtils::GetCrossDocParentFrameInProcess(f);
2189 if (parent && parent->PresContext()->IsChrome()) {
2190 break;
2193 f = parent;
2196 if (visible) {
2197 presShell->EnsureFrameInApproximatelyVisibleList(this);
2198 } else {
2199 presShell->RemoveFrameFromApproximatelyVisibleList(this);
2203 void nsIFrame::EnableVisibilityTracking() {
2204 if (HasAnyStateBits(NS_FRAME_VISIBILITY_IS_TRACKED)) {
2205 return; // Nothing to do.
2208 MOZ_ASSERT(!HasProperty(VisibilityStateProperty()),
2209 "Shouldn't have a VisibilityStateProperty value "
2210 "if NS_FRAME_VISIBILITY_IS_TRACKED is not set");
2212 // Add the state bit so we know to track visibility for this frame, and
2213 // initialize the frame property.
2214 AddStateBits(NS_FRAME_VISIBILITY_IS_TRACKED);
2215 SetProperty(VisibilityStateProperty(), 0);
2217 mozilla::PresShell* presShell = PresShell();
2218 if (!presShell) {
2219 return;
2222 // Schedule a visibility update. This method will virtually always be called
2223 // when layout has changed anyway, so it's very unlikely that any additional
2224 // visibility updates will be triggered by this, but this way we guarantee
2225 // that if this frame is currently visible we'll eventually find out.
2226 presShell->ScheduleApproximateFrameVisibilityUpdateSoon();
2229 void nsIFrame::DisableVisibilityTracking() {
2230 if (!HasAnyStateBits(NS_FRAME_VISIBILITY_IS_TRACKED)) {
2231 return; // Nothing to do.
2234 bool isSet = false;
2235 uint32_t visibleCount = TakeProperty(VisibilityStateProperty(), &isSet);
2237 MOZ_ASSERT(isSet,
2238 "Should have a VisibilityStateProperty value "
2239 "if NS_FRAME_VISIBILITY_IS_TRACKED is set");
2241 RemoveStateBits(NS_FRAME_VISIBILITY_IS_TRACKED);
2243 if (visibleCount == 0) {
2244 return; // We were nonvisible.
2247 // We were visible, so send an OnVisibilityChange() notification.
2248 OnVisibilityChange(Visibility::ApproximatelyNonVisible);
2251 void nsIFrame::DecApproximateVisibleCount(
2252 const Maybe<OnNonvisible>& aNonvisibleAction
2253 /* = Nothing() */) {
2254 MOZ_ASSERT(HasAnyStateBits(NS_FRAME_VISIBILITY_IS_TRACKED));
2256 bool isSet = false;
2257 uint32_t visibleCount = GetProperty(VisibilityStateProperty(), &isSet);
2259 MOZ_ASSERT(isSet,
2260 "Should have a VisibilityStateProperty value "
2261 "if NS_FRAME_VISIBILITY_IS_TRACKED is set");
2262 MOZ_ASSERT(visibleCount > 0,
2263 "Frame is already nonvisible and we're "
2264 "decrementing its visible count?");
2266 visibleCount--;
2267 SetProperty(VisibilityStateProperty(), visibleCount);
2268 if (visibleCount > 0) {
2269 return;
2272 // We just became nonvisible, so send an OnVisibilityChange() notification.
2273 OnVisibilityChange(Visibility::ApproximatelyNonVisible, aNonvisibleAction);
2276 void nsIFrame::IncApproximateVisibleCount() {
2277 MOZ_ASSERT(HasAnyStateBits(NS_FRAME_VISIBILITY_IS_TRACKED));
2279 bool isSet = false;
2280 uint32_t visibleCount = GetProperty(VisibilityStateProperty(), &isSet);
2282 MOZ_ASSERT(isSet,
2283 "Should have a VisibilityStateProperty value "
2284 "if NS_FRAME_VISIBILITY_IS_TRACKED is set");
2286 visibleCount++;
2287 SetProperty(VisibilityStateProperty(), visibleCount);
2288 if (visibleCount > 1) {
2289 return;
2292 // We just became visible, so send an OnVisibilityChange() notification.
2293 OnVisibilityChange(Visibility::ApproximatelyVisible);
2296 void nsIFrame::OnVisibilityChange(Visibility aNewVisibility,
2297 const Maybe<OnNonvisible>& aNonvisibleAction
2298 /* = Nothing() */) {
2299 // XXX(seth): In bug 1218990 we'll implement visibility tracking for CSS
2300 // images here.
2303 static nsIFrame* GetActiveSelectionFrame(nsPresContext* aPresContext,
2304 nsIFrame* aFrame) {
2305 nsIContent* capturingContent = PresShell::GetCapturingContent();
2306 if (capturingContent) {
2307 nsIFrame* activeFrame = aPresContext->GetPrimaryFrameFor(capturingContent);
2308 return activeFrame ? activeFrame : aFrame;
2311 return aFrame;
2314 int16_t nsIFrame::DetermineDisplaySelection() {
2315 int16_t selType = nsISelectionController::SELECTION_OFF;
2317 nsCOMPtr<nsISelectionController> selCon;
2318 nsresult result =
2319 GetSelectionController(PresContext(), getter_AddRefs(selCon));
2320 if (NS_SUCCEEDED(result) && selCon) {
2321 result = selCon->GetDisplaySelection(&selType);
2322 if (NS_SUCCEEDED(result) &&
2323 (selType != nsISelectionController::SELECTION_OFF)) {
2324 // Check whether style allows selection.
2325 if (!IsSelectable(nullptr)) {
2326 selType = nsISelectionController::SELECTION_OFF;
2330 return selType;
2333 static Element* FindElementAncestorForMozSelection(nsIContent* aContent) {
2334 NS_ENSURE_TRUE(aContent, nullptr);
2335 while (aContent && aContent->IsInNativeAnonymousSubtree()) {
2336 aContent = aContent->GetClosestNativeAnonymousSubtreeRootParent();
2338 NS_ASSERTION(aContent, "aContent isn't in non-anonymous tree?");
2339 return aContent ? aContent->GetAsElementOrParentElement() : nullptr;
2342 already_AddRefed<ComputedStyle> nsIFrame::ComputeSelectionStyle(
2343 int16_t aSelectionStatus) const {
2344 // Just bail out if not a selection-status that ::selection applies to.
2345 if (aSelectionStatus != nsISelectionController::SELECTION_ON &&
2346 aSelectionStatus != nsISelectionController::SELECTION_DISABLED) {
2347 return nullptr;
2349 // When in high-contrast mode, the style system ends up ignoring the color
2350 // declarations, which means that the ::selection style becomes the inherited
2351 // color, and default background. That's no good.
2352 if (PresContext()->ForcingColors()) {
2353 return nullptr;
2355 Element* element = FindElementAncestorForMozSelection(GetContent());
2356 if (!element) {
2357 return nullptr;
2359 return PresContext()->StyleSet()->ProbePseudoElementStyle(
2360 *element, PseudoStyleType::selection, Style());
2363 template <typename SizeOrMaxSize>
2364 static inline bool IsIntrinsicKeyword(const SizeOrMaxSize& aSize) {
2365 // All keywords other than auto/none/-moz-available depend on intrinsic sizes.
2366 return aSize.IsMaxContent() || aSize.IsMinContent() || aSize.IsFitContent() ||
2367 aSize.IsFitContentFunction();
2370 bool nsIFrame::CanBeDynamicReflowRoot() const {
2371 if (!StaticPrefs::layout_dynamic_reflow_roots_enabled()) {
2372 return false;
2375 auto& display = *StyleDisplay();
2376 if (IsFrameOfType(nsIFrame::eLineParticipant) ||
2377 nsStyleDisplay::IsRubyDisplayType(display.mDisplay) ||
2378 display.DisplayOutside() == StyleDisplayOutside::InternalTable ||
2379 display.DisplayInside() == StyleDisplayInside::Table ||
2380 (GetParent() && GetParent()->IsXULBoxFrame())) {
2381 // We have a display type where 'width' and 'height' don't actually set the
2382 // width or height (i.e., the size depends on content).
2383 MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_DYNAMIC_REFLOW_ROOT),
2384 "should not have dynamic reflow root bit");
2385 return false;
2388 // We can't serve as a dynamic reflow root if our used 'width' and 'height'
2389 // might be influenced by content.
2391 // FIXME: For display:block, we should probably optimize inline-size: auto.
2392 // FIXME: Other flex and grid cases?
2393 auto& pos = *StylePosition();
2394 const auto& width = pos.mWidth;
2395 const auto& height = pos.mHeight;
2396 if (!width.IsLengthPercentage() || width.HasPercent() ||
2397 !height.IsLengthPercentage() || height.HasPercent() ||
2398 IsIntrinsicKeyword(pos.mMinWidth) || IsIntrinsicKeyword(pos.mMaxWidth) ||
2399 IsIntrinsicKeyword(pos.mMinHeight) ||
2400 IsIntrinsicKeyword(pos.mMaxHeight) ||
2401 ((pos.mMinWidth.IsAuto() || pos.mMinHeight.IsAuto()) &&
2402 IsFlexOrGridItem())) {
2403 return false;
2406 // If our flex-basis is 'auto', it'll defer to 'width' (or 'height') which
2407 // we've already checked. Otherwise, it preempts them, so we need to
2408 // perform the same "could-this-value-be-influenced-by-content" checks that
2409 // we performed for 'width' and 'height' above.
2410 if (IsFlexItem()) {
2411 const auto& flexBasis = pos.mFlexBasis;
2412 if (!flexBasis.IsAuto()) {
2413 if (!flexBasis.IsSize() || !flexBasis.AsSize().IsLengthPercentage() ||
2414 flexBasis.AsSize().HasPercent()) {
2415 return false;
2420 if (!IsFixedPosContainingBlock()) {
2421 // We can't treat this frame as a reflow root, since dynamic changes
2422 // to absolutely-positioned frames inside of it require that we
2423 // reflow the placeholder before we reflow the absolutely positioned
2424 // frame.
2425 // FIXME: Alternatively, we could sort the reflow roots in
2426 // PresShell::ProcessReflowCommands by depth in the tree, from
2427 // deepest to least deep. However, for performance (FIXME) we
2428 // should really be sorting them in the opposite order!
2429 return false;
2432 // If we participate in a container's block reflow context, or margins
2433 // can collapse through us, we can't be a dynamic reflow root.
2434 if (IsBlockFrameOrSubclass() &&
2435 !HasAllStateBits(NS_BLOCK_FLOAT_MGR | NS_BLOCK_MARGIN_ROOT)) {
2436 return false;
2439 // Subgrids are never reflow roots, but 'contain:layout/paint' prevents
2440 // creating a subgrid in the first place.
2441 if (pos.mGridTemplateColumns.IsSubgrid() ||
2442 pos.mGridTemplateRows.IsSubgrid()) {
2443 // NOTE: we could check that 'display' of our parent's primary frame is
2444 // '[inline-]grid' here but that's probably not worth it in practice.
2445 if (!display.IsContainLayout() && !display.IsContainPaint()) {
2446 return false;
2450 // If we are split, we can't be a dynamic reflow root. Our reflow status may
2451 // change after reflow, and our parent is responsible to create or delete our
2452 // next-in-flow.
2453 if (GetPrevContinuation() || GetNextContinuation()) {
2454 return false;
2457 return true;
2460 /********************************************************
2461 * Refreshes each content's frame
2462 *********************************************************/
2464 void nsIFrame::DisplayOutlineUnconditional(nsDisplayListBuilder* aBuilder,
2465 const nsDisplayListSet& aLists) {
2466 // Per https://drafts.csswg.org/css-tables-3/#global-style-overrides:
2467 // "All css properties of table-column and table-column-group boxes are
2468 // ignored, except when explicitly specified by this specification."
2469 // CSS outlines fall into this category, so we skip them on these boxes.
2470 MOZ_ASSERT(!IsTableColGroupFrame() && !IsTableColFrame());
2471 const auto& outline = *StyleOutline();
2473 if (!outline.ShouldPaintOutline()) {
2474 return;
2477 // Outlines are painted by the table wrapper frame.
2478 if (IsTableFrame()) {
2479 return;
2482 if (HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT) &&
2483 ScrollableOverflowRect().IsEmpty()) {
2484 // Skip parts of IB-splits with an empty overflow rect, see bug 434301.
2485 // We may still want to fix some of the overflow area calculations over in
2486 // that bug.
2487 return;
2490 // We don't display outline-style: auto on themed frames that have their own
2491 // focus indicators.
2492 if (outline.mOutlineStyle.IsAuto()) {
2493 auto* disp = StyleDisplay();
2494 if (IsThemed(disp) && PresContext()->Theme()->ThemeDrawsFocusForWidget(
2495 this, disp->EffectiveAppearance())) {
2496 return;
2500 aLists.Outlines()->AppendNewToTop<nsDisplayOutline>(aBuilder, this);
2503 void nsIFrame::DisplayOutline(nsDisplayListBuilder* aBuilder,
2504 const nsDisplayListSet& aLists) {
2505 if (!IsVisibleForPainting()) return;
2507 DisplayOutlineUnconditional(aBuilder, aLists);
2510 void nsIFrame::DisplayInsetBoxShadowUnconditional(
2511 nsDisplayListBuilder* aBuilder, nsDisplayList* aList) {
2512 // XXXbz should box-shadow for rows/rowgroups/columns/colgroups get painted
2513 // just because we're visible? Or should it depend on the cell visibility
2514 // when we're not the whole table?
2515 const auto* effects = StyleEffects();
2516 if (effects->HasBoxShadowWithInset(true)) {
2517 aList->AppendNewToTop<nsDisplayBoxShadowInner>(aBuilder, this);
2521 void nsIFrame::DisplayInsetBoxShadow(nsDisplayListBuilder* aBuilder,
2522 nsDisplayList* aList) {
2523 if (!IsVisibleForPainting()) return;
2525 DisplayInsetBoxShadowUnconditional(aBuilder, aList);
2528 void nsIFrame::DisplayOutsetBoxShadowUnconditional(
2529 nsDisplayListBuilder* aBuilder, nsDisplayList* aList) {
2530 // XXXbz should box-shadow for rows/rowgroups/columns/colgroups get painted
2531 // just because we're visible? Or should it depend on the cell visibility
2532 // when we're not the whole table?
2533 const auto* effects = StyleEffects();
2534 if (effects->HasBoxShadowWithInset(false)) {
2535 aList->AppendNewToTop<nsDisplayBoxShadowOuter>(aBuilder, this);
2539 void nsIFrame::DisplayOutsetBoxShadow(nsDisplayListBuilder* aBuilder,
2540 nsDisplayList* aList) {
2541 if (!IsVisibleForPainting()) return;
2543 DisplayOutsetBoxShadowUnconditional(aBuilder, aList);
2546 void nsIFrame::DisplayCaret(nsDisplayListBuilder* aBuilder,
2547 nsDisplayList* aList) {
2548 if (!IsVisibleForPainting()) return;
2550 aList->AppendNewToTop<nsDisplayCaret>(aBuilder, this);
2553 nscolor nsIFrame::GetCaretColorAt(int32_t aOffset) {
2554 return nsLayoutUtils::GetColor(this, &nsStyleUI::mCaretColor);
2557 auto nsIFrame::ComputeShouldPaintBackground() const -> ShouldPaintBackground {
2558 nsPresContext* pc = PresContext();
2559 ShouldPaintBackground settings{pc->GetBackgroundColorDraw(),
2560 pc->GetBackgroundImageDraw()};
2561 if (settings.mColor && settings.mImage) {
2562 return settings;
2565 if (!HonorPrintBackgroundSettings() ||
2566 StyleVisibility()->mPrintColorAdjust == StylePrintColorAdjust::Exact) {
2567 return {true, true};
2570 return settings;
2573 bool nsIFrame::DisplayBackgroundUnconditional(nsDisplayListBuilder* aBuilder,
2574 const nsDisplayListSet& aLists,
2575 bool aForceBackground) {
2576 const bool hitTesting = aBuilder->IsForEventDelivery();
2577 if (hitTesting && !aBuilder->HitTestIsForVisibility()) {
2578 // For hit-testing, we generally just need a light-weight data structure
2579 // like nsDisplayEventReceiver. But if the hit-testing is for visibility,
2580 // then we need to know the opaque region in order to determine whether to
2581 // stop or not.
2582 aLists.BorderBackground()->AppendNewToTop<nsDisplayEventReceiver>(aBuilder,
2583 this);
2584 return false;
2587 AppendedBackgroundType result = AppendedBackgroundType::None;
2589 // Here we don't try to detect background propagation. Frames that might
2590 // receive a propagated background should just set aForceBackground to
2591 // true.
2592 if (hitTesting || aForceBackground ||
2593 !StyleBackground()->IsTransparent(this) ||
2594 StyleDisplay()->HasAppearance() ||
2595 // We do forcibly create a display item for background color animations
2596 // even if the current background-color is transparent so that we can
2597 // run the animations on the compositor.
2598 EffectCompositor::HasAnimationsForCompositor(
2599 this, DisplayItemType::TYPE_BACKGROUND_COLOR)) {
2600 result = nsDisplayBackgroundImage::AppendBackgroundItemsToTop(
2601 aBuilder, this,
2602 GetRectRelativeToSelf() + aBuilder->ToReferenceFrame(this),
2603 aLists.BorderBackground());
2606 if (result == AppendedBackgroundType::None) {
2607 aBuilder->BuildCompositorHitTestInfoIfNeeded(this,
2608 aLists.BorderBackground());
2611 return result == AppendedBackgroundType::ThemedBackground;
2614 void nsIFrame::DisplayBorderBackgroundOutline(nsDisplayListBuilder* aBuilder,
2615 const nsDisplayListSet& aLists,
2616 bool aForceBackground) {
2617 // The visibility check belongs here since child elements have the
2618 // opportunity to override the visibility property and display even if
2619 // their parent is hidden.
2620 if (!IsVisibleForPainting()) {
2621 return;
2624 DisplayOutsetBoxShadowUnconditional(aBuilder, aLists.BorderBackground());
2626 bool bgIsThemed =
2627 DisplayBackgroundUnconditional(aBuilder, aLists, aForceBackground);
2629 DisplayInsetBoxShadowUnconditional(aBuilder, aLists.BorderBackground());
2631 // If there's a themed background, we should not create a border item.
2632 // It won't be rendered.
2633 // Don't paint borders for tables here, since they paint them in a different
2634 // order.
2635 if (!bgIsThemed && StyleBorder()->HasBorder() && !IsTableFrame()) {
2636 aLists.BorderBackground()->AppendNewToTop<nsDisplayBorder>(aBuilder, this);
2639 DisplayOutlineUnconditional(aBuilder, aLists);
2642 inline static bool IsSVGContentWithCSSClip(const nsIFrame* aFrame) {
2643 // The CSS spec says that the 'clip' property only applies to absolutely
2644 // positioned elements, whereas the SVG spec says that it applies to SVG
2645 // elements regardless of the value of the 'position' property. Here we obey
2646 // the CSS spec for outer-<svg> (since that's what we generally do), but
2647 // obey the SVG spec for other SVG elements to which 'clip' applies.
2648 return aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT) &&
2649 aFrame->GetContent()->IsAnyOfSVGElements(nsGkAtoms::svg,
2650 nsGkAtoms::foreignObject);
2653 bool nsIFrame::FormsBackdropRoot(const nsStyleDisplay* aStyleDisplay,
2654 const nsStyleEffects* aStyleEffects,
2655 const nsStyleSVGReset* aStyleSVGReset) {
2656 // Check if this is a root frame.
2657 if (!GetParent()) {
2658 return true;
2661 // Check for filter effects.
2662 if (aStyleEffects->HasFilters() || aStyleEffects->HasBackdropFilters() ||
2663 aStyleEffects->HasMixBlendMode()) {
2664 return true;
2667 // Check for opacity.
2668 if (HasOpacity(aStyleDisplay, aStyleEffects)) {
2669 return true;
2672 // Check for mask or clip path.
2673 if (aStyleSVGReset->HasMask() || aStyleSVGReset->HasClipPath()) {
2674 return true;
2677 // TODO(cbrewster): Check will-change attributes
2679 return false;
2682 Maybe<nsRect> nsIFrame::GetClipPropClipRect(const nsStyleDisplay* aDisp,
2683 const nsStyleEffects* aEffects,
2684 const nsSize& aSize) const {
2685 if (aEffects->mClip.IsAuto() ||
2686 !(aDisp->IsAbsolutelyPositioned(this) || IsSVGContentWithCSSClip(this))) {
2687 return Nothing();
2690 auto& clipRect = aEffects->mClip.AsRect();
2691 nsRect rect = clipRect.ToLayoutRect();
2692 if (MOZ_LIKELY(StyleBorder()->mBoxDecorationBreak ==
2693 StyleBoxDecorationBreak::Slice)) {
2694 // The clip applies to the joined boxes so it's relative the first
2695 // continuation.
2696 nscoord y = 0;
2697 for (nsIFrame* f = GetPrevContinuation(); f; f = f->GetPrevContinuation()) {
2698 y += f->GetRect().height;
2700 rect.MoveBy(nsPoint(0, -y));
2703 if (clipRect.right.IsAuto()) {
2704 rect.width = aSize.width - rect.x;
2706 if (clipRect.bottom.IsAuto()) {
2707 rect.height = aSize.height - rect.y;
2709 return Some(rect);
2713 * If the CSS 'overflow' property applies to this frame, and is not
2714 * handled by constructing a dedicated nsHTML/XULScrollFrame, set up clipping
2715 * for that overflow in aBuilder->ClipState() to clip all containing-block
2716 * descendants.
2718 static void ApplyOverflowClipping(
2719 nsDisplayListBuilder* aBuilder, const nsIFrame* aFrame,
2720 nsIFrame::PhysicalAxes aClipAxes,
2721 DisplayListClipState::AutoClipMultiple& aClipState) {
2722 // Only 'clip' is handled here (and 'hidden' for table frames, and any
2723 // non-'visible' value for blocks in a paginated context).
2724 // We allow 'clip' to apply to any kind of frame. This is required by
2725 // comboboxes which make their display text (an inline frame) have clipping.
2726 MOZ_ASSERT(aClipAxes != nsIFrame::PhysicalAxes::None);
2727 MOZ_ASSERT(aFrame->ShouldApplyOverflowClipping(aFrame->StyleDisplay()) ==
2728 aClipAxes);
2730 nsRect clipRect;
2731 bool haveRadii = false;
2732 nscoord radii[8];
2733 auto* disp = aFrame->StyleDisplay();
2734 // Only deflate the padding if we clip to the content-box in that axis.
2735 auto wm = aFrame->GetWritingMode();
2736 bool cbH = (wm.IsVertical() ? disp->mOverflowClipBoxBlock
2737 : disp->mOverflowClipBoxInline) ==
2738 StyleOverflowClipBox::ContentBox;
2739 bool cbV = (wm.IsVertical() ? disp->mOverflowClipBoxInline
2740 : disp->mOverflowClipBoxBlock) ==
2741 StyleOverflowClipBox::ContentBox;
2742 nsMargin bp = aFrame->GetUsedPadding();
2743 if (!cbH) {
2744 bp.left = bp.right = nscoord(0);
2746 if (!cbV) {
2747 bp.top = bp.bottom = nscoord(0);
2750 bp += aFrame->GetUsedBorder();
2751 bp.ApplySkipSides(aFrame->GetSkipSides());
2752 nsRect rect(nsPoint(0, 0), aFrame->GetSize());
2753 rect.Deflate(bp);
2754 if (MOZ_UNLIKELY(!(aClipAxes & nsIFrame::PhysicalAxes::Horizontal))) {
2755 // NOTE(mats) We shouldn't be clipping at all in this dimension really,
2756 // but clipping in just one axis isn't supported by our GFX APIs so we
2757 // clip to our visual overflow rect instead.
2758 nsRect o = aFrame->InkOverflowRect();
2759 rect.x = o.x;
2760 rect.width = o.width;
2762 if (MOZ_UNLIKELY(!(aClipAxes & nsIFrame::PhysicalAxes::Vertical))) {
2763 // See the note above.
2764 nsRect o = aFrame->InkOverflowRect();
2765 rect.y = o.y;
2766 rect.height = o.height;
2768 clipRect = rect + aBuilder->ToReferenceFrame(aFrame);
2769 haveRadii = aFrame->GetBoxBorderRadii(radii, bp, false);
2770 aClipState.ClipContainingBlockDescendantsExtra(clipRect,
2771 haveRadii ? radii : nullptr);
2774 #ifdef DEBUG
2775 static void PaintDebugBorder(nsIFrame* aFrame, DrawTarget* aDrawTarget,
2776 const nsRect& aDirtyRect, nsPoint aPt) {
2777 nsRect r(aPt, aFrame->GetSize());
2778 int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
2779 sRGBColor blueOrRed(aFrame->HasView() ? sRGBColor(0.f, 0.f, 1.f, 1.f)
2780 : sRGBColor(1.f, 0.f, 0.f, 1.f));
2781 aDrawTarget->StrokeRect(NSRectToRect(r, appUnitsPerDevPixel),
2782 ColorPattern(ToDeviceColor(blueOrRed)));
2785 static void PaintEventTargetBorder(nsIFrame* aFrame, DrawTarget* aDrawTarget,
2786 const nsRect& aDirtyRect, nsPoint aPt) {
2787 nsRect r(aPt, aFrame->GetSize());
2788 int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
2789 ColorPattern purple(ToDeviceColor(sRGBColor(.5f, 0.f, .5f, 1.f)));
2790 aDrawTarget->StrokeRect(NSRectToRect(r, appUnitsPerDevPixel), purple);
2793 static void DisplayDebugBorders(nsDisplayListBuilder* aBuilder,
2794 nsIFrame* aFrame,
2795 const nsDisplayListSet& aLists) {
2796 // Draw a border around the child
2797 // REVIEW: From nsContainerFrame::PaintChild
2798 if (nsIFrame::GetShowFrameBorders() && !aFrame->GetRect().IsEmpty()) {
2799 aLists.Outlines()->AppendNewToTop<nsDisplayGeneric>(
2800 aBuilder, aFrame, PaintDebugBorder, "DebugBorder",
2801 DisplayItemType::TYPE_DEBUG_BORDER);
2803 // Draw a border around the current event target
2804 if (nsIFrame::GetShowEventTargetFrameBorder() &&
2805 aFrame->PresShell()->GetDrawEventTargetFrame() == aFrame) {
2806 aLists.Outlines()->AppendNewToTop<nsDisplayGeneric>(
2807 aBuilder, aFrame, PaintEventTargetBorder, "EventTargetBorder",
2808 DisplayItemType::TYPE_EVENT_TARGET_BORDER);
2811 #endif
2814 * Returns whether a display item that gets created with the builder's current
2815 * state will have a scrolled clip, i.e. a clip that is scrolled by a scroll
2816 * frame which does not move the item itself.
2818 static bool BuilderHasScrolledClip(nsDisplayListBuilder* aBuilder) {
2819 const DisplayItemClipChain* currentClip =
2820 aBuilder->ClipState().GetCurrentCombinedClipChain(aBuilder);
2821 if (!currentClip) {
2822 return false;
2825 const ActiveScrolledRoot* currentClipASR = currentClip->mASR;
2826 const ActiveScrolledRoot* currentASR = aBuilder->CurrentActiveScrolledRoot();
2827 return ActiveScrolledRoot::PickDescendant(currentClipASR, currentASR) !=
2828 currentASR;
2831 class AutoSaveRestoreContainsBlendMode {
2832 nsDisplayListBuilder& mBuilder;
2833 bool mSavedContainsBlendMode;
2835 public:
2836 explicit AutoSaveRestoreContainsBlendMode(nsDisplayListBuilder& aBuilder)
2837 : mBuilder(aBuilder),
2838 mSavedContainsBlendMode(aBuilder.ContainsBlendMode()) {}
2840 ~AutoSaveRestoreContainsBlendMode() {
2841 mBuilder.SetContainsBlendMode(mSavedContainsBlendMode);
2845 class AutoSaveRestoreContainsBackdropFilter {
2846 nsDisplayListBuilder& mBuilder;
2847 bool mSavedContainsBackdropFilter;
2849 public:
2850 explicit AutoSaveRestoreContainsBackdropFilter(nsDisplayListBuilder& aBuilder)
2851 : mBuilder(aBuilder),
2852 mSavedContainsBackdropFilter(aBuilder.ContainsBackdropFilter()) {}
2855 * This is called if a stacking context which does not form a backdrop root
2856 * contains a descendent with a backdrop filter. In this case we need to
2857 * delegate backdrop root creation to the next parent in the tree until we hit
2858 * the nearest backdrop root ancestor.
2860 void DelegateUp(bool aContainsBackdropFilter) {
2861 mSavedContainsBackdropFilter = aContainsBackdropFilter;
2864 ~AutoSaveRestoreContainsBackdropFilter() {
2865 mBuilder.SetContainsBackdropFilter(mSavedContainsBackdropFilter);
2869 static void CheckForApzAwareEventHandlers(nsDisplayListBuilder* aBuilder,
2870 nsIFrame* aFrame) {
2871 if (aBuilder->GetAncestorHasApzAwareEventHandler()) {
2872 return;
2875 nsIContent* content = aFrame->GetContent();
2876 if (!content) {
2877 return;
2880 if (content->IsNodeApzAware()) {
2881 aBuilder->SetAncestorHasApzAwareEventHandler(true);
2885 static void UpdateCurrentHitTestInfo(nsDisplayListBuilder* aBuilder,
2886 nsIFrame* aFrame) {
2887 if (!aBuilder->BuildCompositorHitTestInfo()) {
2888 // Compositor hit test info is not used.
2889 return;
2892 CheckForApzAwareEventHandlers(aBuilder, aFrame);
2894 const CompositorHitTestInfo info = aFrame->GetCompositorHitTestInfo(aBuilder);
2895 aBuilder->SetCompositorHitTestInfo(info);
2899 * True if aDescendant participates the context aAncestor participating.
2901 static bool FrameParticipatesIn3DContext(nsIFrame* aAncestor,
2902 nsIFrame* aDescendant) {
2903 MOZ_ASSERT(aAncestor != aDescendant);
2904 MOZ_ASSERT(aAncestor->GetContent() != aDescendant->GetContent());
2905 MOZ_ASSERT(aAncestor->Extend3DContext());
2907 nsIFrame* ancestor = aAncestor->FirstContinuation();
2908 MOZ_ASSERT(ancestor->IsPrimaryFrame());
2910 nsIFrame* frame;
2911 for (frame = aDescendant->GetClosestFlattenedTreeAncestorPrimaryFrame();
2912 frame && ancestor != frame;
2913 frame = frame->GetClosestFlattenedTreeAncestorPrimaryFrame()) {
2914 if (!frame->Extend3DContext()) {
2915 return false;
2919 MOZ_ASSERT(frame == ancestor);
2920 return true;
2923 static bool ItemParticipatesIn3DContext(nsIFrame* aAncestor,
2924 nsDisplayItem* aItem) {
2925 auto type = aItem->GetType();
2926 const bool isContainer = type == DisplayItemType::TYPE_WRAP_LIST ||
2927 type == DisplayItemType::TYPE_CONTAINER;
2929 if (isContainer && aItem->GetChildren()->Length() == 1) {
2930 // If the wraplist has only one child item, use the type of that item.
2931 type = aItem->GetChildren()->GetBottom()->GetType();
2934 if (type != DisplayItemType::TYPE_TRANSFORM &&
2935 type != DisplayItemType::TYPE_PERSPECTIVE) {
2936 return false;
2938 nsIFrame* transformFrame = aItem->Frame();
2939 if (aAncestor->GetContent() == transformFrame->GetContent()) {
2940 return true;
2942 return FrameParticipatesIn3DContext(aAncestor, transformFrame);
2945 static void WrapSeparatorTransform(nsDisplayListBuilder* aBuilder,
2946 nsIFrame* aFrame,
2947 nsDisplayList* aNonParticipants,
2948 nsDisplayList* aParticipants, int aIndex,
2949 nsDisplayItem** aSeparator) {
2950 if (aNonParticipants->IsEmpty()) {
2951 return;
2954 nsDisplayTransform* item = MakeDisplayItemWithIndex<nsDisplayTransform>(
2955 aBuilder, aFrame, aIndex, aNonParticipants, aBuilder->GetVisibleRect());
2957 if (*aSeparator == nullptr && item) {
2958 *aSeparator = item;
2961 aParticipants->AppendToTop(item);
2964 // Try to compute a clip rect to bound the contents of the mask item
2965 // that will be built for |aMaskedFrame|. If we're not able to compute
2966 // one, return an empty Maybe.
2967 // The returned clip rect, if there is one, is relative to |aMaskedFrame|.
2968 static Maybe<nsRect> ComputeClipForMaskItem(nsDisplayListBuilder* aBuilder,
2969 nsIFrame* aMaskedFrame) {
2970 const nsStyleSVGReset* svgReset = aMaskedFrame->StyleSVGReset();
2972 SVGUtils::MaskUsage maskUsage;
2973 SVGUtils::DetermineMaskUsage(aMaskedFrame, false, maskUsage);
2975 nsPoint offsetToUserSpace =
2976 nsLayoutUtils::ComputeOffsetToUserSpace(aBuilder, aMaskedFrame);
2977 int32_t devPixelRatio = aMaskedFrame->PresContext()->AppUnitsPerDevPixel();
2978 gfxPoint devPixelOffsetToUserSpace =
2979 nsLayoutUtils::PointToGfxPoint(offsetToUserSpace, devPixelRatio);
2980 CSSToLayoutDeviceScale cssToDevScale =
2981 aMaskedFrame->PresContext()->CSSToDevPixelScale();
2983 nsPoint toReferenceFrame;
2984 aBuilder->FindReferenceFrameFor(aMaskedFrame, &toReferenceFrame);
2986 Maybe<gfxRect> combinedClip;
2987 if (maskUsage.shouldApplyBasicShapeOrPath) {
2988 Maybe<Rect> result =
2989 CSSClipPathInstance::GetBoundingRectForBasicShapeOrPathClip(
2990 aMaskedFrame, svgReset->mClipPath);
2991 if (result) {
2992 combinedClip = Some(ThebesRect(*result));
2994 } else if (maskUsage.shouldApplyClipPath) {
2995 gfxRect result = SVGUtils::GetBBox(
2996 aMaskedFrame,
2997 SVGUtils::eBBoxIncludeClipped | SVGUtils::eBBoxIncludeFill |
2998 SVGUtils::eBBoxIncludeMarkers | SVGUtils::eBBoxIncludeStroke |
2999 SVGUtils::eDoNotClipToBBoxOfContentInsideClipPath);
3000 combinedClip = Some(
3001 ThebesRect((CSSRect::FromUnknownRect(ToRect(result)) * cssToDevScale)
3002 .ToUnknownRect()));
3003 } else {
3004 // The code for this case is adapted from ComputeMaskGeometry().
3006 nsRect borderArea(toReferenceFrame, aMaskedFrame->GetSize());
3007 borderArea -= offsetToUserSpace;
3009 // Use an infinite dirty rect to pass into nsCSSRendering::
3010 // GetImageLayerClip() because we don't have an actual dirty rect to
3011 // pass in. This is fine because the only time GetImageLayerClip() will
3012 // not intersect the incoming dirty rect with something is in the "NoClip"
3013 // case, and we handle that specially.
3014 nsRect dirtyRect(nscoord_MIN / 2, nscoord_MIN / 2, nscoord_MAX,
3015 nscoord_MAX);
3017 nsIFrame* firstFrame =
3018 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aMaskedFrame);
3019 nsTArray<SVGMaskFrame*> maskFrames;
3020 // XXX check return value?
3021 SVGObserverUtils::GetAndObserveMasks(firstFrame, &maskFrames);
3023 for (uint32_t i = 0; i < maskFrames.Length(); ++i) {
3024 gfxRect clipArea;
3025 if (maskFrames[i]) {
3026 clipArea = maskFrames[i]->GetMaskArea(aMaskedFrame);
3027 clipArea = ThebesRect(
3028 (CSSRect::FromUnknownRect(ToRect(clipArea)) * cssToDevScale)
3029 .ToUnknownRect());
3030 } else {
3031 const auto& layer = svgReset->mMask.mLayers[i];
3032 if (layer.mClip == StyleGeometryBox::NoClip) {
3033 return Nothing();
3036 nsCSSRendering::ImageLayerClipState clipState;
3037 nsCSSRendering::GetImageLayerClip(
3038 layer, aMaskedFrame, *aMaskedFrame->StyleBorder(), borderArea,
3039 dirtyRect, false /* aWillPaintBorder */, devPixelRatio, &clipState);
3040 clipArea = clipState.mDirtyRectInDevPx;
3042 combinedClip = UnionMaybeRects(combinedClip, Some(clipArea));
3045 if (combinedClip) {
3046 if (combinedClip->IsEmpty()) {
3047 // *clipForMask might be empty if all mask references are not resolvable
3048 // or the size of them are empty. We still need to create a transparent
3049 // mask before bug 1276834 fixed, so don't clip ctx by an empty rectangle
3050 // for for now.
3051 return Nothing();
3054 // Convert to user space.
3055 *combinedClip += devPixelOffsetToUserSpace;
3057 // Round the clip out. In FrameLayerBuilder we round clips to nearest
3058 // pixels, and if we have a really thin clip here, that can cause the
3059 // clip to become empty if we didn't round out here.
3060 // The rounding happens in coordinates that are relative to the reference
3061 // frame, which matches what FrameLayerBuilder does.
3062 combinedClip->RoundOut();
3064 // Convert to app units.
3065 nsRect result =
3066 nsLayoutUtils::RoundGfxRectToAppRect(*combinedClip, devPixelRatio);
3068 // The resulting clip is relative to the reference frame, but the caller
3069 // expects it to be relative to the masked frame, so adjust it.
3070 result -= toReferenceFrame;
3071 return Some(result);
3073 return Nothing();
3076 struct AutoCheckBuilder {
3077 explicit AutoCheckBuilder(nsDisplayListBuilder* aBuilder)
3078 : mBuilder(aBuilder) {
3079 aBuilder->Check();
3082 ~AutoCheckBuilder() { mBuilder->Check(); }
3084 nsDisplayListBuilder* mBuilder;
3088 * Helper class to track container creation. Stores the first tracked container.
3089 * Used to find the innermost container for hit test information, and to notify
3090 * callers whether a container item was created or not.
3092 struct ContainerTracker {
3093 void TrackContainer(nsDisplayItem* aContainer) {
3094 if (!aContainer) {
3095 return;
3098 if (!mContainer) {
3099 mContainer = aContainer;
3102 mCreatedContainer = true;
3105 void ResetCreatedContainer() { mCreatedContainer = false; }
3107 nsDisplayItem* mContainer = nullptr;
3108 bool mCreatedContainer = false;
3112 * Tries to reuse a top-level stacking context item from the previous paint.
3113 * Returns true if an item was reused, otherwise false.
3115 bool TryToReuseStackingContextItem(nsDisplayListBuilder* aBuilder,
3116 nsDisplayList* aList, nsIFrame* aFrame) {
3117 if (!aBuilder->IsForPainting() || !aBuilder->IsPartialUpdate() ||
3118 aBuilder->InInvalidSubtree()) {
3119 return false;
3122 if (aFrame->IsFrameModified() || aFrame->HasModifiedDescendants()) {
3123 return false;
3126 auto& items = aFrame->DisplayItems();
3127 auto* res = std::find_if(
3128 items.begin(), items.end(),
3129 [](nsDisplayItem* aItem) { return aItem->IsPreProcessed(); });
3131 if (res == items.end()) {
3132 return false;
3135 nsDisplayItem* container = *res;
3136 MOZ_ASSERT(container->Frame() == aFrame);
3137 DL_LOGD("RDL - Found SC item %p (%s) (frame: %p)", container,
3138 container->Name(), container->Frame());
3140 aList->AppendToTop(container);
3141 aBuilder->ReuseDisplayItem(container);
3142 return true;
3145 void nsIFrame::BuildDisplayListForStackingContext(
3146 nsDisplayListBuilder* aBuilder, nsDisplayList* aList,
3147 bool* aCreatedContainerItem) {
3148 if (aBuilder->IsForContent()) {
3149 DL_LOGV("BuildDisplayListForStackingContext (%p) <", this);
3152 ScopeExit e([this, aBuilder]() {
3153 if (aBuilder->IsForContent()) {
3154 DL_LOGV("> BuildDisplayListForStackingContext (%p)", this);
3158 AutoCheckBuilder check(aBuilder);
3160 if (aBuilder->IsReusingStackingContextItems() &&
3161 TryToReuseStackingContextItem(aBuilder, aList, this)) {
3162 if (aCreatedContainerItem) {
3163 *aCreatedContainerItem = true;
3165 return;
3168 if (HasAnyStateBits(NS_FRAME_TOO_DEEP_IN_FRAME_TREE)) {
3169 return;
3172 const nsStyleDisplay* disp = StyleDisplay();
3173 const nsStyleEffects* effects = StyleEffects();
3174 EffectSet* effectSetForOpacity = EffectSet::GetEffectSetForFrame(
3175 this, nsCSSPropertyIDSet::OpacityProperties());
3176 // We can stop right away if this is a zero-opacity stacking context and
3177 // we're painting, and we're not animating opacity.
3178 bool needHitTestInfo = aBuilder->BuildCompositorHitTestInfo() &&
3179 Style()->PointerEvents() != StylePointerEvents::None;
3180 bool opacityItemForEventsOnly = false;
3181 if (effects->mOpacity == 0.0 && aBuilder->IsForPainting() &&
3182 !(disp->mWillChange.bits & StyleWillChangeBits::OPACITY) &&
3183 !nsLayoutUtils::HasAnimationOfPropertySet(
3184 this, nsCSSPropertyIDSet::OpacityProperties(), effectSetForOpacity)) {
3185 if (needHitTestInfo) {
3186 opacityItemForEventsOnly = true;
3187 } else {
3188 return;
3192 if (aBuilder->IsForPainting() && disp->mWillChange.bits) {
3193 aBuilder->AddToWillChangeBudget(this, GetSize());
3196 // For preserves3d, use the dirty rect already installed on the
3197 // builder, since aDirtyRect maybe distorted for transforms along
3198 // the chain.
3199 nsRect visibleRect = aBuilder->GetVisibleRect();
3200 nsRect dirtyRect = aBuilder->GetDirtyRect();
3202 // We build an opacity item if it's not going to be drawn by SVG content.
3203 // We could in principle skip creating an nsDisplayOpacity item if
3204 // nsDisplayOpacity::NeedsActiveLayer returns false and usingSVGEffects is
3205 // true (the nsDisplayFilter/nsDisplayMasksAndClipPaths could handle the
3206 // opacity). Since SVG has perf issues where we sometimes spend a lot of
3207 // time creating display list items that might be helpful. We'd need to
3208 // restore our mechanism to do that (changed in bug 1482403), and we'd
3209 // need to invalidate the frame if the value that would be return from
3210 // NeedsActiveLayer was to change, which we don't currently do.
3211 const bool useOpacity =
3212 HasVisualOpacity(disp, effects, effectSetForOpacity) &&
3213 !SVGUtils::CanOptimizeOpacity(this);
3215 const bool isTransformed = IsTransformed();
3216 const bool hasPerspective = isTransformed && HasPerspective();
3217 const bool extend3DContext =
3218 Extend3DContext(disp, effects, effectSetForOpacity);
3219 const bool combines3DTransformWithAncestors =
3220 (extend3DContext || isTransformed) && Combines3DTransformWithAncestors();
3222 Maybe<nsDisplayListBuilder::AutoPreserves3DContext> autoPreserves3DContext;
3223 if (extend3DContext && !combines3DTransformWithAncestors) {
3224 // Start a new preserves3d context to keep informations on
3225 // nsDisplayListBuilder.
3226 autoPreserves3DContext.emplace(aBuilder);
3227 // Save dirty rect on the builder to avoid being distorted for
3228 // multiple transforms along the chain.
3229 aBuilder->SavePreserves3DRect();
3231 // We rebuild everything within preserve-3d and don't try
3232 // to retain, so override the dirty rect now.
3233 if (aBuilder->IsRetainingDisplayList()) {
3234 dirtyRect = visibleRect;
3235 aBuilder->SetDisablePartialUpdates(true);
3239 const bool useBlendMode = effects->mMixBlendMode != StyleBlend::Normal;
3240 if (useBlendMode) {
3241 aBuilder->SetContainsBlendMode(true);
3244 // reset blend mode so we can keep track if this stacking context needs have
3245 // a nsDisplayBlendContainer. Set the blend mode back when the routine exits
3246 // so we keep track if the parent stacking context needs a container too.
3247 AutoSaveRestoreContainsBlendMode autoRestoreBlendMode(*aBuilder);
3248 aBuilder->SetContainsBlendMode(false);
3250 bool usingBackdropFilter =
3251 effects->HasBackdropFilters() &&
3252 nsDisplayBackdropFilters::CanCreateWebRenderCommands(aBuilder, this);
3254 if (usingBackdropFilter) {
3255 aBuilder->SetContainsBackdropFilter(true);
3258 AutoSaveRestoreContainsBackdropFilter autoRestoreBackdropFilter(*aBuilder);
3259 aBuilder->SetContainsBackdropFilter(false);
3261 nsRect visibleRectOutsideTransform = visibleRect;
3262 nsDisplayTransform::PrerenderInfo prerenderInfo;
3263 bool inTransform = aBuilder->IsInTransform();
3264 if (isTransformed) {
3265 prerenderInfo = nsDisplayTransform::ShouldPrerenderTransformedContent(
3266 aBuilder, this, &visibleRect);
3268 switch (prerenderInfo.mDecision) {
3269 case nsDisplayTransform::PrerenderDecision::Full:
3270 case nsDisplayTransform::PrerenderDecision::Partial:
3271 dirtyRect = visibleRect;
3272 break;
3273 case nsDisplayTransform::PrerenderDecision::No: {
3274 // If we didn't prerender an animated frame in a preserve-3d context,
3275 // then we want disable async animations for the rest of the preserve-3d
3276 // (especially ancestors).
3277 if ((extend3DContext || combines3DTransformWithAncestors) &&
3278 prerenderInfo.mHasAnimations) {
3279 aBuilder->SavePreserves3DAllowAsyncAnimation(false);
3282 const nsRect overflow = InkOverflowRectRelativeToSelf();
3283 if (overflow.IsEmpty() && !extend3DContext) {
3284 return;
3287 // If we're in preserve-3d then grab the dirty rect that was given to
3288 // the root and transform using the combined transform.
3289 if (combines3DTransformWithAncestors) {
3290 visibleRect = dirtyRect = aBuilder->GetPreserves3DRect();
3293 nsRect untransformedDirtyRect;
3294 if (nsDisplayTransform::UntransformRect(dirtyRect, overflow, this,
3295 &untransformedDirtyRect)) {
3296 dirtyRect = untransformedDirtyRect;
3297 nsDisplayTransform::UntransformRect(visibleRect, overflow, this,
3298 &visibleRect);
3299 } else {
3300 // This should only happen if the transform is singular, in which case
3301 // nothing is visible anyway
3302 dirtyRect.SetEmpty();
3303 visibleRect.SetEmpty();
3307 inTransform = true;
3308 } else if (IsFixedPosContainingBlock()) {
3309 // Restict the building area to the overflow rect for these frames, since
3310 // RetainedDisplayListBuilder uses it to know if the size of the stacking
3311 // context changed.
3312 visibleRect.IntersectRect(visibleRect, InkOverflowRect());
3313 dirtyRect.IntersectRect(dirtyRect, InkOverflowRect());
3316 bool hasOverrideDirtyRect = false;
3317 // If we're doing a partial build, we're not invalid and we're capable
3318 // of having an override building rect (stacking context and fixed pos
3319 // containing block), then we should assume we have one.
3320 // Either we have an explicit one, or nothing in our subtree changed and
3321 // we have an implicit empty rect.
3323 // These conditions should match |CanStoreDisplayListBuildingRect()| in
3324 // RetainedDisplayListBuilder.cpp
3325 if (!aBuilder->IsReusingStackingContextItems() &&
3326 aBuilder->IsPartialUpdate() && !aBuilder->InInvalidSubtree() &&
3327 !IsFrameModified() && IsFixedPosContainingBlock() &&
3328 !GetPrevContinuation() && !GetNextContinuation()) {
3329 dirtyRect = nsRect();
3330 if (HasOverrideDirtyRegion()) {
3331 nsDisplayListBuilder::DisplayListBuildingData* data =
3332 GetProperty(nsDisplayListBuilder::DisplayListBuildingRect());
3333 if (data) {
3334 dirtyRect = data->mDirtyRect.Intersect(visibleRect);
3335 hasOverrideDirtyRect = true;
3340 bool usingFilter = effects->HasFilters();
3341 bool usingMask = SVGIntegrationUtils::UsingMaskOrClipPathForFrame(this);
3342 bool usingSVGEffects = usingFilter || usingMask;
3344 nsRect visibleRectOutsideSVGEffects = visibleRect;
3345 nsDisplayList hoistedScrollInfoItemsStorage(aBuilder);
3346 if (usingSVGEffects) {
3347 dirtyRect =
3348 SVGIntegrationUtils::GetRequiredSourceForInvalidArea(this, dirtyRect);
3349 visibleRect =
3350 SVGIntegrationUtils::GetRequiredSourceForInvalidArea(this, visibleRect);
3351 aBuilder->EnterSVGEffectsContents(this, &hoistedScrollInfoItemsStorage);
3354 bool useStickyPosition = disp->mPosition == StylePositionProperty::Sticky;
3356 bool useFixedPosition =
3357 disp->mPosition == StylePositionProperty::Fixed &&
3358 (DisplayPortUtils::IsFixedPosFrameInDisplayPort(this) ||
3359 BuilderHasScrolledClip(aBuilder));
3361 nsDisplayListBuilder::AutoBuildingDisplayList buildingDisplayList(
3362 aBuilder, this, visibleRect, dirtyRect, isTransformed);
3364 UpdateCurrentHitTestInfo(aBuilder, this);
3366 // Depending on the effects that are applied to this frame, we can create
3367 // multiple container display items and wrap them around our contents.
3368 // This enum lists all the potential container display items, in the order
3369 // outside to inside.
3370 enum class ContainerItemType : uint8_t {
3371 None = 0,
3372 OwnLayerIfNeeded,
3373 BlendMode,
3374 FixedPosition,
3375 OwnLayerForTransformWithRoundedClip,
3376 Perspective,
3377 Transform,
3378 SeparatorTransforms,
3379 Opacity,
3380 Filter,
3381 BlendContainer
3384 nsDisplayListBuilder::AutoContainerASRTracker contASRTracker(aBuilder);
3386 auto cssClip = GetClipPropClipRect(disp, effects, GetSize());
3387 auto ApplyClipProp = [&](DisplayListClipState::AutoSaveRestore& aClipState) {
3388 if (!cssClip) {
3389 return;
3391 nsPoint offset = aBuilder->GetCurrentFrameOffsetToReferenceFrame();
3392 aBuilder->IntersectDirtyRect(*cssClip);
3393 aBuilder->IntersectVisibleRect(*cssClip);
3394 aClipState.ClipContentDescendants(*cssClip + offset);
3397 // The CSS clip property is effectively inside the transform, but outside the
3398 // filters. So if we're not transformed we can apply it just here for
3399 // simplicity, instead of on each of the places that handle clipCapturedBy.
3400 DisplayListClipState::AutoSaveRestore untransformedCssClip(aBuilder);
3401 if (!isTransformed) {
3402 ApplyClipProp(untransformedCssClip);
3405 // If there is a current clip, then depending on the container items we
3406 // create, different things can happen to it. Some container items simply
3407 // propagate the clip to their children and aren't clipped themselves.
3408 // But other container items, especially those that establish a different
3409 // geometry for their contents (e.g. transforms), capture the clip on
3410 // themselves and unset the clip for their contents. If we create more than
3411 // one of those container items, the clip will be captured on the outermost
3412 // one and the inner container items will be unclipped.
3413 ContainerItemType clipCapturedBy = ContainerItemType::None;
3414 if (useFixedPosition) {
3415 clipCapturedBy = ContainerItemType::FixedPosition;
3416 } else if (isTransformed) {
3417 const DisplayItemClipChain* currentClip =
3418 aBuilder->ClipState().GetCurrentCombinedClipChain(aBuilder);
3419 if ((hasPerspective || extend3DContext) &&
3420 (currentClip && currentClip->HasRoundedCorners())) {
3421 // If we're creating an nsDisplayTransform item that is going to combine
3422 // its transform with its children (preserve-3d or perspective), then we
3423 // can't have an intermediate surface. Mask layers force an intermediate
3424 // surface, so if we're going to need both then create a separate
3425 // wrapping layer for the mask.
3426 clipCapturedBy = ContainerItemType::OwnLayerForTransformWithRoundedClip;
3427 } else if (hasPerspective) {
3428 clipCapturedBy = ContainerItemType::Perspective;
3429 } else {
3430 clipCapturedBy = ContainerItemType::Transform;
3432 } else if (usingFilter) {
3433 clipCapturedBy = ContainerItemType::Filter;
3436 DisplayListClipState::AutoSaveRestore clipState(aBuilder);
3437 if (clipCapturedBy != ContainerItemType::None) {
3438 clipState.Clear();
3441 DisplayListClipState::AutoSaveRestore transformedCssClip(aBuilder);
3442 if (isTransformed) {
3443 // FIXME(emilio, bug 1525159): In the case we have a both a transform _and_
3444 // filters, this clips the input to the filters as well, which is not
3445 // correct (clipping by the `clip` property is supposed to happen after
3446 // applying the filter effects, per [1].
3448 // This is not a regression though, since we used to do that anyway before
3449 // bug 1514384, and even without the transform we get it wrong.
3451 // [1]: https://drafts.fxtf.org/css-masking/#placement
3452 ApplyClipProp(transformedCssClip);
3455 nsDisplayListCollection set(aBuilder);
3456 Maybe<nsRect> clipForMask;
3457 bool insertBackdropRoot;
3459 DisplayListClipState::AutoSaveRestore nestedClipState(aBuilder);
3460 nsDisplayListBuilder::AutoInTransformSetter inTransformSetter(aBuilder,
3461 inTransform);
3462 nsDisplayListBuilder::AutoEnterFilter filterASRSetter(aBuilder,
3463 usingFilter);
3464 nsDisplayListBuilder::AutoInEventsOnly inEventsSetter(
3465 aBuilder, opacityItemForEventsOnly);
3467 // If we have a mask, compute a clip to bound the masked content.
3468 // This is necessary in case the content moves with an ancestor
3469 // ASR of the mask.
3470 // Don't do this if we also have a filter, because then the clip
3471 // would be applied before the filter, violating
3472 // https://www.w3.org/TR/filter-effects-1/#placement.
3473 // Filters are a containing block for fixed and absolute descendants,
3474 // so the masked content cannot move with an ancestor ASR.
3475 if (usingMask && !usingFilter) {
3476 clipForMask = ComputeClipForMaskItem(aBuilder, this);
3477 if (clipForMask) {
3478 aBuilder->IntersectDirtyRect(*clipForMask);
3479 aBuilder->IntersectVisibleRect(*clipForMask);
3480 nestedClipState.ClipContentDescendants(
3481 *clipForMask + aBuilder->GetCurrentFrameOffsetToReferenceFrame());
3485 // extend3DContext also guarantees that applyAbsPosClipping and
3486 // usingSVGEffects are false We only modify the preserve-3d rect if we are
3487 // the top of a preserve-3d heirarchy
3488 if (extend3DContext) {
3489 // Mark these first so MarkAbsoluteFramesForDisplayList knows if we are
3490 // going to be forced to descend into frames.
3491 aBuilder->MarkPreserve3DFramesForDisplayList(this);
3494 aBuilder->AdjustWindowDraggingRegion(this);
3496 MarkAbsoluteFramesForDisplayList(aBuilder);
3497 aBuilder->Check();
3498 BuildDisplayList(aBuilder, set);
3499 SetBuiltDisplayList(true);
3500 aBuilder->Check();
3501 aBuilder->DisplayCaret(this, set.Outlines());
3503 insertBackdropRoot = aBuilder->ContainsBackdropFilter() &&
3504 FormsBackdropRoot(disp, effects, StyleSVGReset());
3506 // Blend modes are a real pain for retained display lists. We build a blend
3507 // container item if the built list contains any blend mode items within
3508 // the current stacking context. This can change without an invalidation
3509 // to the stacking context frame, or the blend mode frame (e.g. by moving
3510 // an intermediate frame).
3511 // When we gain/remove a blend container item, we need to mark this frame
3512 // as invalid and have the full display list for merging to track
3513 // the change correctly.
3514 // It seems really hard to track this in advance, as the bookkeeping
3515 // required to note which stacking contexts have blend descendants
3516 // is complex and likely to be buggy.
3517 // Instead we're doing the sad thing, detecting it afterwards, and just
3518 // repeating display list building if it changed.
3519 // We have to repeat building for the entire display list (or at least
3520 // the outer stacking context), since we need to mark this frame as invalid
3521 // to remove any existing content that isn't wrapped in the blend container,
3522 // and then we need to build content infront/behind the blend container
3523 // to get correct positioning during merging.
3524 if ((insertBackdropRoot || aBuilder->ContainsBlendMode()) &&
3525 aBuilder->IsRetainingDisplayList()) {
3526 if (aBuilder->IsPartialUpdate()) {
3527 aBuilder->SetPartialBuildFailed(true);
3528 } else {
3529 aBuilder->SetDisablePartialUpdates(true);
3534 // If a child contains a backdrop filter, but this stacking context does not
3535 // form a backdrop root, we need to propogate up the tree until we find an
3536 // ancestor that does form a backdrop root.
3537 if (!insertBackdropRoot && aBuilder->ContainsBackdropFilter()) {
3538 autoRestoreBackdropFilter.DelegateUp(true);
3541 if (aBuilder->IsBackgroundOnly()) {
3542 set.BlockBorderBackgrounds()->DeleteAll(aBuilder);
3543 set.Floats()->DeleteAll(aBuilder);
3544 set.Content()->DeleteAll(aBuilder);
3545 set.PositionedDescendants()->DeleteAll(aBuilder);
3546 set.Outlines()->DeleteAll(aBuilder);
3549 if (hasOverrideDirtyRect &&
3550 StaticPrefs::layout_display_list_show_rebuild_area()) {
3551 nsDisplaySolidColor* color = MakeDisplayItem<nsDisplaySolidColor>(
3552 aBuilder, this,
3553 dirtyRect + aBuilder->GetCurrentFrameOffsetToReferenceFrame(),
3554 NS_RGBA(255, 0, 0, 64), false);
3555 if (color) {
3556 color->SetOverrideZIndex(INT32_MAX);
3557 set.PositionedDescendants()->AppendToTop(color);
3561 nsIContent* content = GetContent();
3562 if (!content) {
3563 content = PresContext()->Document()->GetRootElement();
3566 nsDisplayList resultList(aBuilder);
3567 set.SerializeWithCorrectZOrder(&resultList, content);
3569 #ifdef DEBUG
3570 DisplayDebugBorders(aBuilder, this, set);
3571 #endif
3573 // Get the ASR to use for the container items that we create here.
3574 const ActiveScrolledRoot* containerItemASR = contASRTracker.GetContainerASR();
3576 ContainerTracker ct;
3578 /* If adding both a nsDisplayBlendContainer and a nsDisplayBlendMode to the
3579 * same list, the nsDisplayBlendContainer should be added first. This only
3580 * happens when the element creating this stacking context has mix-blend-mode
3581 * and also contains a child which has mix-blend-mode.
3582 * The nsDisplayBlendContainer must be added to the list first, so it does not
3583 * isolate the containing element blending as well.
3585 if (aBuilder->ContainsBlendMode()) {
3586 DisplayListClipState::AutoSaveRestore blendContainerClipState(aBuilder);
3587 resultList.AppendToTop(nsDisplayBlendContainer::CreateForMixBlendMode(
3588 aBuilder, this, &resultList, containerItemASR));
3589 ct.TrackContainer(resultList.GetTop());
3592 if (insertBackdropRoot) {
3593 DisplayListClipState::AutoSaveRestore backdropRootContainerClipState(
3594 aBuilder);
3595 resultList.AppendNewToTop<nsDisplayBackdropRootContainer>(
3596 aBuilder, this, &resultList, containerItemASR);
3597 ct.TrackContainer(resultList.GetTop());
3600 if (usingBackdropFilter) {
3601 DisplayListClipState::AutoSaveRestore clipState(aBuilder);
3602 nsRect backdropRect =
3603 GetRectRelativeToSelf() + aBuilder->ToReferenceFrame(this);
3604 resultList.AppendNewToTop<nsDisplayBackdropFilters>(
3605 aBuilder, this, &resultList, backdropRect);
3606 ct.TrackContainer(resultList.GetTop());
3609 /* If there are any SVG effects, wrap the list up in an SVG effects item
3610 * (which also handles CSS group opacity). Note that we create an SVG effects
3611 * item even if resultList is empty, since a filter can produce graphical
3612 * output even if the element being filtered wouldn't otherwise do so.
3614 if (usingSVGEffects) {
3615 MOZ_ASSERT(usingFilter || usingMask,
3616 "Beside filter & mask/clip-path, what else effect do we have?");
3618 if (clipCapturedBy == ContainerItemType::Filter) {
3619 clipState.Restore();
3621 // Revert to the post-filter dirty rect.
3622 aBuilder->SetVisibleRect(visibleRectOutsideSVGEffects);
3624 // Skip all filter effects while generating glyph mask.
3625 if (usingFilter && !aBuilder->IsForGenerateGlyphMask()) {
3626 /* List now emptied, so add the new list to the top. */
3627 resultList.AppendNewToTop<nsDisplayFilters>(aBuilder, this, &resultList);
3628 ct.TrackContainer(resultList.GetTop());
3631 if (usingMask) {
3632 DisplayListClipState::AutoSaveRestore maskClipState(aBuilder);
3633 // The mask should move with aBuilder->CurrentActiveScrolledRoot(), so
3634 // that's the ASR we prefer to use for the mask item. However, we can
3635 // only do this if the mask if clipped with respect to that ASR, because
3636 // an item always needs to have finite bounds with respect to its ASR.
3637 // If we weren't able to compute a clip for the mask, we fall back to
3638 // using containerItemASR, which is the lowest common ancestor clip of
3639 // the mask's contents. That's not entirely correct, but it satisfies
3640 // the base requirement of the ASR system (that items have finite bounds
3641 // wrt. their ASR).
3642 const ActiveScrolledRoot* maskASR =
3643 clipForMask.isSome() ? aBuilder->CurrentActiveScrolledRoot()
3644 : containerItemASR;
3645 /* List now emptied, so add the new list to the top. */
3646 resultList.AppendNewToTop<nsDisplayMasksAndClipPaths>(
3647 aBuilder, this, &resultList, maskASR);
3648 ct.TrackContainer(resultList.GetTop());
3651 // TODO(miko): We could probably create a wraplist here and avoid creating
3652 // it later in |BuildDisplayListForChild()|.
3653 ct.ResetCreatedContainer();
3655 // Also add the hoisted scroll info items. We need those for APZ scrolling
3656 // because nsDisplayMasksAndClipPaths items can't build active layers.
3657 aBuilder->ExitSVGEffectsContents();
3658 resultList.AppendToTop(&hoistedScrollInfoItemsStorage);
3661 /* If the list is non-empty and there is CSS group opacity without SVG
3662 * effects, wrap it up in an opacity item.
3664 if (useOpacity) {
3665 // Don't clip nsDisplayOpacity items. We clip their descendants instead.
3666 // The clip we would set on an element with opacity would clip
3667 // all descendant content, but some should not be clipped.
3668 DisplayListClipState::AutoSaveRestore opacityClipState(aBuilder);
3669 const bool needsActiveOpacityLayer =
3670 nsDisplayOpacity::NeedsActiveLayer(aBuilder, this);
3672 resultList.AppendNewToTop<nsDisplayOpacity>(
3673 aBuilder, this, &resultList, containerItemASR, opacityItemForEventsOnly,
3674 needsActiveOpacityLayer);
3675 ct.TrackContainer(resultList.GetTop());
3678 /* If we're going to apply a transformation and don't have preserve-3d set,
3679 * wrap everything in an nsDisplayTransform. If there's nothing in the list,
3680 * don't add anything.
3682 * For the preserve-3d case we want to individually wrap every child in the
3683 * list with a separate nsDisplayTransform instead. When the child is already
3684 * an nsDisplayTransform, we can skip this step, as the computed transform
3685 * will already include our own.
3687 * We also traverse into sublists created by nsDisplayWrapList, so that we
3688 * find all the correct children.
3690 if (isTransformed && extend3DContext) {
3691 // Install dummy nsDisplayTransform as a leaf containing
3692 // descendants not participating this 3D rendering context.
3693 nsDisplayList nonparticipants(aBuilder);
3694 nsDisplayList participants(aBuilder);
3695 int index = 1;
3697 nsDisplayItem* separator = nullptr;
3699 // TODO: This can be simplified: |participants| is just |resultList|.
3700 for (nsDisplayItem* item : resultList.TakeItems()) {
3701 if (ItemParticipatesIn3DContext(this, item) &&
3702 !item->GetClip().HasClip()) {
3703 // The frame of this item participates the same 3D context.
3704 WrapSeparatorTransform(aBuilder, this, &nonparticipants, &participants,
3705 index++, &separator);
3707 participants.AppendToTop(item);
3708 } else {
3709 // The frame of the item doesn't participate the current
3710 // context, or has no transform.
3712 // For items participating but not transformed, they are add
3713 // to nonparticipants to get a separator layer for handling
3714 // clips, if there is, on an intermediate surface.
3715 // \see ContainerLayer::DefaultComputeEffectiveTransforms().
3716 nonparticipants.AppendToTop(item);
3719 WrapSeparatorTransform(aBuilder, this, &nonparticipants, &participants,
3720 index++, &separator);
3722 if (separator) {
3723 ct.TrackContainer(separator);
3726 resultList.AppendToTop(&participants);
3729 if (isTransformed) {
3730 transformedCssClip.Restore();
3731 if (clipCapturedBy == ContainerItemType::Transform) {
3732 // Restore clip state now so nsDisplayTransform is clipped properly.
3733 clipState.Restore();
3735 // Revert to the dirtyrect coming in from the parent, without our transform
3736 // taken into account.
3737 aBuilder->SetVisibleRect(visibleRectOutsideTransform);
3739 if (this != aBuilder->RootReferenceFrame()) {
3740 // Revert to the outer reference frame and offset because all display
3741 // items we create from now on are outside the transform.
3742 nsPoint toOuterReferenceFrame;
3743 const nsIFrame* outerReferenceFrame =
3744 aBuilder->FindReferenceFrameFor(GetParent(), &toOuterReferenceFrame);
3745 toOuterReferenceFrame += GetPosition();
3747 buildingDisplayList.SetReferenceFrameAndCurrentOffset(
3748 outerReferenceFrame, toOuterReferenceFrame);
3751 // We would like to block async animations for ancestors of ones not
3752 // prerendered in the preserve-3d tree. Now that we've finished processing
3753 // all descendants, update allowAsyncAnimation to take their prerender
3754 // state into account
3755 // FIXME: We don't block async animations for previous siblings because
3756 // their prerender decisions have been made. We may have to figure out a
3757 // better way to rollback their prerender decisions.
3758 // Alternatively we could not block animations for later siblings, and only
3759 // block them for ancestors of a blocked one.
3760 if ((extend3DContext || combines3DTransformWithAncestors) &&
3761 prerenderInfo.CanUseAsyncAnimations() &&
3762 !aBuilder->GetPreserves3DAllowAsyncAnimation()) {
3763 // aBuilder->GetPreserves3DAllowAsyncAnimation() means the inner or
3764 // previous silbing frames are allowed/disallowed for async animations.
3765 prerenderInfo.mDecision = nsDisplayTransform::PrerenderDecision::No;
3768 nsDisplayTransform* transformItem = MakeDisplayItem<nsDisplayTransform>(
3769 aBuilder, this, &resultList, visibleRect, prerenderInfo.mDecision);
3770 if (transformItem) {
3771 resultList.AppendToTop(transformItem);
3772 ct.TrackContainer(transformItem);
3775 if (hasPerspective) {
3776 transformItem->MarkWithAssociatedPerspective();
3778 if (clipCapturedBy == ContainerItemType::Perspective) {
3779 clipState.Restore();
3781 resultList.AppendNewToTop<nsDisplayPerspective>(aBuilder, this,
3782 &resultList);
3783 ct.TrackContainer(resultList.GetTop());
3787 if (clipCapturedBy ==
3788 ContainerItemType::OwnLayerForTransformWithRoundedClip) {
3789 clipState.Restore();
3790 resultList.AppendNewToTopWithIndex<nsDisplayOwnLayer>(
3791 aBuilder, this,
3792 /* aIndex = */ nsDisplayOwnLayer::OwnLayerForTransformWithRoundedClip,
3793 &resultList, aBuilder->CurrentActiveScrolledRoot(),
3794 nsDisplayOwnLayerFlags::None, ScrollbarData{},
3795 /* aForceActive = */ false, false);
3796 ct.TrackContainer(resultList.GetTop());
3799 /* If we have sticky positioning, wrap it in a sticky position item.
3801 if (useFixedPosition) {
3802 if (clipCapturedBy == ContainerItemType::FixedPosition) {
3803 clipState.Restore();
3805 // The ASR for the fixed item should be the ASR of our containing block,
3806 // which has been set as the builder's current ASR, unless this frame is
3807 // invisible and we hadn't saved display item data for it. In that case,
3808 // we need to take the containerItemASR since we might have fixed children.
3809 // For WebRender, we want to the know what |containerItemASR| is for the
3810 // case where the fixed-pos item is not a "real" fixed-pos item (e.g. it's
3811 // nested inside a scrolling transform), so we stash that on the display
3812 // item as well.
3813 const ActiveScrolledRoot* fixedASR = ActiveScrolledRoot::PickAncestor(
3814 containerItemASR, aBuilder->CurrentActiveScrolledRoot());
3815 resultList.AppendNewToTop<nsDisplayFixedPosition>(
3816 aBuilder, this, &resultList, fixedASR, containerItemASR);
3817 ct.TrackContainer(resultList.GetTop());
3818 } else if (useStickyPosition) {
3819 // For position:sticky, the clip needs to be applied both to the sticky
3820 // container item and to the contents. The container item needs the clip
3821 // because a scrolled clip needs to move independently from the sticky
3822 // contents, and the contents need the clip so that they have finite
3823 // clipped bounds with respect to the container item's ASR. The latter is
3824 // a little tricky in the case where the sticky item has both fixed and
3825 // non-fixed descendants, because that means that the sticky container
3826 // item's ASR is the ASR of the fixed descendant.
3827 // For WebRender display list building, though, we still want to know the
3828 // the ASR that the sticky container item would normally have, so we stash
3829 // that on the display item as the "container ASR" (i.e. the normal ASR of
3830 // the container item, excluding the special behaviour induced by fixed
3831 // descendants).
3832 const ActiveScrolledRoot* stickyASR = ActiveScrolledRoot::PickAncestor(
3833 containerItemASR, aBuilder->CurrentActiveScrolledRoot());
3835 auto* stickyItem = MakeDisplayItem<nsDisplayStickyPosition>(
3836 aBuilder, this, &resultList, stickyASR,
3837 aBuilder->CurrentActiveScrolledRoot(),
3838 clipState.IsClippedToDisplayPort());
3840 bool shouldFlatten = true;
3842 StickyScrollContainer* stickyScrollContainer =
3843 StickyScrollContainer::GetStickyScrollContainerForFrame(this);
3844 if (stickyScrollContainer &&
3845 stickyScrollContainer->ScrollFrame()->IsMaybeAsynchronouslyScrolled()) {
3846 shouldFlatten = false;
3849 stickyItem->SetShouldFlatten(shouldFlatten);
3851 resultList.AppendToTop(stickyItem);
3852 ct.TrackContainer(stickyItem);
3854 // If the sticky element is inside a filter, annotate the scroll frame that
3855 // scrolls the filter as having out-of-flow content inside a filter (this
3856 // inhibits paint skipping).
3857 if (aBuilder->GetFilterASR() && aBuilder->GetFilterASR() == stickyASR) {
3858 aBuilder->GetFilterASR()
3859 ->mScrollableFrame->SetHasOutOfFlowContentInsideFilter();
3863 /* If there's blending, wrap up the list in a blend-mode item. Note
3864 * that opacity can be applied before blending as the blend color is
3865 * not affected by foreground opacity (only background alpha).
3868 if (useBlendMode) {
3869 DisplayListClipState::AutoSaveRestore blendModeClipState(aBuilder);
3870 resultList.AppendNewToTop<nsDisplayBlendMode>(aBuilder, this, &resultList,
3871 effects->mMixBlendMode,
3872 containerItemASR, false);
3873 ct.TrackContainer(resultList.GetTop());
3876 bool createdOwnLayer = false;
3877 CreateOwnLayerIfNeeded(aBuilder, &resultList,
3878 nsDisplayOwnLayer::OwnLayerForStackingContext,
3879 &createdOwnLayer);
3881 if (createdOwnLayer) {
3882 ct.TrackContainer(resultList.GetTop());
3885 if (aBuilder->IsReusingStackingContextItems()) {
3886 if (resultList.IsEmpty()) {
3887 return;
3890 nsDisplayItem* container = resultList.GetBottom();
3891 if (resultList.Length() > 1 || container->Frame() != this) {
3892 container = MakeDisplayItem<nsDisplayContainer>(
3893 aBuilder, this, containerItemASR, &resultList);
3894 } else {
3895 MOZ_ASSERT(resultList.Length() == 1);
3896 resultList.Clear();
3899 // Mark the outermost display item as reusable. These display items and
3900 // their chidren can be reused during the next paint if no ancestor or
3901 // descendant frames have been modified.
3902 if (!container->IsReusedItem()) {
3903 container->SetReusable();
3905 aList->AppendToTop(container);
3906 ct.TrackContainer(container);
3907 } else {
3908 aList->AppendToTop(&resultList);
3911 if (aCreatedContainerItem) {
3912 *aCreatedContainerItem = ct.mCreatedContainer;
3916 static nsDisplayItem* WrapInWrapList(nsDisplayListBuilder* aBuilder,
3917 nsIFrame* aFrame, nsDisplayList* aList,
3918 const ActiveScrolledRoot* aContainerASR,
3919 bool aBuiltContainerItem = false) {
3920 nsDisplayItem* item = aList->GetBottom();
3921 if (!item) {
3922 return nullptr;
3925 // We need a wrap list if there are multiple items, or if the single
3926 // item has a different frame. This can change in a partial build depending
3927 // on which items we build, so we need to ensure that we don't transition
3928 // to/from a wrap list without invalidating correctly.
3929 bool needsWrapList =
3930 aList->Length() > 1 || item->Frame() != aFrame || item->GetChildren();
3932 // If we have an explicit container item (that can't change without an
3933 // invalidation) or we're doing a full build and don't need a wrap list, then
3934 // we can skip adding one.
3935 if (aBuiltContainerItem || (!aBuilder->IsPartialUpdate() && !needsWrapList)) {
3936 MOZ_ASSERT(aList->Length() == 1);
3937 aList->Clear();
3938 return item;
3941 // If we're doing a partial build and we didn't need a wrap list
3942 // previously then we can try to work from there.
3943 if (aBuilder->IsPartialUpdate() &&
3944 !aFrame->HasDisplayItem(uint32_t(DisplayItemType::TYPE_CONTAINER))) {
3945 // If we now need a wrap list, we must previously have had no display items
3946 // or a single one belonging to this frame. Mark the item itself as
3947 // discarded so that RetainedDisplayListBuilder uses the ones we just built.
3948 // We don't want to mark the frame as modified as that would invalidate
3949 // positioned descendants that might be outside of this list, and might not
3950 // have been rebuilt this time.
3951 if (needsWrapList) {
3952 DiscardOldItems(aFrame);
3953 } else {
3954 MOZ_ASSERT(aList->Length() == 1);
3955 aList->Clear();
3956 return item;
3960 // The last case we could try to handle is when we previously had a wrap list,
3961 // but no longer need it. Unfortunately we can't differentiate this case from
3962 // a partial build where other children exist but we just didn't build them
3963 // this time.
3964 // TODO:RetainedDisplayListBuilder's merge phase has the full list and
3965 // could strip them out.
3967 return MakeDisplayItem<nsDisplayContainer>(aBuilder, aFrame, aContainerASR,
3968 aList);
3972 * Check if a frame should be visited for building display list.
3974 static bool DescendIntoChild(nsDisplayListBuilder* aBuilder,
3975 const nsIFrame* aChild, const nsRect& aVisible,
3976 const nsRect& aDirty) {
3977 if (aChild->HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO)) {
3978 return true;
3981 // If the child is a scrollframe that we want to ignore, then we need
3982 // to descend into it because its scrolled child may intersect the dirty
3983 // area even if the scrollframe itself doesn't.
3984 if (aChild == aBuilder->GetIgnoreScrollFrame()) {
3985 return true;
3988 // There are cases where the "ignore scroll frame" on the builder is not set
3989 // correctly, and so we additionally want to catch cases where the child is
3990 // a root scrollframe and we are ignoring scrolling on the viewport.
3991 if (aChild == aBuilder->GetPresShellIgnoreScrollFrame()) {
3992 return true;
3995 nsRect overflow = aChild->InkOverflowRect();
3997 // On mobile, there may be a dynamic toolbar. The root content document's
3998 // root scroll frame's ink overflow rect does not include the toolbar
3999 // height, but if the toolbar is hidden, we still want to be able to target
4000 // content underneath the toolbar, so expand the overflow rect here to
4001 // allow display list building to descend into the scroll frame.
4002 if (aBuilder->IsForEventDelivery() &&
4003 aChild == aChild->PresShell()->GetRootScrollFrame() &&
4004 aChild->PresContext()->IsRootContentDocumentCrossProcess() &&
4005 aChild->PresContext()->HasDynamicToolbar()) {
4006 overflow.SizeTo(nsLayoutUtils::ExpandHeightForDynamicToolbar(
4007 aChild->PresContext(), overflow.Size()));
4010 if (aDirty.Intersects(overflow)) {
4011 return true;
4014 if (aChild->ForceDescendIntoIfVisible() && aVisible.Intersects(overflow)) {
4015 return true;
4018 if (aChild->IsFrameOfType(nsIFrame::eTablePart)) {
4019 // Relative positioning and transforms can cause table parts to move, but we
4020 // will still paint the backgrounds for their ancestor parts under them at
4021 // their 'normal' position. That means that we must consider the overflow
4022 // rects at both positions.
4024 // We convert the overflow rect into the nsTableFrame's coordinate
4025 // space, applying the normal position offset at each step. Then we
4026 // compare that against the builder's cached dirty rect in table
4027 // coordinate space.
4028 const nsIFrame* f = aChild;
4029 nsRect normalPositionOverflowRelativeToTable = overflow;
4031 while (f->IsFrameOfType(nsIFrame::eTablePart)) {
4032 normalPositionOverflowRelativeToTable += f->GetNormalPosition();
4033 f = f->GetParent();
4036 nsDisplayTableBackgroundSet* tableBGs = aBuilder->GetTableBackgroundSet();
4037 if (tableBGs && tableBGs->GetDirtyRect().Intersects(
4038 normalPositionOverflowRelativeToTable)) {
4039 return true;
4043 return false;
4046 void nsIFrame::BuildDisplayListForSimpleChild(nsDisplayListBuilder* aBuilder,
4047 nsIFrame* aChild,
4048 const nsDisplayListSet& aLists) {
4049 // This is the shortcut for frames been handled along the common
4050 // path, the most common one of THE COMMON CASE mentioned later.
4051 MOZ_ASSERT(aChild->Type() != LayoutFrameType::Placeholder);
4052 MOZ_ASSERT(!aBuilder->GetSelectedFramesOnly() &&
4053 !aBuilder->GetIncludeAllOutOfFlows(),
4054 "It should be held for painting to window");
4055 MOZ_ASSERT(aChild->HasAnyStateBits(NS_FRAME_SIMPLE_DISPLAYLIST));
4057 const nsPoint offset = aChild->GetOffsetTo(this);
4058 const nsRect visible = aBuilder->GetVisibleRect() - offset;
4059 const nsRect dirty = aBuilder->GetDirtyRect() - offset;
4061 if (!DescendIntoChild(aBuilder, aChild, visible, dirty)) {
4062 DL_LOGV("Skipped frame %p", aChild);
4063 return;
4066 // Child cannot be transformed since it is not a stacking context.
4067 nsDisplayListBuilder::AutoBuildingDisplayList buildingForChild(
4068 aBuilder, aChild, visible, dirty, false);
4070 UpdateCurrentHitTestInfo(aBuilder, aChild);
4072 aChild->MarkAbsoluteFramesForDisplayList(aBuilder);
4073 aBuilder->AdjustWindowDraggingRegion(aChild);
4074 aBuilder->Check();
4075 aChild->BuildDisplayList(aBuilder, aLists);
4076 aChild->SetBuiltDisplayList(true);
4077 aBuilder->Check();
4078 aBuilder->DisplayCaret(aChild, aLists.Outlines());
4079 #ifdef DEBUG
4080 DisplayDebugBorders(aBuilder, aChild, aLists);
4081 #endif
4084 nsIFrame::DisplayChildFlag nsIFrame::DisplayFlagForFlexOrGridItem() const {
4085 MOZ_ASSERT(IsFlexOrGridItem(),
4086 "Should only be called on flex or grid items!");
4087 return DisplayChildFlag::ForcePseudoStackingContext;
4090 static bool ShouldSkipFrame(nsDisplayListBuilder* aBuilder,
4091 const nsIFrame* aFrame) {
4092 // If painting is restricted to just the background of the top level frame,
4093 // then we have nothing to do here.
4094 if (aBuilder->IsBackgroundOnly()) {
4095 return true;
4098 if (aBuilder->IsForGenerateGlyphMask() &&
4099 (!aFrame->IsTextFrame() && aFrame->IsLeaf())) {
4100 return true;
4103 // The placeholder frame should have the same content as the OOF frame.
4104 if (aBuilder->GetSelectedFramesOnly() &&
4105 (aFrame->IsLeaf() && !aFrame->IsSelected())) {
4106 return true;
4109 static const nsFrameState skipFlags =
4110 (NS_FRAME_TOO_DEEP_IN_FRAME_TREE | NS_FRAME_IS_NONDISPLAY);
4112 return aFrame->HasAnyStateBits(skipFlags);
4115 void nsIFrame::BuildDisplayListForChild(nsDisplayListBuilder* aBuilder,
4116 nsIFrame* aChild,
4117 const nsDisplayListSet& aLists,
4118 DisplayChildFlags aFlags) {
4119 AutoCheckBuilder check(aBuilder);
4120 if (aBuilder->IsForContent()) {
4121 DL_LOGV("BuildDisplayListForChild (%p) <", aChild);
4123 ScopeExit e([aChild, aBuilder]() {
4124 if (aBuilder->IsForContent()) {
4125 DL_LOGV("> BuildDisplayListForChild (%p)", aChild);
4129 if (ShouldSkipFrame(aBuilder, aChild)) {
4130 return;
4133 // If we're generating a display list for printing, include Link items for
4134 // frames that correspond to HTML link elements so that we can have active
4135 // links in saved PDF output. Note that the state of "within a link" is
4136 // set on the display-list builder, such that all descendants of the link
4137 // element will generate display-list links.
4138 // TODO: we should be able to optimize this so as to avoid creating links
4139 // for the same destination that entirely overlap each other, which adds
4140 // nothing useful to the final PDF.
4141 Maybe<nsDisplayListBuilder::Linkifier> linkifier;
4142 if (StaticPrefs::print_save_as_pdf_links_enabled() &&
4143 aBuilder->IsForPrinting()) {
4144 linkifier.emplace(aBuilder, aChild, aLists.Content());
4145 linkifier->MaybeAppendLink(aBuilder, aChild);
4148 nsIFrame* child = aChild;
4149 auto* placeholder = child->IsPlaceholderFrame()
4150 ? static_cast<nsPlaceholderFrame*>(child)
4151 : nullptr;
4152 nsIFrame* childOrOutOfFlow =
4153 placeholder ? placeholder->GetOutOfFlowFrame() : child;
4155 nsIFrame* parent = childOrOutOfFlow->GetParent();
4156 const auto* parentDisplay = parent->StyleDisplay();
4157 const auto overflowClipAxes =
4158 parent->ShouldApplyOverflowClipping(parentDisplay);
4160 const bool isPaintingToWindow = aBuilder->IsPaintingToWindow();
4161 const bool doingShortcut =
4162 isPaintingToWindow &&
4163 child->HasAnyStateBits(NS_FRAME_SIMPLE_DISPLAYLIST) &&
4164 // Animations may change the stacking context state.
4165 // ShouldApplyOverflowClipping is affected by the parent style, which does
4166 // not invalidate the NS_FRAME_SIMPLE_DISPLAYLIST bit.
4167 !(overflowClipAxes != PhysicalAxes::None ||
4168 child->MayHaveTransformAnimation() || child->MayHaveOpacityAnimation());
4170 if (aBuilder->IsForPainting()) {
4171 aBuilder->ClearWillChangeBudgetStatus(child);
4174 if (StaticPrefs::layout_css_scroll_anchoring_highlight()) {
4175 if (child->FirstContinuation()->IsScrollAnchor()) {
4176 nsRect bounds = child->GetContentRectRelativeToSelf() +
4177 aBuilder->ToReferenceFrame(child);
4178 nsDisplaySolidColor* color = MakeDisplayItem<nsDisplaySolidColor>(
4179 aBuilder, child, bounds, NS_RGBA(255, 0, 255, 64));
4180 if (color) {
4181 color->SetOverrideZIndex(INT32_MAX);
4182 aLists.PositionedDescendants()->AppendToTop(color);
4187 if (doingShortcut) {
4188 BuildDisplayListForSimpleChild(aBuilder, child, aLists);
4189 return;
4192 // dirty rect in child-relative coordinates
4193 NS_ASSERTION(aBuilder->GetCurrentFrame() == this, "Wrong coord space!");
4194 const nsPoint offset = child->GetOffsetTo(this);
4195 nsRect visible = aBuilder->GetVisibleRect() - offset;
4196 nsRect dirty = aBuilder->GetDirtyRect() - offset;
4198 nsDisplayListBuilder::OutOfFlowDisplayData* savedOutOfFlowData = nullptr;
4199 if (placeholder) {
4200 if (placeholder->HasAnyStateBits(PLACEHOLDER_FOR_TOPLAYER)) {
4201 // If the out-of-flow frame is in the top layer, the viewport frame
4202 // will paint it. Skip it here. Note that, only out-of-flow frames
4203 // with this property should be skipped, because non-HTML elements
4204 // may stop their children from being out-of-flow. Those frames
4205 // should still be handled in the normal in-flow path.
4206 return;
4209 child = childOrOutOfFlow;
4210 if (aBuilder->IsForPainting()) {
4211 aBuilder->ClearWillChangeBudgetStatus(child);
4214 // If 'child' is a pushed float then it's owned by a block that's not an
4215 // ancestor of the placeholder, and it will be painted by that block and
4216 // should not be painted through the placeholder. Also recheck
4217 // NS_FRAME_TOO_DEEP_IN_FRAME_TREE and NS_FRAME_IS_NONDISPLAY.
4218 static const nsFrameState skipFlags =
4219 (NS_FRAME_IS_PUSHED_FLOAT | NS_FRAME_TOO_DEEP_IN_FRAME_TREE |
4220 NS_FRAME_IS_NONDISPLAY);
4221 if (child->HasAnyStateBits(skipFlags) || nsLayoutUtils::IsPopup(child)) {
4222 return;
4225 MOZ_ASSERT(child->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW));
4226 savedOutOfFlowData = nsDisplayListBuilder::GetOutOfFlowData(child);
4228 if (aBuilder->GetIncludeAllOutOfFlows()) {
4229 visible = child->InkOverflowRect();
4230 dirty = child->InkOverflowRect();
4231 } else if (savedOutOfFlowData) {
4232 visible =
4233 savedOutOfFlowData->GetVisibleRectForFrame(aBuilder, child, &dirty);
4234 } else {
4235 // The out-of-flow frame did not intersect the dirty area. We may still
4236 // need to traverse into it, since it may contain placeholders we need
4237 // to enter to reach other out-of-flow frames that are visible.
4238 visible.SetEmpty();
4239 dirty.SetEmpty();
4243 NS_ASSERTION(!child->IsPlaceholderFrame(),
4244 "Should have dealt with placeholders already");
4246 if (!DescendIntoChild(aBuilder, child, visible, dirty)) {
4247 DL_LOGV("Skipped frame %p", child);
4248 return;
4251 const bool isSVG = child->HasAnyStateBits(NS_FRAME_SVG_LAYOUT);
4253 // This flag is raised if the control flow strays off the common path.
4254 // The common path is the most common one of THE COMMON CASE mentioned later.
4255 bool awayFromCommonPath = !isPaintingToWindow;
4257 // true if this is a real or pseudo stacking context
4258 bool pseudoStackingContext =
4259 aFlags.contains(DisplayChildFlag::ForcePseudoStackingContext);
4261 if (!pseudoStackingContext && !isSVG &&
4262 aFlags.contains(DisplayChildFlag::Inline) &&
4263 !child->IsFrameOfType(eLineParticipant)) {
4264 // child is a non-inline frame in an inline context, i.e.,
4265 // it acts like inline-block or inline-table. Therefore it is a
4266 // pseudo-stacking-context.
4267 pseudoStackingContext = true;
4270 const nsStyleDisplay* ourDisp = StyleDisplay();
4271 // REVIEW: Taken from nsBoxFrame::Paint
4272 // Don't paint our children if the theme object is a leaf.
4273 if (IsThemed(ourDisp) && !PresContext()->Theme()->WidgetIsContainer(
4274 ourDisp->EffectiveAppearance()))
4275 return;
4277 // Since we're now sure that we're adding this frame to the display list
4278 // (which means we're painting it, modulo occlusion), mark it as visible
4279 // within the displayport.
4280 if (isPaintingToWindow && child->TrackingVisibility()) {
4281 child->PresShell()->EnsureFrameInApproximatelyVisibleList(child);
4282 awayFromCommonPath = true;
4285 // Child is composited if it's transformed, partially transparent, or has
4286 // SVG effects or a blend mode..
4287 const nsStyleDisplay* disp = child->StyleDisplay();
4288 const nsStyleEffects* effects = child->StyleEffects();
4290 const bool isPositioned = disp->IsPositionedStyle();
4291 const bool isStackingContext =
4292 aFlags.contains(DisplayChildFlag::ForceStackingContext) ||
4293 child->IsStackingContext(disp, effects);
4295 if (pseudoStackingContext || isStackingContext || isPositioned ||
4296 placeholder || (!isSVG && disp->IsFloating(child)) ||
4297 (isSVG && effects->mClip.IsRect() && IsSVGContentWithCSSClip(child))) {
4298 pseudoStackingContext = true;
4299 awayFromCommonPath = true;
4302 NS_ASSERTION(!isStackingContext || pseudoStackingContext,
4303 "Stacking contexts must also be pseudo-stacking-contexts");
4305 nsDisplayListBuilder::AutoBuildingDisplayList buildingForChild(
4306 aBuilder, child, visible, dirty);
4308 UpdateCurrentHitTestInfo(aBuilder, child);
4310 DisplayListClipState::AutoClipMultiple clipState(aBuilder);
4311 nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter asrSetter(aBuilder);
4313 if (savedOutOfFlowData) {
4314 aBuilder->SetBuildingInvisibleItems(false);
4316 clipState.SetClipChainForContainingBlockDescendants(
4317 savedOutOfFlowData->mContainingBlockClipChain);
4318 asrSetter.SetCurrentActiveScrolledRoot(
4319 savedOutOfFlowData->mContainingBlockActiveScrolledRoot);
4320 MOZ_ASSERT(awayFromCommonPath,
4321 "It is impossible when savedOutOfFlowData is true");
4322 } else if (HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO) &&
4323 placeholder) {
4324 NS_ASSERTION(visible.IsEmpty(), "should have empty visible rect");
4325 // Every item we build from now until we descent into an out of flow that
4326 // does have saved out of flow data should be invisible. This state gets
4327 // restored when AutoBuildingDisplayList gets out of scope.
4328 aBuilder->SetBuildingInvisibleItems(true);
4330 // If we have nested out-of-flow frames and the outer one isn't visible
4331 // then we won't have stored clip data for it. We can just clear the clip
4332 // instead since we know we won't render anything, and the inner out-of-flow
4333 // frame will setup the correct clip for itself.
4334 clipState.SetClipChainForContainingBlockDescendants(nullptr);
4337 // Setup clipping for the parent's overflow:clip,
4338 // or overflow:hidden on elements that don't support scrolling (and therefore
4339 // don't create nsHTML/XULScrollFrame). This clipping needs to not clip
4340 // anything directly rendered by the parent, only the rendering of its
4341 // children.
4342 // Don't use overflowClip to restrict the dirty rect, since some of the
4343 // descendants may not be clipped by it. Even if we end up with unnecessary
4344 // display items, they'll be pruned during ComputeVisibility.
4346 // FIXME(emilio): Why can't we handle this more similarly to `clip` (on the
4347 // parent, rather than on the children)? Would ClipContentDescendants do what
4348 // we want?
4349 if (overflowClipAxes != PhysicalAxes::None) {
4350 ApplyOverflowClipping(aBuilder, parent, overflowClipAxes, clipState);
4351 awayFromCommonPath = true;
4354 nsDisplayList list(aBuilder);
4355 nsDisplayList extraPositionedDescendants(aBuilder);
4356 const ActiveScrolledRoot* wrapListASR;
4357 bool builtContainerItem = false;
4358 if (isStackingContext) {
4359 // True stacking context.
4360 // For stacking contexts, BuildDisplayListForStackingContext handles
4361 // clipping and MarkAbsoluteFramesForDisplayList.
4362 nsDisplayListBuilder::AutoContainerASRTracker contASRTracker(aBuilder);
4363 child->BuildDisplayListForStackingContext(aBuilder, &list,
4364 &builtContainerItem);
4365 wrapListASR = contASRTracker.GetContainerASR();
4366 if (!aBuilder->IsReusingStackingContextItems() &&
4367 aBuilder->GetCaretFrame() == child) {
4368 builtContainerItem = false;
4370 } else {
4371 Maybe<nsRect> clipPropClip =
4372 child->GetClipPropClipRect(disp, effects, child->GetSize());
4373 if (clipPropClip) {
4374 aBuilder->IntersectVisibleRect(*clipPropClip);
4375 aBuilder->IntersectDirtyRect(*clipPropClip);
4376 clipState.ClipContentDescendants(*clipPropClip +
4377 aBuilder->ToReferenceFrame(child));
4378 awayFromCommonPath = true;
4381 child->MarkAbsoluteFramesForDisplayList(aBuilder);
4382 child->SetBuiltDisplayList(true);
4384 if (!awayFromCommonPath &&
4385 // Some SVG frames might change opacity without invalidating the frame,
4386 // so exclude them from the fast-path.
4387 !child->IsFrameOfType(nsIFrame::eSVG)) {
4388 // The shortcut is available for the child for next time.
4389 child->AddStateBits(NS_FRAME_SIMPLE_DISPLAYLIST);
4392 if (!pseudoStackingContext) {
4393 // THIS IS THE COMMON CASE.
4394 // Not a pseudo or real stacking context. Do the simple thing and
4395 // return early.
4396 aBuilder->AdjustWindowDraggingRegion(child);
4397 aBuilder->Check();
4398 child->BuildDisplayList(aBuilder, aLists);
4399 aBuilder->Check();
4400 aBuilder->DisplayCaret(child, aLists.Outlines());
4401 #ifdef DEBUG
4402 DisplayDebugBorders(aBuilder, child, aLists);
4403 #endif
4404 return;
4407 // A pseudo-stacking context (e.g., a positioned element with z-index auto).
4408 // We allow positioned descendants of the child to escape to our parent
4409 // stacking context's positioned descendant list, because they might be
4410 // z-index:non-auto
4411 nsDisplayListCollection pseudoStack(aBuilder);
4413 aBuilder->AdjustWindowDraggingRegion(child);
4414 nsDisplayListBuilder::AutoContainerASRTracker contASRTracker(aBuilder);
4415 aBuilder->Check();
4416 child->BuildDisplayList(aBuilder, pseudoStack);
4417 aBuilder->Check();
4418 if (aBuilder->DisplayCaret(child, pseudoStack.Outlines())) {
4419 builtContainerItem = false;
4421 wrapListASR = contASRTracker.GetContainerASR();
4423 list.AppendToTop(pseudoStack.BorderBackground());
4424 list.AppendToTop(pseudoStack.BlockBorderBackgrounds());
4425 list.AppendToTop(pseudoStack.Floats());
4426 list.AppendToTop(pseudoStack.Content());
4427 list.AppendToTop(pseudoStack.Outlines());
4428 extraPositionedDescendants.AppendToTop(pseudoStack.PositionedDescendants());
4429 #ifdef DEBUG
4430 DisplayDebugBorders(aBuilder, child, aLists);
4431 #endif
4434 buildingForChild.RestoreBuildingInvisibleItemsValue();
4436 if (!list.IsEmpty()) {
4437 if (isPositioned || isStackingContext) {
4438 // Genuine stacking contexts, and positioned pseudo-stacking-contexts,
4439 // go in this level.
4440 nsDisplayItem* item = WrapInWrapList(aBuilder, child, &list, wrapListASR,
4441 builtContainerItem);
4442 if (isSVG) {
4443 aLists.Content()->AppendToTop(item);
4444 } else {
4445 aLists.PositionedDescendants()->AppendToTop(item);
4447 } else if (!isSVG && disp->IsFloating(child)) {
4448 aLists.Floats()->AppendToTop(
4449 WrapInWrapList(aBuilder, child, &list, wrapListASR));
4450 } else {
4451 aLists.Content()->AppendToTop(&list);
4454 // We delay placing the positioned descendants of positioned frames to here,
4455 // because in the absence of z-index this is the correct order for them.
4456 // This doesn't affect correctness because the positioned descendants list
4457 // is sorted by z-order and content in BuildDisplayListForStackingContext,
4458 // but it means that sort routine needs to do less work.
4459 aLists.PositionedDescendants()->AppendToTop(&extraPositionedDescendants);
4462 void nsIFrame::MarkAbsoluteFramesForDisplayList(
4463 nsDisplayListBuilder* aBuilder) {
4464 if (IsAbsoluteContainer()) {
4465 aBuilder->MarkFramesForDisplayList(
4466 this, GetAbsoluteContainingBlock()->GetChildList());
4470 nsresult nsIFrame::GetContentForEvent(WidgetEvent* aEvent,
4471 nsIContent** aContent) {
4472 nsIFrame* f = nsLayoutUtils::GetNonGeneratedAncestor(this);
4473 *aContent = f->GetContent();
4474 NS_IF_ADDREF(*aContent);
4475 return NS_OK;
4478 void nsIFrame::FireDOMEvent(const nsAString& aDOMEventName,
4479 nsIContent* aContent) {
4480 nsIContent* target = aContent ? aContent : GetContent();
4482 if (target) {
4483 RefPtr<AsyncEventDispatcher> asyncDispatcher = new AsyncEventDispatcher(
4484 target, aDOMEventName, CanBubble::eYes, ChromeOnlyDispatch::eNo);
4485 DebugOnly<nsresult> rv = asyncDispatcher->PostDOMEvent();
4486 NS_ASSERTION(NS_SUCCEEDED(rv), "AsyncEventDispatcher failed to dispatch");
4490 nsresult nsIFrame::HandleEvent(nsPresContext* aPresContext,
4491 WidgetGUIEvent* aEvent,
4492 nsEventStatus* aEventStatus) {
4493 if (aEvent->mMessage == eMouseMove) {
4494 // XXX If the second argument of HandleDrag() is WidgetMouseEvent,
4495 // the implementation becomes simpler.
4496 return HandleDrag(aPresContext, aEvent, aEventStatus);
4499 if ((aEvent->mClass == eMouseEventClass &&
4500 aEvent->AsMouseEvent()->mButton == MouseButton::ePrimary) ||
4501 aEvent->mClass == eTouchEventClass) {
4502 if (aEvent->mMessage == eMouseDown || aEvent->mMessage == eTouchStart) {
4503 HandlePress(aPresContext, aEvent, aEventStatus);
4504 } else if (aEvent->mMessage == eMouseUp || aEvent->mMessage == eTouchEnd) {
4505 HandleRelease(aPresContext, aEvent, aEventStatus);
4507 return NS_OK;
4510 // When middle button is down, we need to just move selection and focus at
4511 // the clicked point. Note that even if middle click paste is not enabled,
4512 // Chrome moves selection at middle mouse button down. So, we should follow
4513 // the behavior for the compatibility.
4514 if (aEvent->mMessage == eMouseDown) {
4515 WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
4516 if (mouseEvent && mouseEvent->mButton == MouseButton::eMiddle) {
4517 if (*aEventStatus == nsEventStatus_eConsumeNoDefault) {
4518 return NS_OK;
4520 return MoveCaretToEventPoint(aPresContext, mouseEvent, aEventStatus);
4524 return NS_OK;
4527 nsresult nsIFrame::GetDataForTableSelection(
4528 const nsFrameSelection* aFrameSelection, mozilla::PresShell* aPresShell,
4529 WidgetMouseEvent* aMouseEvent, nsIContent** aParentContent,
4530 int32_t* aContentOffset, TableSelectionMode* aTarget) {
4531 if (!aFrameSelection || !aPresShell || !aMouseEvent || !aParentContent ||
4532 !aContentOffset || !aTarget)
4533 return NS_ERROR_NULL_POINTER;
4535 *aParentContent = nullptr;
4536 *aContentOffset = 0;
4537 *aTarget = TableSelectionMode::None;
4539 int16_t displaySelection = aPresShell->GetSelectionFlags();
4541 bool selectingTableCells = aFrameSelection->IsInTableSelectionMode();
4543 // DISPLAY_ALL means we're in an editor.
4544 // If already in cell selection mode,
4545 // continue selecting with mouse drag or end on mouse up,
4546 // or when using shift key to extend block of cells
4547 // (Mouse down does normal selection unless Ctrl/Cmd is pressed)
4548 bool doTableSelection =
4549 displaySelection == nsISelectionDisplay::DISPLAY_ALL &&
4550 selectingTableCells &&
4551 (aMouseEvent->mMessage == eMouseMove ||
4552 (aMouseEvent->mMessage == eMouseUp &&
4553 aMouseEvent->mButton == MouseButton::ePrimary) ||
4554 aMouseEvent->IsShift());
4556 if (!doTableSelection) {
4557 // In Browser, special 'table selection' key must be pressed for table
4558 // selection or when just Shift is pressed and we're already in table/cell
4559 // selection mode
4560 #ifdef XP_MACOSX
4561 doTableSelection = aMouseEvent->IsMeta() ||
4562 (aMouseEvent->IsShift() && selectingTableCells);
4563 #else
4564 doTableSelection = aMouseEvent->IsControl() ||
4565 (aMouseEvent->IsShift() && selectingTableCells);
4566 #endif
4568 if (!doTableSelection) return NS_OK;
4570 // Get the cell frame or table frame (or parent) of the current content node
4571 nsIFrame* frame = this;
4572 bool foundCell = false;
4573 bool foundTable = false;
4575 // Get the limiting node to stop parent frame search
4576 nsIContent* limiter = aFrameSelection->GetLimiter();
4578 // If our content node is an ancestor of the limiting node,
4579 // we should stop the search right now.
4580 if (limiter && limiter->IsInclusiveDescendantOf(GetContent())) return NS_OK;
4582 // We don't initiate row/col selection from here now,
4583 // but we may in future
4584 // bool selectColumn = false;
4585 // bool selectRow = false;
4587 while (frame) {
4588 // Check for a table cell by querying to a known CellFrame interface
4589 nsITableCellLayout* cellElement = do_QueryFrame(frame);
4590 if (cellElement) {
4591 foundCell = true;
4592 // TODO: If we want to use proximity to top or left border
4593 // for row and column selection, this is the place to do it
4594 break;
4595 } else {
4596 // If not a cell, check for table
4597 // This will happen when starting frame is the table or child of a table,
4598 // such as a row (we were inbetween cells or in table border)
4599 nsTableWrapperFrame* tableFrame = do_QueryFrame(frame);
4600 if (tableFrame) {
4601 foundTable = true;
4602 // TODO: How can we select row when along left table edge
4603 // or select column when along top edge?
4604 break;
4605 } else {
4606 frame = frame->GetParent();
4607 // Stop if we have hit the selection's limiting content node
4608 if (frame && frame->GetContent() == limiter) break;
4612 // We aren't in a cell or table
4613 if (!foundCell && !foundTable) return NS_OK;
4615 nsIContent* tableOrCellContent = frame->GetContent();
4616 if (!tableOrCellContent) return NS_ERROR_FAILURE;
4618 nsCOMPtr<nsIContent> parentContent = tableOrCellContent->GetParent();
4619 if (!parentContent) return NS_ERROR_FAILURE;
4621 const int32_t offset =
4622 parentContent->ComputeIndexOf_Deprecated(tableOrCellContent);
4623 // Not likely?
4624 if (offset < 0) {
4625 return NS_ERROR_FAILURE;
4628 // Everything is OK -- set the return values
4629 parentContent.forget(aParentContent);
4631 *aContentOffset = offset;
4633 #if 0
4634 if (selectRow)
4635 *aTarget = TableSelectionMode::Row;
4636 else if (selectColumn)
4637 *aTarget = TableSelectionMode::Column;
4638 else
4639 #endif
4640 if (foundCell) {
4641 *aTarget = TableSelectionMode::Cell;
4642 } else if (foundTable) {
4643 *aTarget = TableSelectionMode::Table;
4646 return NS_OK;
4649 static bool IsEditingHost(const nsIFrame* aFrame) {
4650 auto* element = nsGenericHTMLElement::FromNodeOrNull(aFrame->GetContent());
4651 return element && element->IsEditableRoot();
4654 static bool IsTopmostModalDialog(const nsIFrame* aFrame) {
4655 auto* element = Element::FromNodeOrNull(aFrame->GetContent());
4656 return element &&
4657 element->State().HasState(NS_EVENT_STATE_TOPMOST_MODAL_DIALOG);
4660 static StyleUserSelect UsedUserSelect(const nsIFrame* aFrame) {
4661 if (aFrame->IsGeneratedContentFrame()) {
4662 return StyleUserSelect::None;
4665 // Per https://drafts.csswg.org/css-ui-4/#content-selection:
4667 // The computed value is the specified value, except:
4669 // 1 - on editable elements where the computed value is always 'contain'
4670 // regardless of the specified value.
4671 // 2 - when the specified value is auto, which computes to one of the other
4672 // values [...]
4674 // See https://github.com/w3c/csswg-drafts/issues/3344 to see why we do this
4675 // at used-value time instead of at computed-value time.
4677 // Also, we check for auto first to allow explicitly overriding the value for
4678 // the editing host.
4679 auto style = aFrame->Style()->UserSelect();
4680 if (style != StyleUserSelect::Auto) {
4681 return style;
4684 if (aFrame->IsTextInputFrame() || IsEditingHost(aFrame) ||
4685 IsTopmostModalDialog(aFrame)) {
4686 // We don't implement 'contain' itself, but we make 'text' behave as
4687 // 'contain' for contenteditable and <input> / <textarea> elements anyway so
4688 // this is ok.
4690 // Topmost modal dialogs need to behave like `text` too, because they're
4691 // supposed to be selectable even if their ancestors are inert.
4692 return StyleUserSelect::Text;
4695 auto* parent = nsLayoutUtils::GetParentOrPlaceholderFor(aFrame);
4696 return parent ? UsedUserSelect(parent) : StyleUserSelect::Text;
4699 bool nsIFrame::IsSelectable(StyleUserSelect* aSelectStyle) const {
4700 auto style = UsedUserSelect(this);
4701 if (aSelectStyle) {
4702 *aSelectStyle = style;
4704 return style != StyleUserSelect::None;
4707 bool nsIFrame::ShouldHaveLineIfEmpty() const {
4708 if (Style()->IsPseudoOrAnonBox() &&
4709 Style()->GetPseudoType() != PseudoStyleType::scrolledContent) {
4710 return false;
4712 return IsEditingHost(this);
4716 * Handles the Mouse Press Event for the frame
4718 NS_IMETHODIMP
4719 nsIFrame::HandlePress(nsPresContext* aPresContext, WidgetGUIEvent* aEvent,
4720 nsEventStatus* aEventStatus) {
4721 NS_ENSURE_ARG_POINTER(aEventStatus);
4722 if (nsEventStatus_eConsumeNoDefault == *aEventStatus) {
4723 return NS_OK;
4726 NS_ENSURE_ARG_POINTER(aEvent);
4727 if (aEvent->mClass == eTouchEventClass) {
4728 return NS_OK;
4731 return MoveCaretToEventPoint(aPresContext, aEvent->AsMouseEvent(),
4732 aEventStatus);
4735 nsresult nsIFrame::MoveCaretToEventPoint(nsPresContext* aPresContext,
4736 WidgetMouseEvent* aMouseEvent,
4737 nsEventStatus* aEventStatus) {
4738 MOZ_ASSERT(aPresContext);
4739 MOZ_ASSERT(aMouseEvent);
4740 MOZ_ASSERT(aMouseEvent->mMessage == eMouseDown);
4741 MOZ_ASSERT(aMouseEvent->mButton == MouseButton::ePrimary ||
4742 aMouseEvent->mButton == MouseButton::eMiddle);
4743 MOZ_ASSERT(aEventStatus);
4744 MOZ_ASSERT(nsEventStatus_eConsumeNoDefault != *aEventStatus);
4746 mozilla::PresShell* presShell = aPresContext->GetPresShell();
4747 if (!presShell) {
4748 return NS_ERROR_FAILURE;
4751 // We often get out of sync state issues with mousedown events that
4752 // get interrupted by alerts/dialogs.
4753 // Check with the ESM to see if we should process this one
4754 if (!aPresContext->EventStateManager()->EventStatusOK(aMouseEvent)) {
4755 return NS_OK;
4758 const nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(
4759 aMouseEvent, RelativeTo{this});
4761 // When not using `alt`, and clicking on a draggable, but non-editable
4762 // element, don't do anything, and let d&d handle the event.
4764 // See bug 48876, bug 388659 and bug 55921 for context here.
4766 // FIXME(emilio): The .Contains(pt) check looks a bit fishy. When would it be
4767 // false given we're the event target? If it is needed, why not checking the
4768 // actual draggable node rect instead?
4769 if (!aMouseEvent->IsAlt() && GetRectRelativeToSelf().Contains(pt)) {
4770 for (nsIContent* content = mContent; content;
4771 content = content->GetFlattenedTreeParent()) {
4772 if (nsContentUtils::ContentIsDraggable(content) &&
4773 !content->IsEditable()) {
4774 return NS_OK;
4779 // If we are in Navigator and the click is in a draggable node, we don't want
4780 // to start selection because we don't want to interfere with a potential
4781 // drag of said node and steal all its glory.
4782 const bool isEditor =
4783 presShell->GetSelectionFlags() == nsISelectionDisplay::DISPLAY_ALL;
4785 // Don't do something if it's middle button down event.
4786 const bool isPrimaryButtonDown =
4787 aMouseEvent->mButton == MouseButton::ePrimary;
4789 // check whether style allows selection
4790 // if not, don't tell selection the mouse event even occurred.
4791 StyleUserSelect selectStyle;
4792 // check for select: none
4793 if (!IsSelectable(&selectStyle)) {
4794 return NS_OK;
4797 if (isPrimaryButtonDown) {
4798 // If the mouse is dragged outside the nearest enclosing scrollable area
4799 // while making a selection, the area will be scrolled. To do this, capture
4800 // the mouse on the nearest scrollable frame. If there isn't a scrollable
4801 // frame, or something else is already capturing the mouse, there's no
4802 // reason to capture.
4803 if (!PresShell::GetCapturingContent()) {
4804 nsIScrollableFrame* scrollFrame =
4805 nsLayoutUtils::GetNearestScrollableFrame(
4806 this, nsLayoutUtils::SCROLLABLE_SAME_DOC |
4807 nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN);
4808 if (scrollFrame) {
4809 nsIFrame* capturingFrame = do_QueryFrame(scrollFrame);
4810 PresShell::SetCapturingContent(capturingFrame->GetContent(),
4811 CaptureFlags::IgnoreAllowedState);
4816 // XXX This is screwy; it really should use the selection frame, not the
4817 // event frame
4818 const nsFrameSelection* frameselection =
4819 selectStyle == StyleUserSelect::Text ? GetConstFrameSelection()
4820 : presShell->ConstFrameSelection();
4822 if (!frameselection || frameselection->GetDisplaySelection() ==
4823 nsISelectionController::SELECTION_OFF) {
4824 return NS_OK; // nothing to do we cannot affect selection from here
4827 #ifdef XP_MACOSX
4828 // If Control key is pressed on macOS, it should be treated as right click.
4829 // So, don't change selection.
4830 if (aMouseEvent->IsControl()) {
4831 return NS_OK;
4833 const bool control = aMouseEvent->IsMeta();
4834 #else
4835 const bool control = aMouseEvent->IsControl();
4836 #endif
4838 RefPtr<nsFrameSelection> fc = const_cast<nsFrameSelection*>(frameselection);
4839 if (isPrimaryButtonDown && aMouseEvent->mClickCount > 1) {
4840 // These methods aren't const but can't actually delete anything,
4841 // so no need for AutoWeakFrame.
4842 fc->SetDragState(true);
4843 return HandleMultiplePress(aPresContext, aMouseEvent, aEventStatus,
4844 control);
4847 ContentOffsets offsets = GetContentOffsetsFromPoint(pt, SKIP_HIDDEN);
4849 if (!offsets.content) {
4850 return NS_ERROR_FAILURE;
4853 if (aMouseEvent->mMessage == eMouseDown &&
4854 aMouseEvent->mButton == MouseButton::eMiddle &&
4855 !offsets.content->IsEditable()) {
4856 // However, some users don't like the Chrome compatible behavior of
4857 // middle mouse click. They want to keep selection after starting
4858 // autoscroll. However, the selection change is important for middle
4859 // mouse past. Therefore, we should allow users to take the traditional
4860 // behavior back by themselves unless middle click paste is enabled or
4861 // autoscrolling is disabled.
4862 if (!Preferences::GetBool("middlemouse.paste", false) &&
4863 Preferences::GetBool("general.autoScroll", false) &&
4864 Preferences::GetBool("general.autoscroll.prevent_to_collapse_selection_"
4865 "by_middle_mouse_down",
4866 false)) {
4867 return NS_OK;
4871 if (isPrimaryButtonDown) {
4872 // Let Ctrl/Cmd + left mouse down do table selection instead of drag
4873 // initiation.
4874 nsCOMPtr<nsIContent> parentContent;
4875 int32_t contentOffset;
4876 TableSelectionMode target;
4877 nsresult rv = GetDataForTableSelection(
4878 frameselection, presShell, aMouseEvent, getter_AddRefs(parentContent),
4879 &contentOffset, &target);
4880 if (NS_SUCCEEDED(rv) && parentContent) {
4881 fc->SetDragState(true);
4882 return fc->HandleTableSelection(parentContent, contentOffset, target,
4883 aMouseEvent);
4887 fc->SetDelayedCaretData(0);
4889 if (isPrimaryButtonDown) {
4890 // Check if any part of this frame is selected, and if the user clicked
4891 // inside the selected region, and if it's the left button. If so, we delay
4892 // starting a new selection since the user may be trying to drag the
4893 // selected region to some other app.
4895 if (GetContent() && GetContent()->IsMaybeSelected()) {
4896 bool inSelection = false;
4897 UniquePtr<SelectionDetails> details = frameselection->LookUpSelection(
4898 offsets.content, 0, offsets.EndOffset(), false);
4901 // If there are any details, check to see if the user clicked
4902 // within any selected region of the frame.
4905 for (SelectionDetails* curDetail = details.get(); curDetail;
4906 curDetail = curDetail->mNext.get()) {
4908 // If the user clicked inside a selection, then just
4909 // return without doing anything. We will handle placing
4910 // the caret later on when the mouse is released. We ignore
4911 // the spellcheck, find and url formatting selections.
4913 if (curDetail->mSelectionType != SelectionType::eSpellCheck &&
4914 curDetail->mSelectionType != SelectionType::eFind &&
4915 curDetail->mSelectionType != SelectionType::eURLSecondary &&
4916 curDetail->mSelectionType != SelectionType::eURLStrikeout &&
4917 curDetail->mStart <= offsets.StartOffset() &&
4918 offsets.EndOffset() <= curDetail->mEnd) {
4919 inSelection = true;
4923 if (inSelection) {
4924 fc->SetDragState(false);
4925 fc->SetDelayedCaretData(aMouseEvent);
4926 return NS_OK;
4930 fc->SetDragState(true);
4933 // Do not touch any nsFrame members after this point without adding
4934 // weakFrame checks.
4935 const nsFrameSelection::FocusMode focusMode = [&]() {
4936 // If "Shift" and "Ctrl" are both pressed, "Shift" is given precedence. This
4937 // mimics the old behaviour.
4938 if (aMouseEvent->IsShift()) {
4939 // If clicked in a link when focused content is editable, we should
4940 // collapse selection in the link for compatibility with Blink.
4941 if (isEditor) {
4942 nsCOMPtr<nsIURI> uri;
4943 for (Element* element : mContent->InclusiveAncestorsOfType<Element>()) {
4944 if (element->IsLink(getter_AddRefs(uri))) {
4945 return nsFrameSelection::FocusMode::kCollapseToNewPoint;
4949 return nsFrameSelection::FocusMode::kExtendSelection;
4952 if (isPrimaryButtonDown && control) {
4953 return nsFrameSelection::FocusMode::kMultiRangeSelection;
4956 return nsFrameSelection::FocusMode::kCollapseToNewPoint;
4957 }();
4959 nsresult rv = fc->HandleClick(
4960 MOZ_KnownLive(offsets.content) /* bug 1636889 */, offsets.StartOffset(),
4961 offsets.EndOffset(), focusMode, offsets.associate);
4962 if (NS_FAILED(rv)) {
4963 return rv;
4966 // We don't handle mouse button up if it's middle button.
4967 if (isPrimaryButtonDown && offsets.offset != offsets.secondaryOffset) {
4968 fc->MaintainSelection();
4971 if (isPrimaryButtonDown && isEditor && !aMouseEvent->IsShift() &&
4972 (offsets.EndOffset() - offsets.StartOffset()) == 1) {
4973 // A single node is selected and we aren't extending an existing selection,
4974 // which means the user clicked directly on an object (either
4975 // `user-select: all` or a non-text node without children). Therefore,
4976 // disable selection extension during mouse moves.
4977 // XXX This is a bit hacky; shouldn't editor be able to deal with this?
4978 fc->SetDragState(false);
4981 return NS_OK;
4984 nsresult nsIFrame::SelectByTypeAtPoint(nsPresContext* aPresContext,
4985 const nsPoint& aPoint,
4986 nsSelectionAmount aBeginAmountType,
4987 nsSelectionAmount aEndAmountType,
4988 uint32_t aSelectFlags) {
4989 NS_ENSURE_ARG_POINTER(aPresContext);
4991 // No point in selecting if selection is turned off
4992 if (DetermineDisplaySelection() == nsISelectionController::SELECTION_OFF) {
4993 return NS_OK;
4996 ContentOffsets offsets = GetContentOffsetsFromPoint(aPoint, SKIP_HIDDEN);
4997 if (!offsets.content) {
4998 return NS_ERROR_FAILURE;
5001 int32_t offset;
5002 nsIFrame* frame = nsFrameSelection::GetFrameForNodeOffset(
5003 offsets.content, offsets.offset, offsets.associate, &offset);
5004 if (!frame) {
5005 return NS_ERROR_FAILURE;
5007 return frame->PeekBackwardAndForward(aBeginAmountType, aEndAmountType, offset,
5008 aBeginAmountType != eSelectWord,
5009 aSelectFlags);
5013 * Multiple Mouse Press -- line or paragraph selection -- for the frame.
5014 * Wouldn't it be nice if this didn't have to be hardwired into Frame code?
5016 NS_IMETHODIMP
5017 nsIFrame::HandleMultiplePress(nsPresContext* aPresContext,
5018 WidgetGUIEvent* aEvent,
5019 nsEventStatus* aEventStatus, bool aControlHeld) {
5020 NS_ENSURE_ARG_POINTER(aEvent);
5021 NS_ENSURE_ARG_POINTER(aEventStatus);
5023 if (nsEventStatus_eConsumeNoDefault == *aEventStatus ||
5024 DetermineDisplaySelection() == nsISelectionController::SELECTION_OFF) {
5025 return NS_OK;
5028 // Find out whether we're doing line or paragraph selection.
5029 // If browser.triple_click_selects_paragraph is true, triple-click selects
5030 // paragraph. Otherwise, triple-click selects line, and quadruple-click
5031 // selects paragraph (on platforms that support quadruple-click).
5032 nsSelectionAmount beginAmount, endAmount;
5033 WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
5034 if (!mouseEvent) {
5035 return NS_OK;
5038 if (mouseEvent->mClickCount == 4) {
5039 beginAmount = endAmount = eSelectParagraph;
5040 } else if (mouseEvent->mClickCount == 3) {
5041 if (Preferences::GetBool("browser.triple_click_selects_paragraph")) {
5042 beginAmount = endAmount = eSelectParagraph;
5043 } else {
5044 beginAmount = eSelectBeginLine;
5045 endAmount = eSelectEndLine;
5047 } else if (mouseEvent->mClickCount == 2) {
5048 // We only want inline frames; PeekBackwardAndForward dislikes blocks
5049 beginAmount = endAmount = eSelectWord;
5050 } else {
5051 return NS_OK;
5054 nsPoint relPoint = nsLayoutUtils::GetEventCoordinatesRelativeTo(
5055 mouseEvent, RelativeTo{this});
5056 return SelectByTypeAtPoint(aPresContext, relPoint, beginAmount, endAmount,
5057 (aControlHeld ? SELECT_ACCUMULATE : 0));
5060 nsresult nsIFrame::PeekBackwardAndForward(nsSelectionAmount aAmountBack,
5061 nsSelectionAmount aAmountForward,
5062 int32_t aStartPos, bool aJumpLines,
5063 uint32_t aSelectFlags) {
5064 nsIFrame* baseFrame = this;
5065 int32_t baseOffset = aStartPos;
5066 nsresult rv;
5068 if (aAmountBack == eSelectWord) {
5069 // To avoid selecting the previous word when at start of word,
5070 // first move one character forward.
5071 nsPeekOffsetStruct pos(eSelectCharacter, eDirNext, aStartPos, nsPoint(0, 0),
5072 aJumpLines,
5073 true, // limit on scrolled views
5074 false, false, false);
5075 rv = PeekOffset(&pos);
5076 if (NS_SUCCEEDED(rv)) {
5077 baseFrame = pos.mResultFrame;
5078 baseOffset = pos.mContentOffset;
5082 // Search backward for a boundary.
5083 nsPeekOffsetStruct startpos(aAmountBack, eDirPrevious, baseOffset,
5084 nsPoint(0, 0), aJumpLines,
5085 true, // limit on scrolled views
5086 false, false, false);
5087 rv = baseFrame->PeekOffset(&startpos);
5088 if (NS_FAILED(rv)) {
5089 return rv;
5092 // If the backward search stayed within the same frame, search forward from
5093 // that position for the end boundary; but if it crossed out to a sibling or
5094 // ancestor, start from the original position.
5095 if (startpos.mResultFrame == baseFrame) {
5096 baseOffset = startpos.mContentOffset;
5097 } else {
5098 baseFrame = this;
5099 baseOffset = aStartPos;
5102 nsPeekOffsetStruct endpos(aAmountForward, eDirNext, baseOffset, nsPoint(0, 0),
5103 aJumpLines,
5104 true, // limit on scrolled views
5105 false, false, false);
5106 rv = baseFrame->PeekOffset(&endpos);
5107 if (NS_FAILED(rv)) {
5108 return rv;
5111 // Keep frameSelection alive.
5112 RefPtr<nsFrameSelection> frameSelection = GetFrameSelection();
5114 const nsFrameSelection::FocusMode focusMode =
5115 (aSelectFlags & SELECT_ACCUMULATE)
5116 ? nsFrameSelection::FocusMode::kMultiRangeSelection
5117 : nsFrameSelection::FocusMode::kCollapseToNewPoint;
5118 rv = frameSelection->HandleClick(
5119 MOZ_KnownLive(startpos.mResultContent) /* bug 1636889 */,
5120 startpos.mContentOffset, startpos.mContentOffset, focusMode,
5121 CARET_ASSOCIATE_AFTER);
5122 if (NS_FAILED(rv)) {
5123 return rv;
5126 rv = frameSelection->HandleClick(
5127 MOZ_KnownLive(endpos.mResultContent) /* bug 1636889 */,
5128 endpos.mContentOffset, endpos.mContentOffset,
5129 nsFrameSelection::FocusMode::kExtendSelection, CARET_ASSOCIATE_BEFORE);
5130 if (NS_FAILED(rv)) {
5131 return rv;
5134 // maintain selection
5135 return frameSelection->MaintainSelection(aAmountBack);
5138 NS_IMETHODIMP nsIFrame::HandleDrag(nsPresContext* aPresContext,
5139 WidgetGUIEvent* aEvent,
5140 nsEventStatus* aEventStatus) {
5141 MOZ_ASSERT(aEvent->mClass == eMouseEventClass,
5142 "HandleDrag can only handle mouse event");
5144 NS_ENSURE_ARG_POINTER(aEventStatus);
5146 RefPtr<nsFrameSelection> frameselection = GetFrameSelection();
5147 if (!frameselection) {
5148 return NS_OK;
5151 bool mouseDown = frameselection->GetDragState();
5152 if (!mouseDown) {
5153 return NS_OK;
5156 nsIFrame* scrollbar =
5157 nsLayoutUtils::GetClosestFrameOfType(this, LayoutFrameType::Scrollbar);
5158 if (!scrollbar) {
5159 // XXX Do we really need to exclude non-selectable content here?
5160 // GetContentOffsetsFromPoint can handle it just fine, although some
5161 // other stuff might not like it.
5162 // NOTE: DetermineDisplaySelection() returns SELECTION_OFF for
5163 // non-selectable frames.
5164 if (DetermineDisplaySelection() == nsISelectionController::SELECTION_OFF) {
5165 return NS_OK;
5169 frameselection->StopAutoScrollTimer();
5171 // Check if we are dragging in a table cell
5172 nsCOMPtr<nsIContent> parentContent;
5173 int32_t contentOffset;
5174 TableSelectionMode target;
5175 WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
5176 mozilla::PresShell* presShell = aPresContext->PresShell();
5177 nsresult result;
5178 result = GetDataForTableSelection(frameselection, presShell, mouseEvent,
5179 getter_AddRefs(parentContent),
5180 &contentOffset, &target);
5182 AutoWeakFrame weakThis = this;
5183 if (NS_SUCCEEDED(result) && parentContent) {
5184 result = frameselection->HandleTableSelection(parentContent, contentOffset,
5185 target, mouseEvent);
5186 if (NS_WARN_IF(NS_FAILED(result))) {
5187 return result;
5189 } else {
5190 nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(mouseEvent,
5191 RelativeTo{this});
5192 frameselection->HandleDrag(this, pt);
5195 // The frameselection object notifies selection listeners synchronously above
5196 // which might have killed us.
5197 if (!weakThis.IsAlive()) {
5198 return NS_OK;
5201 // get the nearest scrollframe
5202 nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetNearestScrollableFrame(
5203 this, nsLayoutUtils::SCROLLABLE_SAME_DOC |
5204 nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN);
5206 if (scrollFrame) {
5207 nsIFrame* capturingFrame = scrollFrame->GetScrolledFrame();
5208 if (capturingFrame) {
5209 nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(
5210 mouseEvent, RelativeTo{capturingFrame});
5211 frameselection->StartAutoScrollTimer(capturingFrame, pt, 30);
5215 return NS_OK;
5219 * This static method handles part of the nsIFrame::HandleRelease in a way
5220 * which doesn't rely on the nsFrame object to stay alive.
5222 MOZ_CAN_RUN_SCRIPT_BOUNDARY static nsresult HandleFrameSelection(
5223 nsFrameSelection* aFrameSelection, nsIFrame::ContentOffsets& aOffsets,
5224 bool aHandleTableSel, int32_t aContentOffsetForTableSel,
5225 TableSelectionMode aTargetForTableSel,
5226 nsIContent* aParentContentForTableSel, WidgetGUIEvent* aEvent,
5227 const nsEventStatus* aEventStatus) {
5228 if (!aFrameSelection) {
5229 return NS_OK;
5232 nsresult rv = NS_OK;
5234 if (nsEventStatus_eConsumeNoDefault != *aEventStatus) {
5235 if (!aHandleTableSel) {
5236 if (!aOffsets.content || !aFrameSelection->HasDelayedCaretData()) {
5237 return NS_ERROR_FAILURE;
5240 // We are doing this to simulate what we would have done on HandlePress.
5241 // We didn't do it there to give the user an opportunity to drag
5242 // the text, but since they didn't drag, we want to place the
5243 // caret.
5244 // However, we'll use the mouse position from the release, since:
5245 // * it's easier
5246 // * that's the normal click position to use (although really, in
5247 // the normal case, small movements that don't count as a drag
5248 // can do selection)
5249 aFrameSelection->SetDragState(true);
5251 const nsFrameSelection::FocusMode focusMode =
5252 aFrameSelection->IsShiftDownInDelayedCaretData()
5253 ? nsFrameSelection::FocusMode::kExtendSelection
5254 : nsFrameSelection::FocusMode::kCollapseToNewPoint;
5255 rv = aFrameSelection->HandleClick(
5256 MOZ_KnownLive(aOffsets.content) /* bug 1636889 */,
5257 aOffsets.StartOffset(), aOffsets.EndOffset(), focusMode,
5258 aOffsets.associate);
5259 if (NS_FAILED(rv)) {
5260 return rv;
5262 } else if (aParentContentForTableSel) {
5263 aFrameSelection->SetDragState(false);
5264 rv = aFrameSelection->HandleTableSelection(
5265 aParentContentForTableSel, aContentOffsetForTableSel,
5266 aTargetForTableSel, aEvent->AsMouseEvent());
5267 if (NS_FAILED(rv)) {
5268 return rv;
5271 aFrameSelection->SetDelayedCaretData(0);
5274 aFrameSelection->SetDragState(false);
5275 aFrameSelection->StopAutoScrollTimer();
5277 return NS_OK;
5280 NS_IMETHODIMP nsIFrame::HandleRelease(nsPresContext* aPresContext,
5281 WidgetGUIEvent* aEvent,
5282 nsEventStatus* aEventStatus) {
5283 if (aEvent->mClass != eMouseEventClass) {
5284 return NS_OK;
5287 nsIFrame* activeFrame = GetActiveSelectionFrame(aPresContext, this);
5289 nsCOMPtr<nsIContent> captureContent = PresShell::GetCapturingContent();
5291 bool selectionOff =
5292 (DetermineDisplaySelection() == nsISelectionController::SELECTION_OFF);
5294 RefPtr<nsFrameSelection> frameselection;
5295 ContentOffsets offsets;
5296 nsCOMPtr<nsIContent> parentContent;
5297 int32_t contentOffsetForTableSel = 0;
5298 TableSelectionMode targetForTableSel = TableSelectionMode::None;
5299 bool handleTableSelection = true;
5301 if (!selectionOff) {
5302 frameselection = GetFrameSelection();
5303 if (nsEventStatus_eConsumeNoDefault != *aEventStatus && frameselection) {
5304 // Check if the frameselection recorded the mouse going down.
5305 // If not, the user must have clicked in a part of the selection.
5306 // Place the caret before continuing!
5308 if (frameselection->MouseDownRecorded()) {
5309 nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(
5310 aEvent, RelativeTo{this});
5311 offsets = GetContentOffsetsFromPoint(pt, SKIP_HIDDEN);
5312 handleTableSelection = false;
5313 } else {
5314 GetDataForTableSelection(frameselection, PresShell(),
5315 aEvent->AsMouseEvent(),
5316 getter_AddRefs(parentContent),
5317 &contentOffsetForTableSel, &targetForTableSel);
5322 // We might be capturing in some other document and the event just happened to
5323 // trickle down here. Make sure that document's frame selection is notified.
5324 // Note, this may cause the current nsFrame object to be deleted, bug 336592.
5325 RefPtr<nsFrameSelection> frameSelection;
5326 if (activeFrame != this && activeFrame->DetermineDisplaySelection() !=
5327 nsISelectionController::SELECTION_OFF) {
5328 frameSelection = activeFrame->GetFrameSelection();
5331 // Also check the selection of the capturing content which might be in a
5332 // different document.
5333 if (!frameSelection && captureContent) {
5334 if (Document* doc = captureContent->GetComposedDoc()) {
5335 mozilla::PresShell* capturingPresShell = doc->GetPresShell();
5336 if (capturingPresShell &&
5337 capturingPresShell != PresContext()->GetPresShell()) {
5338 frameSelection = capturingPresShell->FrameSelection();
5343 if (frameSelection) {
5344 AutoWeakFrame wf(this);
5345 frameSelection->SetDragState(false);
5346 frameSelection->StopAutoScrollTimer();
5347 if (wf.IsAlive()) {
5348 nsIScrollableFrame* scrollFrame =
5349 nsLayoutUtils::GetNearestScrollableFrame(
5350 this, nsLayoutUtils::SCROLLABLE_SAME_DOC |
5351 nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN);
5352 if (scrollFrame) {
5353 // Perform any additional scrolling needed to maintain CSS snap point
5354 // requirements when autoscrolling is over.
5355 scrollFrame->ScrollSnap();
5360 // Do not call any methods of the current object after this point!!!
5361 // The object is perhaps dead!
5363 return selectionOff ? NS_OK
5364 : HandleFrameSelection(
5365 frameselection, offsets, handleTableSelection,
5366 contentOffsetForTableSel, targetForTableSel,
5367 parentContent, aEvent, aEventStatus);
5370 struct MOZ_STACK_CLASS FrameContentRange {
5371 FrameContentRange(nsIContent* aContent, int32_t aStart, int32_t aEnd)
5372 : content(aContent), start(aStart), end(aEnd) {}
5373 nsCOMPtr<nsIContent> content;
5374 int32_t start;
5375 int32_t end;
5378 // Retrieve the content offsets of a frame
5379 static FrameContentRange GetRangeForFrame(const nsIFrame* aFrame) {
5380 nsIContent* content = aFrame->GetContent();
5381 if (!content) {
5382 NS_WARNING("Frame has no content");
5383 return FrameContentRange(nullptr, -1, -1);
5386 LayoutFrameType type = aFrame->Type();
5387 if (type == LayoutFrameType::Text) {
5388 auto [offset, offsetEnd] = aFrame->GetOffsets();
5389 return FrameContentRange(content, offset, offsetEnd);
5392 if (type == LayoutFrameType::Br) {
5393 nsIContent* parent = content->GetParent();
5394 const int32_t beginOffset = parent->ComputeIndexOf_Deprecated(content);
5395 return FrameContentRange(parent, beginOffset, beginOffset);
5398 while (content->IsRootOfNativeAnonymousSubtree()) {
5399 content = content->GetParent();
5402 nsIContent* parent = content->GetParent();
5403 if (aFrame->IsBlockOutside() || !parent) {
5404 return FrameContentRange(content, 0, content->GetChildCount());
5407 // TODO(emilio): Revise this in presence of Shadow DOM / display: contents,
5408 // it's likely that we don't want to just walk the light tree, and we need to
5409 // change the representation of FrameContentRange.
5410 const int32_t index = parent->ComputeIndexOf_Deprecated(content);
5411 MOZ_ASSERT(index >= 0);
5412 return FrameContentRange(parent, index, index + 1);
5415 // The FrameTarget represents the closest frame to a point that can be selected
5416 // The frame is the frame represented, frameEdge says whether one end of the
5417 // frame is the result (in which case different handling is needed), and
5418 // afterFrame says which end is represented if frameEdge is true
5419 struct FrameTarget {
5420 explicit operator bool() const { return !!frame; }
5422 nsIFrame* frame = nullptr;
5423 bool frameEdge = false;
5424 bool afterFrame = false;
5427 // See function implementation for information
5428 static FrameTarget GetSelectionClosestFrame(nsIFrame* aFrame,
5429 const nsPoint& aPoint,
5430 uint32_t aFlags);
5432 static bool SelfIsSelectable(nsIFrame* aFrame, uint32_t aFlags) {
5433 if ((aFlags & nsIFrame::SKIP_HIDDEN) &&
5434 !aFrame->StyleVisibility()->IsVisible()) {
5435 return false;
5437 return !aFrame->IsGeneratedContentFrame() &&
5438 aFrame->Style()->UserSelect() != StyleUserSelect::None;
5441 static bool SelectionDescendToKids(nsIFrame* aFrame) {
5442 // If we are only near (not directly over) then don't traverse
5443 // frames with independent selection (e.g. text and list controls, see bug
5444 // 268497). Note that this prevents any of the users of this method from
5445 // entering form controls.
5446 // XXX We might want some way to allow using the up-arrow to go into a form
5447 // control, but the focus didn't work right anyway; it'd probably be enough
5448 // if the left and right arrows could enter textboxes (which I don't believe
5449 // they can at the moment)
5450 if (aFrame->IsTextInputFrame() || aFrame->IsListControlFrame()) {
5451 MOZ_ASSERT(aFrame->HasAnyStateBits(NS_FRAME_INDEPENDENT_SELECTION));
5452 return false;
5455 // Failure in this assertion means a new type of frame forms the root of an
5456 // NS_FRAME_INDEPENDENT_SELECTION subtree. In such case, the condition above
5457 // should be changed to handle it.
5458 MOZ_ASSERT_IF(
5459 aFrame->HasAnyStateBits(NS_FRAME_INDEPENDENT_SELECTION),
5460 aFrame->GetParent()->HasAnyStateBits(NS_FRAME_INDEPENDENT_SELECTION));
5462 if (aFrame->IsGeneratedContentFrame()) {
5463 return false;
5466 auto style = aFrame->Style()->UserSelect();
5467 return style != StyleUserSelect::All && style != StyleUserSelect::None;
5470 static FrameTarget GetSelectionClosestFrameForChild(nsIFrame* aChild,
5471 const nsPoint& aPoint,
5472 uint32_t aFlags) {
5473 nsIFrame* parent = aChild->GetParent();
5474 if (SelectionDescendToKids(aChild)) {
5475 nsPoint pt = aPoint - aChild->GetOffsetTo(parent);
5476 return GetSelectionClosestFrame(aChild, pt, aFlags);
5478 return FrameTarget{aChild, false, false};
5481 // When the cursor needs to be at the beginning of a block, it shouldn't be
5482 // before the first child. A click on a block whose first child is a block
5483 // should put the cursor in the child. The cursor shouldn't be between the
5484 // blocks, because that's not where it's expected.
5485 // Note that this method is guaranteed to succeed.
5486 static FrameTarget DrillDownToSelectionFrame(nsIFrame* aFrame, bool aEndFrame,
5487 uint32_t aFlags) {
5488 if (SelectionDescendToKids(aFrame)) {
5489 nsIFrame* result = nullptr;
5490 nsIFrame* frame = aFrame->PrincipalChildList().FirstChild();
5491 if (!aEndFrame) {
5492 while (frame && (!SelfIsSelectable(frame, aFlags) || frame->IsEmpty()))
5493 frame = frame->GetNextSibling();
5494 if (frame) result = frame;
5495 } else {
5496 // Because the frame tree is singly linked, to find the last frame,
5497 // we have to iterate through all the frames
5498 // XXX I have a feeling this could be slow for long blocks, although
5499 // I can't find any slowdowns
5500 while (frame) {
5501 if (!frame->IsEmpty() && SelfIsSelectable(frame, aFlags))
5502 result = frame;
5503 frame = frame->GetNextSibling();
5506 if (result) return DrillDownToSelectionFrame(result, aEndFrame, aFlags);
5508 // If the current frame has no targetable children, target the current frame
5509 return FrameTarget{aFrame, true, aEndFrame};
5512 // This method finds the closest valid FrameTarget on a given line; if there is
5513 // no valid FrameTarget on the line, it returns a null FrameTarget
5514 static FrameTarget GetSelectionClosestFrameForLine(
5515 nsBlockFrame* aParent, nsBlockFrame::LineIterator aLine,
5516 const nsPoint& aPoint, uint32_t aFlags) {
5517 // Account for end of lines (any iterator from the block is valid)
5518 if (aLine == aParent->LinesEnd())
5519 return DrillDownToSelectionFrame(aParent, true, aFlags);
5520 nsIFrame* frame = aLine->mFirstChild;
5521 nsIFrame* closestFromIStart = nullptr;
5522 nsIFrame* closestFromIEnd = nullptr;
5523 nscoord closestIStart = aLine->IStart(), closestIEnd = aLine->IEnd();
5524 WritingMode wm = aLine->mWritingMode;
5525 LogicalPoint pt(wm, aPoint, aLine->mContainerSize);
5526 bool canSkipBr = false;
5527 bool lastFrameWasEditable = false;
5528 for (int32_t n = aLine->GetChildCount(); n;
5529 --n, frame = frame->GetNextSibling()) {
5530 // Skip brFrames. Can only skip if the line contains at least
5531 // one selectable and non-empty frame before. Also, avoid skipping brs if
5532 // the previous thing had a different editableness than us, since then we
5533 // may end up not being able to select after it if the br is the last thing
5534 // on the line.
5535 if (!SelfIsSelectable(frame, aFlags) || frame->IsEmpty() ||
5536 (canSkipBr && frame->IsBrFrame() &&
5537 lastFrameWasEditable == frame->GetContent()->IsEditable())) {
5538 continue;
5540 canSkipBr = true;
5541 lastFrameWasEditable =
5542 frame->GetContent() && frame->GetContent()->IsEditable();
5543 LogicalRect frameRect =
5544 LogicalRect(wm, frame->GetRect(), aLine->mContainerSize);
5545 if (pt.I(wm) >= frameRect.IStart(wm)) {
5546 if (pt.I(wm) < frameRect.IEnd(wm)) {
5547 return GetSelectionClosestFrameForChild(frame, aPoint, aFlags);
5549 if (frameRect.IEnd(wm) >= closestIStart) {
5550 closestFromIStart = frame;
5551 closestIStart = frameRect.IEnd(wm);
5553 } else {
5554 if (frameRect.IStart(wm) <= closestIEnd) {
5555 closestFromIEnd = frame;
5556 closestIEnd = frameRect.IStart(wm);
5560 if (!closestFromIStart && !closestFromIEnd) {
5561 // We should only get here if there are no selectable frames on a line
5562 // XXX Do we need more elaborate handling here?
5563 return FrameTarget();
5565 if (closestFromIStart &&
5566 (!closestFromIEnd ||
5567 (abs(pt.I(wm) - closestIStart) <= abs(pt.I(wm) - closestIEnd)))) {
5568 return GetSelectionClosestFrameForChild(closestFromIStart, aPoint, aFlags);
5570 return GetSelectionClosestFrameForChild(closestFromIEnd, aPoint, aFlags);
5573 // This method is for the special handling we do for block frames; they're
5574 // special because they represent paragraphs and because they are organized
5575 // into lines, which have bounds that are not stored elsewhere in the
5576 // frame tree. Returns a null FrameTarget for frames which are not
5577 // blocks or blocks with no lines except editable one.
5578 static FrameTarget GetSelectionClosestFrameForBlock(nsIFrame* aFrame,
5579 const nsPoint& aPoint,
5580 uint32_t aFlags) {
5581 nsBlockFrame* bf = do_QueryFrame(aFrame);
5582 if (!bf) {
5583 return FrameTarget();
5586 // This code searches for the correct line
5587 nsBlockFrame::LineIterator end = bf->LinesEnd();
5588 nsBlockFrame::LineIterator curLine = bf->LinesBegin();
5589 nsBlockFrame::LineIterator closestLine = end;
5591 if (curLine != end) {
5592 // Convert aPoint into a LogicalPoint in the writing-mode of this block
5593 WritingMode wm = curLine->mWritingMode;
5594 LogicalPoint pt(wm, aPoint, curLine->mContainerSize);
5595 do {
5596 // Check to see if our point lies within the line's block-direction bounds
5597 nscoord BCoord = pt.B(wm) - curLine->BStart();
5598 nscoord BSize = curLine->BSize();
5599 if (BCoord >= 0 && BCoord < BSize) {
5600 closestLine = curLine;
5601 break; // We found the line; stop looking
5603 if (BCoord < 0) break;
5604 ++curLine;
5605 } while (curLine != end);
5607 if (closestLine == end) {
5608 nsBlockFrame::LineIterator prevLine = curLine.prev();
5609 nsBlockFrame::LineIterator nextLine = curLine;
5610 // Avoid empty lines
5611 while (nextLine != end && nextLine->IsEmpty()) ++nextLine;
5612 while (prevLine != end && prevLine->IsEmpty()) --prevLine;
5614 // This hidden pref dictates whether a point above or below all lines
5615 // comes up with a line or the beginning or end of the frame; 0 on
5616 // Windows, 1 on other platforms by default at the writing of this code
5617 int32_t dragOutOfFrame =
5618 Preferences::GetInt("browser.drag_out_of_frame_style");
5620 if (prevLine == end) {
5621 if (dragOutOfFrame == 1 || nextLine == end)
5622 return DrillDownToSelectionFrame(aFrame, false, aFlags);
5623 closestLine = nextLine;
5624 } else if (nextLine == end) {
5625 if (dragOutOfFrame == 1)
5626 return DrillDownToSelectionFrame(aFrame, true, aFlags);
5627 closestLine = prevLine;
5628 } else { // Figure out which line is closer
5629 if (pt.B(wm) - prevLine->BEnd() < nextLine->BStart() - pt.B(wm))
5630 closestLine = prevLine;
5631 else
5632 closestLine = nextLine;
5637 do {
5638 if (auto target =
5639 GetSelectionClosestFrameForLine(bf, closestLine, aPoint, aFlags)) {
5640 return target;
5642 ++closestLine;
5643 } while (closestLine != end);
5645 // Fall back to just targeting the last targetable place
5646 return DrillDownToSelectionFrame(aFrame, true, aFlags);
5649 // Use frame edge for grid, flex, table, and non-editable images. Choose the
5650 // edge based on the point position past the frame rect. If past the middle,
5651 // caret should be at end, otherwise at start. This behavior matches Blink.
5653 // TODO(emilio): Can we use this code path for other replaced elements other
5654 // than images? Or even all other frames? We only get there when we didn't find
5655 // selectable children... At least one XUL test fails if we make this apply to
5656 // XUL labels. Also, editable images need _not_ to use the frame edge, see
5657 // below.
5658 static bool UseFrameEdge(nsIFrame* aFrame) {
5659 if (aFrame->IsFlexOrGridContainer() || aFrame->IsTableFrame()) {
5660 return true;
5662 const nsImageFrame* image = do_QueryFrame(aFrame);
5663 if (image && !aFrame->GetContent()->IsEditable()) {
5664 // Editable images are a special-case because editing relies on clicking on
5665 // an editable image selecting it, for it to show resizers.
5666 return true;
5668 return false;
5671 static FrameTarget LastResortFrameTargetForFrame(nsIFrame* aFrame,
5672 const nsPoint& aPoint) {
5673 if (!UseFrameEdge(aFrame)) {
5674 return {aFrame, false, false};
5676 const auto& rect = aFrame->GetRectRelativeToSelf();
5677 nscoord reference;
5678 nscoord middle;
5679 if (aFrame->GetWritingMode().IsVertical()) {
5680 reference = aPoint.y;
5681 middle = rect.Height() / 2;
5682 } else {
5683 reference = aPoint.x;
5684 middle = rect.Width() / 2;
5686 const bool afterFrame = reference > middle;
5687 return {aFrame, true, afterFrame};
5690 // GetSelectionClosestFrame is the helper function that calculates the closest
5691 // frame to the given point.
5692 // It doesn't completely account for offset styles, so needs to be used in
5693 // restricted environments.
5694 // Cannot handle overlapping frames correctly, so it should receive the output
5695 // of GetFrameForPoint
5696 // Guaranteed to return a valid FrameTarget.
5697 // aPoint is relative to aFrame.
5698 static FrameTarget GetSelectionClosestFrame(nsIFrame* aFrame,
5699 const nsPoint& aPoint,
5700 uint32_t aFlags) {
5701 // Handle blocks; if the frame isn't a block, the method fails
5702 if (auto target = GetSelectionClosestFrameForBlock(aFrame, aPoint, aFlags)) {
5703 return target;
5706 if (nsIFrame* kid = aFrame->PrincipalChildList().FirstChild()) {
5707 // Go through all the child frames to find the closest one
5708 nsIFrame::FrameWithDistance closest = {nullptr, nscoord_MAX, nscoord_MAX};
5709 for (; kid; kid = kid->GetNextSibling()) {
5710 if (!SelfIsSelectable(kid, aFlags) || kid->IsEmpty()) continue;
5712 kid->FindCloserFrameForSelection(aPoint, &closest);
5714 if (closest.mFrame) {
5715 if (SVGUtils::IsInSVGTextSubtree(closest.mFrame))
5716 return FrameTarget{closest.mFrame, false, false};
5717 return GetSelectionClosestFrameForChild(closest.mFrame, aPoint, aFlags);
5721 return LastResortFrameTargetForFrame(aFrame, aPoint);
5724 static nsIFrame::ContentOffsets OffsetsForSingleFrame(nsIFrame* aFrame,
5725 const nsPoint& aPoint) {
5726 nsIFrame::ContentOffsets offsets;
5727 FrameContentRange range = GetRangeForFrame(aFrame);
5728 offsets.content = range.content;
5729 // If there are continuations (meaning it's not one rectangle), this is the
5730 // best this function can do
5731 if (aFrame->GetNextContinuation() || aFrame->GetPrevContinuation()) {
5732 offsets.offset = range.start;
5733 offsets.secondaryOffset = range.end;
5734 offsets.associate = CARET_ASSOCIATE_AFTER;
5735 return offsets;
5738 // Figure out whether the offsets should be over, after, or before the frame
5739 nsRect rect(nsPoint(0, 0), aFrame->GetSize());
5741 bool isBlock = !aFrame->StyleDisplay()->IsInlineFlow();
5742 bool isRtl = (aFrame->StyleVisibility()->mDirection == StyleDirection::Rtl);
5743 if ((isBlock && rect.y < aPoint.y) ||
5744 (!isBlock && ((isRtl && rect.x + rect.width / 2 > aPoint.x) ||
5745 (!isRtl && rect.x + rect.width / 2 < aPoint.x)))) {
5746 offsets.offset = range.end;
5747 if (rect.Contains(aPoint))
5748 offsets.secondaryOffset = range.start;
5749 else
5750 offsets.secondaryOffset = range.end;
5751 } else {
5752 offsets.offset = range.start;
5753 if (rect.Contains(aPoint))
5754 offsets.secondaryOffset = range.end;
5755 else
5756 offsets.secondaryOffset = range.start;
5758 offsets.associate = offsets.offset == range.start ? CARET_ASSOCIATE_AFTER
5759 : CARET_ASSOCIATE_BEFORE;
5760 return offsets;
5763 static nsIFrame* AdjustFrameForSelectionStyles(nsIFrame* aFrame) {
5764 nsIFrame* adjustedFrame = aFrame;
5765 for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) {
5766 // These are the conditions that make all children not able to handle
5767 // a cursor.
5768 auto userSelect = frame->Style()->UserSelect();
5769 if (userSelect != StyleUserSelect::Auto &&
5770 userSelect != StyleUserSelect::All) {
5771 break;
5773 if (userSelect == StyleUserSelect::All ||
5774 frame->IsGeneratedContentFrame()) {
5775 adjustedFrame = frame;
5778 return adjustedFrame;
5781 nsIFrame::ContentOffsets nsIFrame::GetContentOffsetsFromPoint(
5782 const nsPoint& aPoint, uint32_t aFlags) {
5783 nsIFrame* adjustedFrame;
5784 if (aFlags & IGNORE_SELECTION_STYLE) {
5785 adjustedFrame = this;
5786 } else {
5787 // This section of code deals with special selection styles. Note that
5788 // -moz-all exists, even though it doesn't need to be explicitly handled.
5790 // The offset is forced not to end up in generated content; content offsets
5791 // cannot represent content outside of the document's content tree.
5793 adjustedFrame = AdjustFrameForSelectionStyles(this);
5795 // `user-select: all` needs special handling, because clicking on it should
5796 // lead to the whole frame being selected.
5797 if (adjustedFrame->Style()->UserSelect() == StyleUserSelect::All) {
5798 nsPoint adjustedPoint = aPoint + GetOffsetTo(adjustedFrame);
5799 return OffsetsForSingleFrame(adjustedFrame, adjustedPoint);
5802 // For other cases, try to find a closest frame starting from the parent of
5803 // the unselectable frame
5804 if (adjustedFrame != this) {
5805 adjustedFrame = adjustedFrame->GetParent();
5809 nsPoint adjustedPoint = aPoint + GetOffsetTo(adjustedFrame);
5811 FrameTarget closest =
5812 GetSelectionClosestFrame(adjustedFrame, adjustedPoint, aFlags);
5814 // If the correct offset is at one end of a frame, use offset-based
5815 // calculation method
5816 if (closest.frameEdge) {
5817 ContentOffsets offsets;
5818 FrameContentRange range = GetRangeForFrame(closest.frame);
5819 offsets.content = range.content;
5820 if (closest.afterFrame)
5821 offsets.offset = range.end;
5822 else
5823 offsets.offset = range.start;
5824 offsets.secondaryOffset = offsets.offset;
5825 offsets.associate = offsets.offset == range.start ? CARET_ASSOCIATE_AFTER
5826 : CARET_ASSOCIATE_BEFORE;
5827 return offsets;
5830 nsPoint pt;
5831 if (closest.frame != this) {
5832 if (SVGUtils::IsInSVGTextSubtree(closest.frame)) {
5833 pt = nsLayoutUtils::TransformAncestorPointToFrame(
5834 RelativeTo{closest.frame}, aPoint, RelativeTo{this});
5835 } else {
5836 pt = aPoint - closest.frame->GetOffsetTo(this);
5838 } else {
5839 pt = aPoint;
5841 return closest.frame->CalcContentOffsetsFromFramePoint(pt);
5843 // XXX should I add some kind of offset standardization?
5844 // consider <b>xxxxx</b><i>zzzzz</i>; should any click between the last
5845 // x and first z put the cursor in the same logical position in addition
5846 // to the same visual position?
5849 nsIFrame::ContentOffsets nsIFrame::CalcContentOffsetsFromFramePoint(
5850 const nsPoint& aPoint) {
5851 return OffsetsForSingleFrame(this, aPoint);
5854 bool nsIFrame::AssociateImage(const StyleImage& aImage) {
5855 imgRequestProxy* req = aImage.GetImageRequest();
5856 if (!req) {
5857 return false;
5860 mozilla::css::ImageLoader* loader =
5861 PresContext()->Document()->StyleImageLoader();
5863 loader->AssociateRequestToFrame(req, this);
5864 return true;
5867 void nsIFrame::DisassociateImage(const StyleImage& aImage) {
5868 imgRequestProxy* req = aImage.GetImageRequest();
5869 if (!req) {
5870 return;
5873 mozilla::css::ImageLoader* loader =
5874 PresContext()->Document()->StyleImageLoader();
5876 loader->DisassociateRequestFromFrame(req, this);
5879 StyleImageRendering nsIFrame::UsedImageRendering() const {
5880 ComputedStyle* style;
5881 if (nsCSSRendering::IsCanvasFrame(this)) {
5882 nsCSSRendering::FindBackground(this, &style);
5883 } else {
5884 style = Style();
5886 return style->StyleVisibility()->mImageRendering;
5889 // The touch-action CSS property applies to: all elements except: non-replaced
5890 // inline elements, table rows, row groups, table columns, and column groups.
5891 StyleTouchAction nsIFrame::UsedTouchAction() const {
5892 if (IsFrameOfType(eLineParticipant)) {
5893 return StyleTouchAction::AUTO;
5895 auto& disp = *StyleDisplay();
5896 if (disp.IsInternalTableStyleExceptCell()) {
5897 return StyleTouchAction::AUTO;
5899 return disp.mTouchAction;
5902 Maybe<nsIFrame::Cursor> nsIFrame::GetCursor(const nsPoint&) {
5903 StyleCursorKind kind = StyleUI()->Cursor().keyword;
5904 if (kind == StyleCursorKind::Auto) {
5905 // If this is editable, I-beam cursor is better for most elements.
5906 kind = (mContent && mContent->IsEditable()) ? StyleCursorKind::Text
5907 : StyleCursorKind::Default;
5909 if (kind == StyleCursorKind::Text && GetWritingMode().IsVertical()) {
5910 // Per CSS UI spec, UA may treat value 'text' as
5911 // 'vertical-text' for vertical text.
5912 kind = StyleCursorKind::VerticalText;
5915 return Some(Cursor{kind, AllowCustomCursorImage::Yes});
5918 // Resize and incremental reflow
5920 /* virtual */
5921 void nsIFrame::MarkIntrinsicISizesDirty() {
5922 // This version is meant only for what used to be box-to-block adaptors.
5923 // It should not be called by other derived classes.
5924 if (::IsXULBoxWrapped(this)) {
5925 nsBoxLayoutMetrics* metrics = BoxMetrics();
5927 XULSizeNeedsRecalc(metrics->mPrefSize);
5928 XULSizeNeedsRecalc(metrics->mMinSize);
5929 XULSizeNeedsRecalc(metrics->mMaxSize);
5930 XULSizeNeedsRecalc(metrics->mBlockPrefSize);
5931 XULSizeNeedsRecalc(metrics->mBlockMinSize);
5932 XULCoordNeedsRecalc(metrics->mFlex);
5933 XULCoordNeedsRecalc(metrics->mAscent);
5936 // If we're a flex item, clear our flex-item-specific cached measurements
5937 // (which likely depended on our now-stale intrinsic isize).
5938 if (IsFlexItem()) {
5939 nsFlexContainerFrame::MarkCachedFlexMeasurementsDirty(this);
5942 if (HasAnyStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT)) {
5943 nsFontInflationData::MarkFontInflationDataTextDirty(this);
5946 if (StaticPrefs::layout_css_grid_item_baxis_measurement_enabled()) {
5947 RemoveProperty(nsGridContainerFrame::CachedBAxisMeasurement::Prop());
5951 void nsIFrame::MarkSubtreeDirty() {
5952 if (HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
5953 return;
5955 // Unconditionally mark given frame dirty.
5956 AddStateBits(NS_FRAME_IS_DIRTY);
5958 // Mark all descendants dirty, unless:
5959 // - Already dirty.
5960 // - TableColGroup
5961 // - XULBox
5962 AutoTArray<nsIFrame*, 32> stack;
5963 for (const auto& childLists : ChildLists()) {
5964 for (nsIFrame* kid : childLists.mList) {
5965 stack.AppendElement(kid);
5968 while (!stack.IsEmpty()) {
5969 nsIFrame* f = stack.PopLastElement();
5970 if (f->HasAnyStateBits(NS_FRAME_IS_DIRTY) || f->IsTableColGroupFrame() ||
5971 f->IsXULBoxFrame()) {
5972 continue;
5975 f->AddStateBits(NS_FRAME_IS_DIRTY);
5977 for (const auto& childLists : f->ChildLists()) {
5978 for (nsIFrame* kid : childLists.mList) {
5979 stack.AppendElement(kid);
5985 /* virtual */
5986 nscoord nsIFrame::GetMinISize(gfxContext* aRenderingContext) {
5987 nscoord result = 0;
5988 DISPLAY_MIN_INLINE_SIZE(this, result);
5989 return result;
5992 /* virtual */
5993 nscoord nsIFrame::GetPrefISize(gfxContext* aRenderingContext) {
5994 nscoord result = 0;
5995 DISPLAY_PREF_INLINE_SIZE(this, result);
5996 return result;
5999 /* virtual */
6000 void nsIFrame::AddInlineMinISize(gfxContext* aRenderingContext,
6001 nsIFrame::InlineMinISizeData* aData) {
6002 nscoord isize = nsLayoutUtils::IntrinsicForContainer(
6003 aRenderingContext, this, IntrinsicISizeType::MinISize);
6004 aData->DefaultAddInlineMinISize(this, isize);
6007 /* virtual */
6008 void nsIFrame::AddInlinePrefISize(gfxContext* aRenderingContext,
6009 nsIFrame::InlinePrefISizeData* aData) {
6010 nscoord isize = nsLayoutUtils::IntrinsicForContainer(
6011 aRenderingContext, this, IntrinsicISizeType::PrefISize);
6012 aData->DefaultAddInlinePrefISize(isize);
6015 void nsIFrame::InlineMinISizeData::DefaultAddInlineMinISize(nsIFrame* aFrame,
6016 nscoord aISize,
6017 bool aAllowBreak) {
6018 auto parent = aFrame->GetParent();
6019 MOZ_ASSERT(parent, "Must have a parent if we get here!");
6020 const bool mayBreak = aAllowBreak && !aFrame->CanContinueTextRun() &&
6021 !parent->Style()->ShouldSuppressLineBreak() &&
6022 parent->StyleText()->WhiteSpaceCanWrap(parent);
6023 if (mayBreak) {
6024 OptionallyBreak();
6026 mTrailingWhitespace = 0;
6027 mSkipWhitespace = false;
6028 mCurrentLine += aISize;
6029 mAtStartOfLine = false;
6030 if (mayBreak) {
6031 OptionallyBreak();
6035 void nsIFrame::InlinePrefISizeData::DefaultAddInlinePrefISize(nscoord aISize) {
6036 mCurrentLine = NSCoordSaturatingAdd(mCurrentLine, aISize);
6037 mTrailingWhitespace = 0;
6038 mSkipWhitespace = false;
6039 mLineIsEmpty = false;
6042 void nsIFrame::InlineMinISizeData::ForceBreak() {
6043 mCurrentLine -= mTrailingWhitespace;
6044 mPrevLines = std::max(mPrevLines, mCurrentLine);
6045 mCurrentLine = mTrailingWhitespace = 0;
6047 for (uint32_t i = 0, i_end = mFloats.Length(); i != i_end; ++i) {
6048 nscoord float_min = mFloats[i].Width();
6049 if (float_min > mPrevLines) mPrevLines = float_min;
6051 mFloats.Clear();
6052 mSkipWhitespace = true;
6055 void nsIFrame::InlineMinISizeData::OptionallyBreak(nscoord aHyphenWidth) {
6056 // If we can fit more content into a smaller width by staying on this
6057 // line (because we're still at a negative offset due to negative
6058 // text-indent or negative margin), don't break. Otherwise, do the
6059 // same as ForceBreak. it doesn't really matter when we accumulate
6060 // floats.
6061 if (mCurrentLine + aHyphenWidth < 0 || mAtStartOfLine) return;
6062 mCurrentLine += aHyphenWidth;
6063 ForceBreak();
6066 void nsIFrame::InlinePrefISizeData::ForceBreak(StyleClear aBreakType) {
6067 MOZ_ASSERT(aBreakType == StyleClear::None || aBreakType == StyleClear::Both ||
6068 aBreakType == StyleClear::Left ||
6069 aBreakType == StyleClear::Right,
6070 "Must be a physical break type");
6072 // If this force break is not clearing any float, we can leave all the
6073 // floats to the next force break.
6074 if (mFloats.Length() != 0 && aBreakType != StyleClear::None) {
6075 // preferred widths accumulated for floats that have already
6076 // been cleared past
6077 nscoord floats_done = 0,
6078 // preferred widths accumulated for floats that have not yet
6079 // been cleared past
6080 floats_cur_left = 0, floats_cur_right = 0;
6082 for (uint32_t i = 0, i_end = mFloats.Length(); i != i_end; ++i) {
6083 const FloatInfo& floatInfo = mFloats[i];
6084 const nsStyleDisplay* floatDisp = floatInfo.Frame()->StyleDisplay();
6085 StyleClear breakType = floatDisp->mBreakType;
6086 if (breakType == StyleClear::Left || breakType == StyleClear::Right ||
6087 breakType == StyleClear::Both) {
6088 nscoord floats_cur =
6089 NSCoordSaturatingAdd(floats_cur_left, floats_cur_right);
6090 if (floats_cur > floats_done) {
6091 floats_done = floats_cur;
6093 if (breakType != StyleClear::Right) {
6094 floats_cur_left = 0;
6096 if (breakType != StyleClear::Left) {
6097 floats_cur_right = 0;
6101 StyleFloat floatStyle = floatDisp->mFloat;
6102 nscoord& floats_cur =
6103 floatStyle == StyleFloat::Left ? floats_cur_left : floats_cur_right;
6104 nscoord floatWidth = floatInfo.Width();
6105 // Negative-width floats don't change the available space so they
6106 // shouldn't change our intrinsic line width either.
6107 floats_cur = NSCoordSaturatingAdd(floats_cur, std::max(0, floatWidth));
6110 nscoord floats_cur =
6111 NSCoordSaturatingAdd(floats_cur_left, floats_cur_right);
6112 if (floats_cur > floats_done) floats_done = floats_cur;
6114 mCurrentLine = NSCoordSaturatingAdd(mCurrentLine, floats_done);
6116 if (aBreakType == StyleClear::Both) {
6117 mFloats.Clear();
6118 } else {
6119 // If the break type does not clear all floats, it means there may
6120 // be some floats whose isize should contribute to the intrinsic
6121 // isize of the next line. The code here scans the current mFloats
6122 // and keeps floats which are not cleared by this break. Note that
6123 // floats may be cleared directly or indirectly. See below.
6124 nsTArray<FloatInfo> newFloats;
6125 MOZ_ASSERT(
6126 aBreakType == StyleClear::Left || aBreakType == StyleClear::Right,
6127 "Other values should have been handled in other branches");
6128 StyleFloat clearFloatType =
6129 aBreakType == StyleClear::Left ? StyleFloat::Left : StyleFloat::Right;
6130 // Iterate the array in reverse so that we can stop when there are
6131 // no longer any floats we need to keep. See below.
6132 for (FloatInfo& floatInfo : Reversed(mFloats)) {
6133 const nsStyleDisplay* floatDisp = floatInfo.Frame()->StyleDisplay();
6134 if (floatDisp->mFloat != clearFloatType) {
6135 newFloats.AppendElement(floatInfo);
6136 } else {
6137 // This is a float on the side that this break directly clears
6138 // which means we're not keeping it in mFloats. However, if
6139 // this float clears floats on the opposite side (via a value
6140 // of either 'both' or one of 'left'/'right'), any remaining
6141 // (earlier) floats on that side would be indirectly cleared
6142 // as well. Thus, we should break out of this loop and stop
6143 // considering earlier floats to be kept in mFloats.
6144 StyleClear floatBreakType = floatDisp->mBreakType;
6145 if (floatBreakType != aBreakType &&
6146 floatBreakType != StyleClear::None) {
6147 break;
6151 newFloats.Reverse();
6152 mFloats = std::move(newFloats);
6156 mCurrentLine =
6157 NSCoordSaturatingSubtract(mCurrentLine, mTrailingWhitespace, nscoord_MAX);
6158 mPrevLines = std::max(mPrevLines, mCurrentLine);
6159 mCurrentLine = mTrailingWhitespace = 0;
6160 mSkipWhitespace = true;
6161 mLineIsEmpty = true;
6164 static nscoord ResolveMargin(const LengthPercentageOrAuto& aStyle,
6165 nscoord aPercentageBasis) {
6166 if (aStyle.IsAuto()) {
6167 return nscoord(0);
6169 return nsLayoutUtils::ResolveToLength<false>(aStyle.AsLengthPercentage(),
6170 aPercentageBasis);
6173 static nscoord ResolvePadding(const LengthPercentage& aStyle,
6174 nscoord aPercentageBasis) {
6175 return nsLayoutUtils::ResolveToLength<true>(aStyle, aPercentageBasis);
6178 static nsIFrame::IntrinsicSizeOffsetData IntrinsicSizeOffsets(
6179 nsIFrame* aFrame, nscoord aPercentageBasis, bool aForISize) {
6180 nsIFrame::IntrinsicSizeOffsetData result;
6181 WritingMode wm = aFrame->GetWritingMode();
6182 const auto& margin = aFrame->StyleMargin()->mMargin;
6183 bool verticalAxis = aForISize == wm.IsVertical();
6184 if (verticalAxis) {
6185 result.margin += ResolveMargin(margin.Get(eSideTop), aPercentageBasis);
6186 result.margin += ResolveMargin(margin.Get(eSideBottom), aPercentageBasis);
6187 } else {
6188 result.margin += ResolveMargin(margin.Get(eSideLeft), aPercentageBasis);
6189 result.margin += ResolveMargin(margin.Get(eSideRight), aPercentageBasis);
6192 const auto& padding = aFrame->StylePadding()->mPadding;
6193 if (verticalAxis) {
6194 result.padding += ResolvePadding(padding.Get(eSideTop), aPercentageBasis);
6195 result.padding +=
6196 ResolvePadding(padding.Get(eSideBottom), aPercentageBasis);
6197 } else {
6198 result.padding += ResolvePadding(padding.Get(eSideLeft), aPercentageBasis);
6199 result.padding += ResolvePadding(padding.Get(eSideRight), aPercentageBasis);
6202 const nsStyleBorder* styleBorder = aFrame->StyleBorder();
6203 if (verticalAxis) {
6204 result.border += styleBorder->GetComputedBorderWidth(eSideTop);
6205 result.border += styleBorder->GetComputedBorderWidth(eSideBottom);
6206 } else {
6207 result.border += styleBorder->GetComputedBorderWidth(eSideLeft);
6208 result.border += styleBorder->GetComputedBorderWidth(eSideRight);
6211 const nsStyleDisplay* disp = aFrame->StyleDisplay();
6212 if (aFrame->IsThemed(disp)) {
6213 nsPresContext* presContext = aFrame->PresContext();
6215 LayoutDeviceIntMargin border = presContext->Theme()->GetWidgetBorder(
6216 presContext->DeviceContext(), aFrame, disp->EffectiveAppearance());
6217 result.border = presContext->DevPixelsToAppUnits(
6218 verticalAxis ? border.TopBottom() : border.LeftRight());
6220 LayoutDeviceIntMargin padding;
6221 if (presContext->Theme()->GetWidgetPadding(
6222 presContext->DeviceContext(), aFrame, disp->EffectiveAppearance(),
6223 &padding)) {
6224 result.padding = presContext->DevPixelsToAppUnits(
6225 verticalAxis ? padding.TopBottom() : padding.LeftRight());
6228 return result;
6231 /* virtual */ nsIFrame::IntrinsicSizeOffsetData nsIFrame::IntrinsicISizeOffsets(
6232 nscoord aPercentageBasis) {
6233 return IntrinsicSizeOffsets(this, aPercentageBasis, true);
6236 nsIFrame::IntrinsicSizeOffsetData nsIFrame::IntrinsicBSizeOffsets(
6237 nscoord aPercentageBasis) {
6238 return IntrinsicSizeOffsets(this, aPercentageBasis, false);
6241 /* virtual */
6242 IntrinsicSize nsIFrame::GetIntrinsicSize() {
6243 return IntrinsicSize(); // default is width/height set to eStyleUnit_None
6246 AspectRatio nsIFrame::GetAspectRatio() const {
6247 // Per spec, 'aspect-ratio' property applies to all elements except inline
6248 // boxes and internal ruby or table boxes.
6249 // https://drafts.csswg.org/css-sizing-4/#aspect-ratio
6250 // For those frame types that don't support aspect-ratio, they must not have
6251 // the natural ratio, so this early return is fine.
6252 if (!IsFrameOfType(eSupportsAspectRatio)) {
6253 return AspectRatio();
6256 const StyleAspectRatio& aspectRatio = StylePosition()->mAspectRatio;
6257 // If aspect-ratio is zero or infinite, it's a degenerate ratio and behaves
6258 // as auto.
6259 // https://drafts.csswg.org/css-sizing-4/#valdef-aspect-ratio-ratio
6260 if (!aspectRatio.BehavesAsAuto()) {
6261 // Non-auto. Return the preferred aspect ratio from the aspect-ratio style.
6262 return aspectRatio.ratio.AsRatio().ToLayoutRatio(UseBoxSizing::Yes);
6265 // The rest of the cases are when aspect-ratio has 'auto'.
6266 if (auto intrinsicRatio = GetIntrinsicRatio()) {
6267 return intrinsicRatio;
6270 if (aspectRatio.HasRatio()) {
6271 // If it's a degenerate ratio, this returns 0. Just the same as the auto
6272 // case.
6273 return aspectRatio.ratio.AsRatio().ToLayoutRatio(UseBoxSizing::No);
6276 return AspectRatio();
6279 /* virtual */
6280 AspectRatio nsIFrame::GetIntrinsicRatio() const { return AspectRatio(); }
6282 static bool ShouldApplyAutomaticMinimumOnInlineAxis(
6283 WritingMode aWM, const nsStyleDisplay* aDisplay,
6284 const nsStylePosition* aPosition) {
6285 // Apply the automatic minimum size for aspect ratio:
6286 // Note: The replaced elements shouldn't be here, so we only check the scroll
6287 // container.
6288 // https://drafts.csswg.org/css-sizing-4/#aspect-ratio-minimum
6289 return !aDisplay->IsScrollableOverflow() && aPosition->MinISize(aWM).IsAuto();
6292 struct MinMaxSize {
6293 nscoord mMinSize = 0;
6294 nscoord mMaxSize = NS_UNCONSTRAINEDSIZE;
6296 nscoord ClampSizeToMinAndMax(nscoord aSize) const {
6297 return NS_CSS_MINMAX(aSize, mMinSize, mMaxSize);
6300 static MinMaxSize ComputeTransferredMinMaxInlineSize(
6301 const WritingMode aWM, const AspectRatio& aAspectRatio,
6302 const MinMaxSize& aMinMaxBSize, const LogicalSize& aBoxSizingAdjustment) {
6303 // Note: the spec mentions that
6304 // 1. This transferred minimum is capped by any definite preferred or maximum
6305 // size in the destination axis.
6306 // 2. This transferred maximum is floored by any definite preferred or minimum
6307 // size in the destination axis
6309 // https://drafts.csswg.org/css-sizing-4/#aspect-ratio
6311 // The spec requires us to clamp these by the specified size (it calls it the
6312 // preferred size). However, we actually don't need to worry about that,
6313 // because we only use this if the inline size is indefinite.
6315 // We do not need to clamp the transferred minimum and maximum as long as we
6316 // always apply the transferred min/max size before the explicit min/max size,
6317 // the result will be identical.
6319 MinMaxSize transferredISize;
6321 if (aMinMaxBSize.mMinSize > 0) {
6322 transferredISize.mMinSize = aAspectRatio.ComputeRatioDependentSize(
6323 LogicalAxis::eLogicalAxisInline, aWM, aMinMaxBSize.mMinSize,
6324 aBoxSizingAdjustment);
6327 if (aMinMaxBSize.mMaxSize != NS_UNCONSTRAINEDSIZE) {
6328 transferredISize.mMaxSize = aAspectRatio.ComputeRatioDependentSize(
6329 LogicalAxis::eLogicalAxisInline, aWM, aMinMaxBSize.mMaxSize,
6330 aBoxSizingAdjustment);
6333 // Minimum size wins over maximum size.
6334 transferredISize.mMaxSize =
6335 std::max(transferredISize.mMinSize, transferredISize.mMaxSize);
6336 return transferredISize;
6339 /* virtual */
6340 nsIFrame::SizeComputationResult nsIFrame::ComputeSize(
6341 gfxContext* aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize,
6342 nscoord aAvailableISize, const LogicalSize& aMargin,
6343 const LogicalSize& aBorderPadding, const StyleSizeOverrides& aSizeOverrides,
6344 ComputeSizeFlags aFlags) {
6345 MOZ_ASSERT(!GetIntrinsicRatio(),
6346 "Please override this method and call "
6347 "nsContainerFrame::ComputeSizeWithIntrinsicDimensions instead.");
6348 LogicalSize result =
6349 ComputeAutoSize(aRenderingContext, aWM, aCBSize, aAvailableISize, aMargin,
6350 aBorderPadding, aSizeOverrides, aFlags);
6351 const nsStylePosition* stylePos = StylePosition();
6352 const nsStyleDisplay* disp = StyleDisplay();
6353 auto aspectRatioUsage = AspectRatioUsage::None;
6355 const auto boxSizingAdjust = stylePos->mBoxSizing == StyleBoxSizing::Border
6356 ? aBorderPadding
6357 : LogicalSize(aWM);
6358 nscoord boxSizingToMarginEdgeISize = aMargin.ISize(aWM) +
6359 aBorderPadding.ISize(aWM) -
6360 boxSizingAdjust.ISize(aWM);
6362 const auto& styleISize = aSizeOverrides.mStyleISize
6363 ? *aSizeOverrides.mStyleISize
6364 : stylePos->ISize(aWM);
6365 const auto& styleBSize = aSizeOverrides.mStyleBSize
6366 ? *aSizeOverrides.mStyleBSize
6367 : stylePos->BSize(aWM);
6368 const auto& aspectRatio = aSizeOverrides.mAspectRatio
6369 ? *aSizeOverrides.mAspectRatio
6370 : GetAspectRatio();
6372 auto parentFrame = GetParent();
6373 auto alignCB = parentFrame;
6374 bool isGridItem = IsGridItem();
6375 if (parentFrame && parentFrame->IsTableWrapperFrame() && IsTableFrame()) {
6376 // An inner table frame is sized as a grid item if its table wrapper is,
6377 // because they actually have the same CB (the wrapper's CB).
6378 // @see ReflowInput::InitCBReflowInput
6379 auto tableWrapper = GetParent();
6380 auto grandParent = tableWrapper->GetParent();
6381 isGridItem = grandParent->IsGridContainerFrame() &&
6382 !tableWrapper->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW);
6383 if (isGridItem) {
6384 // When resolving justify/align-self below, we want to use the grid
6385 // container's justify/align-items value and WritingMode.
6386 alignCB = grandParent;
6389 const bool isFlexItem =
6390 IsFlexItem() &&
6391 !parentFrame->HasAnyStateBits(NS_STATE_FLEX_IS_EMULATING_LEGACY_BOX);
6392 // This variable only gets set (and used) if isFlexItem is true. It
6393 // indicates which axis (in this frame's own WM) corresponds to its
6394 // flex container's main axis.
6395 LogicalAxis flexMainAxis =
6396 eLogicalAxisInline; // (init to make valgrind happy)
6397 if (isFlexItem) {
6398 flexMainAxis = nsFlexContainerFrame::IsItemInlineAxisMainAxis(this)
6399 ? eLogicalAxisInline
6400 : eLogicalAxisBlock;
6403 const bool isOrthogonal = aWM.IsOrthogonalTo(alignCB->GetWritingMode());
6404 const bool isAutoISize = styleISize.IsAuto();
6405 const bool isAutoBSize =
6406 nsLayoutUtils::IsAutoBSize(styleBSize, aCBSize.BSize(aWM)) ||
6407 aFlags.contains(ComputeSizeFlag::UseAutoBSize);
6408 // Compute inline-axis size
6409 if (!isAutoISize) {
6410 auto iSizeResult = ComputeISizeValue(
6411 aRenderingContext, aWM, aCBSize, boxSizingAdjust,
6412 boxSizingToMarginEdgeISize, styleISize, aSizeOverrides, aFlags);
6413 result.ISize(aWM) = iSizeResult.mISize;
6414 aspectRatioUsage = iSizeResult.mAspectRatioUsage;
6415 } else if (MOZ_UNLIKELY(isGridItem) && !IsTrueOverflowContainer()) {
6416 // 'auto' inline-size for grid-level box - fill the CB for 'stretch' /
6417 // 'normal' and clamp it to the CB if requested:
6418 bool stretch = false;
6419 bool mayUseAspectRatio = aspectRatio && !isAutoBSize;
6420 if (!aFlags.contains(ComputeSizeFlag::ShrinkWrap) &&
6421 !StyleMargin()->HasInlineAxisAuto(aWM) &&
6422 !alignCB->IsMasonry(isOrthogonal ? eLogicalAxisBlock
6423 : eLogicalAxisInline)) {
6424 auto inlineAxisAlignment =
6425 isOrthogonal ? StylePosition()->UsedAlignSelf(alignCB->Style())._0
6426 : StylePosition()->UsedJustifySelf(alignCB->Style())._0;
6427 stretch = inlineAxisAlignment == StyleAlignFlags::STRETCH ||
6428 (inlineAxisAlignment == StyleAlignFlags::NORMAL &&
6429 !mayUseAspectRatio);
6432 // Apply the preferred aspect ratio for alignments other than *stretch* and
6433 // *normal without aspect ratio*.
6434 // The spec says all other values should size the items as fit-content, and
6435 // the intrinsic size should respect the preferred aspect ratio, so we also
6436 // apply aspect ratio for all other values.
6437 // https://drafts.csswg.org/css-grid/#grid-item-sizing
6438 if (!stretch && mayUseAspectRatio) {
6439 // Note: we don't need to handle aspect ratio for inline axis if both
6440 // width/height are auto. The default ratio-dependent axis is block axis
6441 // in this case, so we can simply get the block size from the non-auto
6442 // |styleBSize|.
6443 auto bSize = nsLayoutUtils::ComputeBSizeValue(
6444 aCBSize.BSize(aWM), boxSizingAdjust.BSize(aWM),
6445 styleBSize.AsLengthPercentage());
6446 result.ISize(aWM) = aspectRatio.ComputeRatioDependentSize(
6447 LogicalAxis::eLogicalAxisInline, aWM, bSize, boxSizingAdjust);
6448 aspectRatioUsage = AspectRatioUsage::ToComputeISize;
6451 if (stretch || aFlags.contains(ComputeSizeFlag::IClampMarginBoxMinSize)) {
6452 auto iSizeToFillCB =
6453 std::max(nscoord(0), aCBSize.ISize(aWM) - aBorderPadding.ISize(aWM) -
6454 aMargin.ISize(aWM));
6455 if (stretch || result.ISize(aWM) > iSizeToFillCB) {
6456 result.ISize(aWM) = iSizeToFillCB;
6459 } else if (aspectRatio && !isAutoBSize) {
6460 auto bSize = nsLayoutUtils::ComputeBSizeValue(
6461 aCBSize.BSize(aWM), boxSizingAdjust.BSize(aWM),
6462 styleBSize.AsLengthPercentage());
6463 result.ISize(aWM) = aspectRatio.ComputeRatioDependentSize(
6464 LogicalAxis::eLogicalAxisInline, aWM, bSize, boxSizingAdjust);
6465 aspectRatioUsage = AspectRatioUsage::ToComputeISize;
6468 // Calculate and apply transferred min & max size contraints.
6469 // https://drafts.csswg.org/css-sizing-4/#aspect-ratio-size-transfers
6471 // Note: The basic principle is that sizing constraints transfer through the
6472 // aspect-ratio to the other side to preserve the aspect ratio to the extent
6473 // that they can without violating any sizes specified explicitly on that
6474 // affected axis.
6475 const bool isDefiniteISize = styleISize.IsLengthPercentage();
6476 const bool isFlexItemInlineAxisMainAxis =
6477 isFlexItem && flexMainAxis == eLogicalAxisInline;
6478 const auto& minBSizeCoord = stylePos->MinBSize(aWM);
6479 const auto& maxBSizeCoord = stylePos->MaxBSize(aWM);
6480 const bool isAutoMinBSize =
6481 nsLayoutUtils::IsAutoBSize(minBSizeCoord, aCBSize.BSize(aWM));
6482 const bool isAutoMaxBSize =
6483 nsLayoutUtils::IsAutoBSize(maxBSizeCoord, aCBSize.BSize(aWM));
6484 if (aspectRatio && !isDefiniteISize && !isFlexItemInlineAxisMainAxis) {
6485 const MinMaxSize minMaxBSize{
6486 isAutoMinBSize ? 0
6487 : nsLayoutUtils::ComputeBSizeValue(
6488 aCBSize.BSize(aWM), boxSizingAdjust.BSize(aWM),
6489 minBSizeCoord.AsLengthPercentage()),
6490 isAutoMaxBSize ? NS_UNCONSTRAINEDSIZE
6491 : nsLayoutUtils::ComputeBSizeValue(
6492 aCBSize.BSize(aWM), boxSizingAdjust.BSize(aWM),
6493 maxBSizeCoord.AsLengthPercentage())};
6494 MinMaxSize transferredMinMaxISize = ComputeTransferredMinMaxInlineSize(
6495 aWM, aspectRatio, minMaxBSize, boxSizingAdjust);
6497 result.ISize(aWM) =
6498 transferredMinMaxISize.ClampSizeToMinAndMax(result.ISize(aWM));
6501 // Flex items ignore their min & max sizing properties in their
6502 // flex container's main-axis. (Those properties get applied later in
6503 // the flexbox algorithm.)
6504 const auto& maxISizeCoord = stylePos->MaxISize(aWM);
6505 nscoord maxISize = NS_UNCONSTRAINEDSIZE;
6506 if (!maxISizeCoord.IsNone() && !isFlexItemInlineAxisMainAxis) {
6507 maxISize = ComputeISizeValue(aRenderingContext, aWM, aCBSize,
6508 boxSizingAdjust, boxSizingToMarginEdgeISize,
6509 maxISizeCoord, aSizeOverrides, aFlags)
6510 .mISize;
6511 result.ISize(aWM) = std::min(maxISize, result.ISize(aWM));
6514 const auto& minISizeCoord = stylePos->MinISize(aWM);
6515 nscoord minISize;
6516 if (!minISizeCoord.IsAuto() && !isFlexItemInlineAxisMainAxis) {
6517 minISize = ComputeISizeValue(aRenderingContext, aWM, aCBSize,
6518 boxSizingAdjust, boxSizingToMarginEdgeISize,
6519 minISizeCoord, aSizeOverrides, aFlags)
6520 .mISize;
6521 } else if (MOZ_UNLIKELY(
6522 aFlags.contains(ComputeSizeFlag::IApplyAutoMinSize))) {
6523 // This implements "Implied Minimum Size of Grid Items".
6524 // https://drafts.csswg.org/css-grid/#min-size-auto
6525 minISize = std::min(maxISize, GetMinISize(aRenderingContext));
6526 if (styleISize.IsLengthPercentage()) {
6527 minISize = std::min(minISize, result.ISize(aWM));
6528 } else if (aFlags.contains(ComputeSizeFlag::IClampMarginBoxMinSize)) {
6529 // "if the grid item spans only grid tracks that have a fixed max track
6530 // sizing function, its automatic minimum size in that dimension is
6531 // further clamped to less than or equal to the size necessary to fit
6532 // its margin box within the resulting grid area (flooring at zero)"
6533 // https://drafts.csswg.org/css-grid/#min-size-auto
6534 auto maxMinISize =
6535 std::max(nscoord(0), aCBSize.ISize(aWM) - aBorderPadding.ISize(aWM) -
6536 aMargin.ISize(aWM));
6537 minISize = std::min(minISize, maxMinISize);
6539 } else if (aspectRatioUsage == AspectRatioUsage::ToComputeISize &&
6540 ShouldApplyAutomaticMinimumOnInlineAxis(aWM, disp, stylePos)) {
6541 // This means we successfully applied aspect-ratio and now need to check
6542 // if we need to apply the implied minimum size:
6543 // https://drafts.csswg.org/css-sizing-4/#aspect-ratio-minimum
6544 MOZ_ASSERT(!IsFrameOfType(eReplacedSizing),
6545 "aspect-ratio minimums should not apply to replaced elements");
6546 // The inline size computed by aspect-ratio shouldn't less than the content
6547 // size.
6548 minISize = GetMinISize(aRenderingContext);
6549 } else {
6550 // Treat "min-width: auto" as 0.
6551 // NOTE: Technically, "auto" is supposed to behave like "min-content" on
6552 // flex items. However, we don't need to worry about that here, because
6553 // flex items' min-sizes are intentionally ignored until the flex
6554 // container explicitly considers them during space distribution.
6555 minISize = 0;
6557 result.ISize(aWM) = std::max(minISize, result.ISize(aWM));
6559 // Compute block-axis size
6560 // (but not if we have auto bsize or if we received the "UseAutoBSize"
6561 // flag -- then, we'll just stick with the bsize that we already calculated
6562 // in the initial ComputeAutoSize() call. However, if we have a valid
6563 // preferred aspect ratio, we still have to compute the block size because
6564 // aspect ratio affects the intrinsic content size.)
6565 if (!isAutoBSize) {
6566 result.BSize(aWM) = nsLayoutUtils::ComputeBSizeValue(
6567 aCBSize.BSize(aWM), boxSizingAdjust.BSize(aWM),
6568 styleBSize.AsLengthPercentage());
6569 } else if (MOZ_UNLIKELY(isGridItem) &&
6570 // FIXME: Any better way to refine the auto check here?
6571 styleBSize.IsAuto() &&
6572 !aFlags.contains(ComputeSizeFlag::UseAutoBSize) &&
6573 !IsTrueOverflowContainer() &&
6574 !alignCB->IsMasonry(isOrthogonal ? eLogicalAxisInline
6575 : eLogicalAxisBlock)) {
6576 auto cbSize = aCBSize.BSize(aWM);
6577 if (cbSize != NS_UNCONSTRAINEDSIZE) {
6578 // 'auto' block-size for grid-level box - fill the CB for 'stretch' /
6579 // 'normal' and clamp it to the CB if requested:
6580 bool stretch = false;
6581 bool mayUseAspectRatio =
6582 aspectRatio && result.ISize(aWM) != NS_UNCONSTRAINEDSIZE;
6583 if (!StyleMargin()->HasBlockAxisAuto(aWM)) {
6584 auto blockAxisAlignment =
6585 isOrthogonal ? StylePosition()->UsedJustifySelf(alignCB->Style())._0
6586 : StylePosition()->UsedAlignSelf(alignCB->Style())._0;
6587 stretch = blockAxisAlignment == StyleAlignFlags::STRETCH ||
6588 (blockAxisAlignment == StyleAlignFlags::NORMAL &&
6589 !mayUseAspectRatio);
6592 // Apply the preferred aspect ratio for alignments other than *stretch*
6593 // and *normal without aspect ratio*.
6594 // The spec says all other values should size the items as fit-content,
6595 // and the intrinsic size should respect the preferred aspect ratio, so
6596 // we also apply aspect ratio for all other values.
6597 // https://drafts.csswg.org/css-grid/#grid-item-sizing
6598 if (!stretch && mayUseAspectRatio) {
6599 result.BSize(aWM) = aspectRatio.ComputeRatioDependentSize(
6600 LogicalAxis::eLogicalAxisBlock, aWM, result.ISize(aWM),
6601 boxSizingAdjust);
6602 MOZ_ASSERT(aspectRatioUsage == AspectRatioUsage::None);
6603 aspectRatioUsage = AspectRatioUsage::ToComputeBSize;
6606 if (stretch || aFlags.contains(ComputeSizeFlag::BClampMarginBoxMinSize)) {
6607 auto bSizeToFillCB =
6608 std::max(nscoord(0),
6609 cbSize - aBorderPadding.BSize(aWM) - aMargin.BSize(aWM));
6610 if (stretch || (result.BSize(aWM) != NS_UNCONSTRAINEDSIZE &&
6611 result.BSize(aWM) > bSizeToFillCB)) {
6612 result.BSize(aWM) = bSizeToFillCB;
6616 } else if (aspectRatio) {
6617 // If both inline and block dimensions are auto, the block axis is the
6618 // ratio-dependent axis by default.
6619 // If we have a super large inline size, aspect-ratio should still be
6620 // applied (so aspectRatioUsage flag is set as expected). That's why we
6621 // apply aspect-ratio unconditionally for auto block size here.
6622 result.BSize(aWM) = aspectRatio.ComputeRatioDependentSize(
6623 LogicalAxis::eLogicalAxisBlock, aWM, result.ISize(aWM),
6624 boxSizingAdjust);
6625 MOZ_ASSERT(aspectRatioUsage == AspectRatioUsage::None);
6626 aspectRatioUsage = AspectRatioUsage::ToComputeBSize;
6629 if (result.BSize(aWM) != NS_UNCONSTRAINEDSIZE) {
6630 const bool isFlexItemBlockAxisMainAxis =
6631 isFlexItem && flexMainAxis == eLogicalAxisBlock;
6632 if (!isAutoMaxBSize && !isFlexItemBlockAxisMainAxis) {
6633 nscoord maxBSize = nsLayoutUtils::ComputeBSizeValue(
6634 aCBSize.BSize(aWM), boxSizingAdjust.BSize(aWM),
6635 maxBSizeCoord.AsLengthPercentage());
6636 result.BSize(aWM) = std::min(maxBSize, result.BSize(aWM));
6639 if (!isAutoMinBSize && !isFlexItemBlockAxisMainAxis) {
6640 nscoord minBSize = nsLayoutUtils::ComputeBSizeValue(
6641 aCBSize.BSize(aWM), boxSizingAdjust.BSize(aWM),
6642 minBSizeCoord.AsLengthPercentage());
6643 result.BSize(aWM) = std::max(minBSize, result.BSize(aWM));
6647 if (IsThemed(disp)) {
6648 LayoutDeviceIntSize widget;
6649 bool canOverride = true;
6650 nsPresContext* presContext = PresContext();
6651 presContext->Theme()->GetMinimumWidgetSize(
6652 presContext, this, disp->EffectiveAppearance(), &widget, &canOverride);
6654 // Convert themed widget's physical dimensions to logical coords
6655 LogicalSize size(aWM,
6656 nsSize(presContext->DevPixelsToAppUnits(widget.width),
6657 presContext->DevPixelsToAppUnits(widget.height)));
6659 // GetMinimumWidgetSize() returns border-box; we need content-box.
6660 size -= aBorderPadding;
6662 if (size.BSize(aWM) > result.BSize(aWM) || !canOverride) {
6663 result.BSize(aWM) = size.BSize(aWM);
6665 if (size.ISize(aWM) > result.ISize(aWM) || !canOverride) {
6666 result.ISize(aWM) = size.ISize(aWM);
6670 result.ISize(aWM) = std::max(0, result.ISize(aWM));
6671 result.BSize(aWM) = std::max(0, result.BSize(aWM));
6673 return {result, aspectRatioUsage};
6676 nsRect nsIFrame::ComputeTightBounds(DrawTarget* aDrawTarget) const {
6677 return InkOverflowRect();
6680 /* virtual */
6681 nsresult nsIFrame::GetPrefWidthTightBounds(gfxContext* aContext, nscoord* aX,
6682 nscoord* aXMost) {
6683 return NS_ERROR_NOT_IMPLEMENTED;
6686 /* virtual */
6687 LogicalSize nsIFrame::ComputeAutoSize(
6688 gfxContext* aRenderingContext, WritingMode aWM,
6689 const mozilla::LogicalSize& aCBSize, nscoord aAvailableISize,
6690 const mozilla::LogicalSize& aMargin,
6691 const mozilla::LogicalSize& aBorderPadding,
6692 const StyleSizeOverrides& aSizeOverrides, ComputeSizeFlags aFlags) {
6693 // Use basic shrink-wrapping as a default implementation.
6694 LogicalSize result(aWM, 0xdeadbeef, NS_UNCONSTRAINEDSIZE);
6696 // don't bother setting it if the result won't be used
6697 const auto& styleISize = aSizeOverrides.mStyleISize
6698 ? *aSizeOverrides.mStyleISize
6699 : StylePosition()->ISize(aWM);
6700 if (styleISize.IsAuto()) {
6701 nscoord availBased =
6702 aAvailableISize - aMargin.ISize(aWM) - aBorderPadding.ISize(aWM);
6703 result.ISize(aWM) = ShrinkWidthToFit(aRenderingContext, availBased, aFlags);
6705 return result;
6708 nscoord nsIFrame::ShrinkWidthToFit(gfxContext* aRenderingContext,
6709 nscoord aISizeInCB,
6710 ComputeSizeFlags aFlags) {
6711 // If we're a container for font size inflation, then shrink
6712 // wrapping inside of us should not apply font size inflation.
6713 AutoMaybeDisableFontInflation an(this);
6715 nscoord result;
6716 nscoord minISize = GetMinISize(aRenderingContext);
6717 if (minISize > aISizeInCB) {
6718 const bool clamp = aFlags.contains(ComputeSizeFlag::IClampMarginBoxMinSize);
6719 result = MOZ_UNLIKELY(clamp) ? aISizeInCB : minISize;
6720 } else {
6721 nscoord prefISize = GetPrefISize(aRenderingContext);
6722 if (prefISize > aISizeInCB) {
6723 result = aISizeInCB;
6724 } else {
6725 result = prefISize;
6728 return result;
6731 Maybe<nscoord> nsIFrame::ComputeInlineSizeFromAspectRatio(
6732 WritingMode aWM, const LogicalSize& aCBSize,
6733 const LogicalSize& aContentEdgeToBoxSizing,
6734 const StyleSizeOverrides& aSizeOverrides, ComputeSizeFlags aFlags) const {
6735 // FIXME: Bug 1670151: Use GetAspectRatio() to cover replaced elements (and
6736 // then we can drop the check of eSupportsAspectRatio).
6737 const AspectRatio aspectRatio =
6738 aSizeOverrides.mAspectRatio
6739 ? *aSizeOverrides.mAspectRatio
6740 : StylePosition()->mAspectRatio.ToLayoutRatio();
6741 if (!IsFrameOfType(eSupportsAspectRatio) || !aspectRatio) {
6742 return Nothing();
6745 const StyleSize& styleBSize = aSizeOverrides.mStyleBSize
6746 ? *aSizeOverrides.mStyleBSize
6747 : StylePosition()->BSize(aWM);
6748 if (aFlags.contains(ComputeSizeFlag::UseAutoBSize) ||
6749 nsLayoutUtils::IsAutoBSize(styleBSize, aCBSize.BSize(aWM))) {
6750 return Nothing();
6753 MOZ_ASSERT(styleBSize.IsLengthPercentage());
6754 nscoord bSize = nsLayoutUtils::ComputeBSizeValue(
6755 aCBSize.BSize(aWM), aContentEdgeToBoxSizing.BSize(aWM),
6756 styleBSize.AsLengthPercentage());
6757 return Some(aspectRatio.ComputeRatioDependentSize(
6758 LogicalAxis::eLogicalAxisInline, aWM, bSize, aContentEdgeToBoxSizing));
6761 nsIFrame::ISizeComputationResult nsIFrame::ComputeISizeValue(
6762 gfxContext* aRenderingContext, const WritingMode aWM,
6763 const LogicalSize& aContainingBlockSize,
6764 const LogicalSize& aContentEdgeToBoxSizing, nscoord aBoxSizingToMarginEdge,
6765 ExtremumLength aSize, Maybe<nscoord> aAvailableISizeOverride,
6766 const StyleSizeOverrides& aSizeOverrides, ComputeSizeFlags aFlags) {
6767 // If 'this' is a container for font size inflation, then shrink
6768 // wrapping inside of it should not apply font size inflation.
6769 AutoMaybeDisableFontInflation an(this);
6770 // If we have an aspect-ratio and a definite block size, we resolve the
6771 // min-content and max-content size by the aspect-ratio and the block size.
6772 // https://github.com/w3c/csswg-drafts/issues/5032
6773 Maybe<nscoord> intrinsicSizeFromAspectRatio =
6774 aSize == ExtremumLength::MozAvailable
6775 ? Nothing()
6776 : ComputeInlineSizeFromAspectRatio(aWM, aContainingBlockSize,
6777 aContentEdgeToBoxSizing,
6778 aSizeOverrides, aFlags);
6779 nscoord result;
6780 switch (aSize) {
6781 case ExtremumLength::MaxContent:
6782 result = intrinsicSizeFromAspectRatio ? *intrinsicSizeFromAspectRatio
6783 : GetPrefISize(aRenderingContext);
6784 NS_ASSERTION(result >= 0, "inline-size less than zero");
6785 return {result, intrinsicSizeFromAspectRatio
6786 ? AspectRatioUsage::ToComputeISize
6787 : AspectRatioUsage::None};
6788 case ExtremumLength::MinContent:
6789 result = intrinsicSizeFromAspectRatio ? *intrinsicSizeFromAspectRatio
6790 : GetMinISize(aRenderingContext);
6791 NS_ASSERTION(result >= 0, "inline-size less than zero");
6792 if (MOZ_UNLIKELY(
6793 aFlags.contains(ComputeSizeFlag::IClampMarginBoxMinSize))) {
6794 auto available =
6795 aContainingBlockSize.ISize(aWM) -
6796 (aBoxSizingToMarginEdge + aContentEdgeToBoxSizing.ISize(aWM));
6797 result = std::min(available, result);
6799 return {result, intrinsicSizeFromAspectRatio
6800 ? AspectRatioUsage::ToComputeISize
6801 : AspectRatioUsage::None};
6802 case ExtremumLength::FitContentFunction:
6803 case ExtremumLength::FitContent: {
6804 nscoord pref = NS_UNCONSTRAINEDSIZE;
6805 nscoord min = 0;
6806 if (intrinsicSizeFromAspectRatio) {
6807 // The min-content and max-content size are identical and equal to the
6808 // size computed from the block size and the aspect ratio.
6809 pref = min = *intrinsicSizeFromAspectRatio;
6810 } else {
6811 pref = GetPrefISize(aRenderingContext);
6812 min = GetMinISize(aRenderingContext);
6815 nscoord fill = aAvailableISizeOverride
6816 ? *aAvailableISizeOverride
6817 : aContainingBlockSize.ISize(aWM) -
6818 (aBoxSizingToMarginEdge +
6819 aContentEdgeToBoxSizing.ISize(aWM));
6821 if (MOZ_UNLIKELY(
6822 aFlags.contains(ComputeSizeFlag::IClampMarginBoxMinSize))) {
6823 min = std::min(min, fill);
6825 result = std::max(min, std::min(pref, fill));
6826 NS_ASSERTION(result >= 0, "inline-size less than zero");
6827 return {result};
6829 case ExtremumLength::MozAvailable:
6830 return {aContainingBlockSize.ISize(aWM) -
6831 (aBoxSizingToMarginEdge + aContentEdgeToBoxSizing.ISize(aWM))};
6833 MOZ_ASSERT_UNREACHABLE("Unknown extremum length?");
6834 return {};
6837 nscoord nsIFrame::ComputeISizeValue(const WritingMode aWM,
6838 const LogicalSize& aContainingBlockSize,
6839 const LogicalSize& aContentEdgeToBoxSizing,
6840 const LengthPercentage& aSize) {
6841 LAYOUT_WARN_IF_FALSE(
6842 aContainingBlockSize.ISize(aWM) != NS_UNCONSTRAINEDSIZE,
6843 "have unconstrained inline-size; this should only result from "
6844 "very large sizes, not attempts at intrinsic inline-size "
6845 "calculation");
6846 NS_ASSERTION(aContainingBlockSize.ISize(aWM) >= 0,
6847 "inline-size less than zero");
6849 nscoord result = aSize.Resolve(aContainingBlockSize.ISize(aWM));
6850 // The result of a calc() expression might be less than 0; we
6851 // should clamp at runtime (below). (Percentages and coords that
6852 // are less than 0 have already been dropped by the parser.)
6853 result -= aContentEdgeToBoxSizing.ISize(aWM);
6854 return std::max(0, result);
6857 void nsIFrame::DidReflow(nsPresContext* aPresContext,
6858 const ReflowInput* aReflowInput) {
6859 NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, ("nsIFrame::DidReflow"));
6861 SVGObserverUtils::InvalidateDirectRenderingObservers(
6862 this, SVGObserverUtils::INVALIDATE_REFLOW);
6864 RemoveStateBits(NS_FRAME_IN_REFLOW | NS_FRAME_FIRST_REFLOW |
6865 NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN);
6867 // Clear bits that were used in ReflowInput::InitResizeFlags (see
6868 // comment there for why we can't clear it there).
6869 SetHasBSizeChange(false);
6870 SetHasPaddingChange(false);
6872 // Notify the percent bsize observer if there is a percent bsize.
6873 // The observer may be able to initiate another reflow with a computed
6874 // bsize. This happens in the case where a table cell has no computed
6875 // bsize but can fabricate one when the cell bsize is known.
6876 if (aReflowInput && aReflowInput->mPercentBSizeObserver && !GetPrevInFlow()) {
6877 const auto& bsize =
6878 aReflowInput->mStylePosition->BSize(aReflowInput->GetWritingMode());
6879 if (bsize.HasPercent()) {
6880 aReflowInput->mPercentBSizeObserver->NotifyPercentBSize(*aReflowInput);
6884 aPresContext->ReflowedFrame();
6886 #ifdef ACCESSIBILITY
6887 if (nsAccessibilityService* accService =
6888 PresShell::GetAccessibilityService()) {
6889 accService->NotifyOfPossibleBoundsChange(PresShell(), mContent);
6891 #endif
6894 void nsIFrame::FinishReflowWithAbsoluteFrames(nsPresContext* aPresContext,
6895 ReflowOutput& aDesiredSize,
6896 const ReflowInput& aReflowInput,
6897 nsReflowStatus& aStatus,
6898 bool aConstrainBSize) {
6899 ReflowAbsoluteFrames(aPresContext, aDesiredSize, aReflowInput, aStatus,
6900 aConstrainBSize);
6902 FinishAndStoreOverflow(&aDesiredSize, aReflowInput.mStyleDisplay);
6905 void nsIFrame::ReflowAbsoluteFrames(nsPresContext* aPresContext,
6906 ReflowOutput& aDesiredSize,
6907 const ReflowInput& aReflowInput,
6908 nsReflowStatus& aStatus,
6909 bool aConstrainBSize) {
6910 if (HasAbsolutelyPositionedChildren()) {
6911 nsAbsoluteContainingBlock* absoluteContainer = GetAbsoluteContainingBlock();
6913 // Let the absolutely positioned container reflow any absolutely positioned
6914 // child frames that need to be reflowed
6916 // The containing block for the abs pos kids is formed by our padding edge.
6917 nsMargin usedBorder = GetUsedBorder();
6918 nscoord containingBlockWidth =
6919 std::max(0, aDesiredSize.Width() - usedBorder.LeftRight());
6920 nscoord containingBlockHeight =
6921 std::max(0, aDesiredSize.Height() - usedBorder.TopBottom());
6922 nsContainerFrame* container = do_QueryFrame(this);
6923 NS_ASSERTION(container,
6924 "Abs-pos children only supported on container frames for now");
6926 nsRect containingBlock(0, 0, containingBlockWidth, containingBlockHeight);
6927 AbsPosReflowFlags flags =
6928 AbsPosReflowFlags::CBWidthAndHeightChanged; // XXX could be optimized
6929 if (aConstrainBSize) {
6930 flags |= AbsPosReflowFlags::ConstrainHeight;
6932 absoluteContainer->Reflow(container, aPresContext, aReflowInput, aStatus,
6933 containingBlock, flags,
6934 &aDesiredSize.mOverflowAreas);
6938 /* virtual */
6939 bool nsIFrame::CanContinueTextRun() const {
6940 // By default, a frame will *not* allow a text run to be continued
6941 // through it.
6942 return false;
6945 void nsIFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
6946 const ReflowInput& aReflowInput,
6947 nsReflowStatus& aStatus) {
6948 MarkInReflow();
6949 DO_GLOBAL_REFLOW_COUNT("nsFrame");
6950 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
6951 aDesiredSize.ClearSize();
6952 NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
6955 bool nsIFrame::IsContentDisabled() const {
6956 // FIXME(emilio): Doing this via CSS means callers must ensure the style is up
6957 // to date, and they don't!
6958 if (StyleUI()->UserInput() == StyleUserInput::None) {
6959 return true;
6962 auto* element = nsGenericHTMLElement::FromNodeOrNull(GetContent());
6963 return element && element->IsDisabled();
6966 nsresult nsIFrame::CharacterDataChanged(const CharacterDataChangeInfo&) {
6967 MOZ_ASSERT_UNREACHABLE("should only be called for text frames");
6968 return NS_OK;
6971 nsresult nsIFrame::AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
6972 int32_t aModType) {
6973 return NS_OK;
6976 // Flow member functions
6978 nsIFrame* nsIFrame::GetPrevContinuation() const { return nullptr; }
6980 void nsIFrame::SetPrevContinuation(nsIFrame* aPrevContinuation) {
6981 MOZ_ASSERT(false, "not splittable");
6984 nsIFrame* nsIFrame::GetNextContinuation() const { return nullptr; }
6986 void nsIFrame::SetNextContinuation(nsIFrame*) {
6987 MOZ_ASSERT(false, "not splittable");
6990 nsIFrame* nsIFrame::GetPrevInFlow() const { return nullptr; }
6992 void nsIFrame::SetPrevInFlow(nsIFrame* aPrevInFlow) {
6993 MOZ_ASSERT(false, "not splittable");
6996 nsIFrame* nsIFrame::GetNextInFlow() const { return nullptr; }
6998 void nsIFrame::SetNextInFlow(nsIFrame*) { MOZ_ASSERT(false, "not splittable"); }
7000 nsIFrame* nsIFrame::GetTailContinuation() {
7001 nsIFrame* frame = this;
7002 while (frame->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
7003 frame = frame->GetPrevContinuation();
7004 NS_ASSERTION(frame, "first continuation can't be overflow container");
7006 for (nsIFrame* next = frame->GetNextContinuation();
7007 next && !next->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
7008 next = frame->GetNextContinuation()) {
7009 frame = next;
7012 MOZ_ASSERT(frame, "illegal state in continuation chain.");
7013 return frame;
7016 // Associated view object
7017 void nsIFrame::SetView(nsView* aView) {
7018 if (aView) {
7019 aView->SetFrame(this);
7021 #ifdef DEBUG
7022 LayoutFrameType frameType = Type();
7023 NS_ASSERTION(frameType == LayoutFrameType::SubDocument ||
7024 frameType == LayoutFrameType::ListControl ||
7025 frameType == LayoutFrameType::Viewport ||
7026 frameType == LayoutFrameType::MenuPopup,
7027 "Only specific frame types can have an nsView");
7028 #endif
7030 // Store the view on the frame.
7031 SetViewInternal(aView);
7033 // Set the frame state bit that says the frame has a view
7034 AddStateBits(NS_FRAME_HAS_VIEW);
7036 // Let all of the ancestors know they have a descendant with a view.
7037 for (nsIFrame* f = GetParent();
7038 f && !f->HasAnyStateBits(NS_FRAME_HAS_CHILD_WITH_VIEW);
7039 f = f->GetParent())
7040 f->AddStateBits(NS_FRAME_HAS_CHILD_WITH_VIEW);
7041 } else {
7042 MOZ_ASSERT_UNREACHABLE("Destroying a view while the frame is alive?");
7043 RemoveStateBits(NS_FRAME_HAS_VIEW);
7044 SetViewInternal(nullptr);
7048 // Find the first geometric parent that has a view
7049 nsIFrame* nsIFrame::GetAncestorWithView() const {
7050 for (nsIFrame* f = GetParent(); nullptr != f; f = f->GetParent()) {
7051 if (f->HasView()) {
7052 return f;
7055 return nullptr;
7058 template <nsPoint (nsIFrame::*PositionGetter)() const>
7059 static nsPoint OffsetCalculator(const nsIFrame* aThis, const nsIFrame* aOther) {
7060 MOZ_ASSERT(aOther, "Must have frame for destination coordinate system!");
7062 NS_ASSERTION(aThis->PresContext() == aOther->PresContext(),
7063 "GetOffsetTo called on frames in different documents");
7065 nsPoint offset(0, 0);
7066 const nsIFrame* f;
7067 for (f = aThis; f != aOther && f; f = f->GetParent()) {
7068 offset += (f->*PositionGetter)();
7071 if (f != aOther) {
7072 // Looks like aOther wasn't an ancestor of |this|. So now we have
7073 // the root-frame-relative position of |this| in |offset|. Convert back
7074 // to the coordinates of aOther
7075 while (aOther) {
7076 offset -= (aOther->*PositionGetter)();
7077 aOther = aOther->GetParent();
7081 return offset;
7084 nsPoint nsIFrame::GetOffsetTo(const nsIFrame* aOther) const {
7085 return OffsetCalculator<&nsIFrame::GetPosition>(this, aOther);
7088 nsPoint nsIFrame::GetOffsetToIgnoringScrolling(const nsIFrame* aOther) const {
7089 return OffsetCalculator<&nsIFrame::GetPositionIgnoringScrolling>(this,
7090 aOther);
7093 nsPoint nsIFrame::GetOffsetToCrossDoc(const nsIFrame* aOther) const {
7094 return GetOffsetToCrossDoc(aOther, PresContext()->AppUnitsPerDevPixel());
7097 nsPoint nsIFrame::GetOffsetToCrossDoc(const nsIFrame* aOther,
7098 const int32_t aAPD) const {
7099 MOZ_ASSERT(aOther, "Must have frame for destination coordinate system!");
7100 NS_ASSERTION(PresContext()->GetRootPresContext() ==
7101 aOther->PresContext()->GetRootPresContext(),
7102 "trying to get the offset between frames in different document "
7103 "hierarchies?");
7104 if (PresContext()->GetRootPresContext() !=
7105 aOther->PresContext()->GetRootPresContext()) {
7106 // crash right away, we are almost certainly going to crash anyway.
7107 MOZ_CRASH(
7108 "trying to get the offset between frames in different "
7109 "document hierarchies?");
7112 const nsIFrame* root = nullptr;
7113 // offset will hold the final offset
7114 // docOffset holds the currently accumulated offset at the current APD, it
7115 // will be converted and added to offset when the current APD changes.
7116 nsPoint offset(0, 0), docOffset(0, 0);
7117 const nsIFrame* f = this;
7118 int32_t currAPD = PresContext()->AppUnitsPerDevPixel();
7119 while (f && f != aOther) {
7120 docOffset += f->GetPosition();
7121 nsIFrame* parent = f->GetParent();
7122 if (parent) {
7123 f = parent;
7124 } else {
7125 nsPoint newOffset(0, 0);
7126 root = f;
7127 f = nsLayoutUtils::GetCrossDocParentFrameInProcess(f, &newOffset);
7128 int32_t newAPD = f ? f->PresContext()->AppUnitsPerDevPixel() : 0;
7129 if (!f || newAPD != currAPD) {
7130 // Convert docOffset to the right APD and add it to offset.
7131 offset += docOffset.ScaleToOtherAppUnits(currAPD, aAPD);
7132 docOffset.x = docOffset.y = 0;
7134 currAPD = newAPD;
7135 docOffset += newOffset;
7138 if (f == aOther) {
7139 offset += docOffset.ScaleToOtherAppUnits(currAPD, aAPD);
7140 } else {
7141 // Looks like aOther wasn't an ancestor of |this|. So now we have
7142 // the root-document-relative position of |this| in |offset|. Subtract the
7143 // root-document-relative position of |aOther| from |offset|.
7144 // This call won't try to recurse again because root is an ancestor of
7145 // aOther.
7146 nsPoint negOffset = aOther->GetOffsetToCrossDoc(root, aAPD);
7147 offset -= negOffset;
7150 return offset;
7153 CSSIntRect nsIFrame::GetScreenRect() const {
7154 return CSSIntRect::FromAppUnitsToNearest(GetScreenRectInAppUnits());
7157 nsRect nsIFrame::GetScreenRectInAppUnits() const {
7158 nsPresContext* presContext = PresContext();
7159 nsIFrame* rootFrame = presContext->PresShell()->GetRootFrame();
7160 nsPoint rootScreenPos(0, 0);
7161 nsPoint rootFrameOffsetInParent(0, 0);
7162 nsIFrame* rootFrameParent = nsLayoutUtils::GetCrossDocParentFrameInProcess(
7163 rootFrame, &rootFrameOffsetInParent);
7164 if (rootFrameParent) {
7165 nsRect parentScreenRectAppUnits =
7166 rootFrameParent->GetScreenRectInAppUnits();
7167 nsPresContext* parentPresContext = rootFrameParent->PresContext();
7168 double parentScale = double(presContext->AppUnitsPerDevPixel()) /
7169 parentPresContext->AppUnitsPerDevPixel();
7170 nsPoint rootPt =
7171 parentScreenRectAppUnits.TopLeft() + rootFrameOffsetInParent;
7172 rootScreenPos.x = NS_round(parentScale * rootPt.x);
7173 rootScreenPos.y = NS_round(parentScale * rootPt.y);
7174 } else {
7175 nsCOMPtr<nsIWidget> rootWidget =
7176 presContext->PresShell()->GetViewManager()->GetRootWidget();
7177 if (rootWidget) {
7178 LayoutDeviceIntPoint rootDevPx = rootWidget->WidgetToScreenOffset();
7179 rootScreenPos.x = presContext->DevPixelsToAppUnits(rootDevPx.x);
7180 rootScreenPos.y = presContext->DevPixelsToAppUnits(rootDevPx.y);
7184 return nsRect(rootScreenPos + GetOffsetTo(rootFrame), GetSize());
7187 // Returns the offset from this frame to the closest geometric parent that
7188 // has a view. Also returns the containing view or null in case of error
7189 void nsIFrame::GetOffsetFromView(nsPoint& aOffset, nsView** aView) const {
7190 MOZ_ASSERT(nullptr != aView, "null OUT parameter pointer");
7191 nsIFrame* frame = const_cast<nsIFrame*>(this);
7193 *aView = nullptr;
7194 aOffset.MoveTo(0, 0);
7195 do {
7196 aOffset += frame->GetPosition();
7197 frame = frame->GetParent();
7198 } while (frame && !frame->HasView());
7200 if (frame) {
7201 *aView = frame->GetView();
7205 nsIWidget* nsIFrame::GetNearestWidget() const {
7206 return GetClosestView()->GetNearestWidget(nullptr);
7209 nsIWidget* nsIFrame::GetNearestWidget(nsPoint& aOffset) const {
7210 nsPoint offsetToView;
7211 nsPoint offsetToWidget;
7212 nsIWidget* widget =
7213 GetClosestView(&offsetToView)->GetNearestWidget(&offsetToWidget);
7214 aOffset = offsetToView + offsetToWidget;
7215 return widget;
7218 Matrix4x4Flagged nsIFrame::GetTransformMatrix(ViewportType aViewportType,
7219 RelativeTo aStopAtAncestor,
7220 nsIFrame** aOutAncestor,
7221 uint32_t aFlags) const {
7222 MOZ_ASSERT(aOutAncestor, "Need a place to put the ancestor!");
7224 /* If we're transformed, we want to hand back the combination
7225 * transform/translate matrix that will apply our current transform, then
7226 * shift us to our parent.
7228 const bool isTransformed = IsTransformed();
7229 const nsIFrame* zoomedContentRoot = nullptr;
7230 if (aStopAtAncestor.mViewportType == ViewportType::Visual) {
7231 zoomedContentRoot = ViewportUtils::IsZoomedContentRoot(this);
7232 if (zoomedContentRoot) {
7233 MOZ_ASSERT(aViewportType != ViewportType::Visual);
7237 if (isTransformed || zoomedContentRoot) {
7238 Matrix4x4 result;
7239 int32_t scaleFactor =
7240 ((aFlags & IN_CSS_UNITS) ? AppUnitsPerCSSPixel()
7241 : PresContext()->AppUnitsPerDevPixel());
7243 /* Compute the delta to the parent, which we need because we are converting
7244 * coordinates to our parent.
7246 if (isTransformed) {
7247 NS_ASSERTION(nsLayoutUtils::GetCrossDocParentFrameInProcess(this),
7248 "Cannot transform the viewport frame!");
7250 result = result * nsDisplayTransform::GetResultingTransformMatrix(
7251 this, nsPoint(0, 0), scaleFactor,
7252 nsDisplayTransform::INCLUDE_PERSPECTIVE |
7253 nsDisplayTransform::OFFSET_BY_ORIGIN);
7256 // The offset from a zoomed content root to its parent (e.g. from
7257 // a canvas frame to a scroll frame) is in layout coordinates, so
7258 // apply it before applying any layout-to-visual transform.
7259 *aOutAncestor = nsLayoutUtils::GetCrossDocParentFrameInProcess(this);
7260 nsPoint delta = GetOffsetToCrossDoc(*aOutAncestor);
7261 /* Combine the raw transform with a translation to our parent. */
7262 result.PostTranslate(NSAppUnitsToFloatPixels(delta.x, scaleFactor),
7263 NSAppUnitsToFloatPixels(delta.y, scaleFactor), 0.0f);
7265 if (zoomedContentRoot) {
7266 Matrix4x4 layoutToVisual;
7267 ScrollableLayerGuid::ViewID targetScrollId =
7268 nsLayoutUtils::FindOrCreateIDFor(zoomedContentRoot->GetContent());
7269 if (aFlags & nsIFrame::IN_CSS_UNITS) {
7270 layoutToVisual =
7271 ViewportUtils::GetVisualToLayoutTransform(targetScrollId)
7272 .Inverse()
7273 .ToUnknownMatrix();
7274 } else {
7275 layoutToVisual =
7276 ViewportUtils::GetVisualToLayoutTransform<LayoutDevicePixel>(
7277 targetScrollId)
7278 .Inverse()
7279 .ToUnknownMatrix();
7281 result = result * layoutToVisual;
7284 return result;
7287 if (nsLayoutUtils::IsPopup(this) && IsListControlFrame()) {
7288 nsPresContext* presContext = PresContext();
7289 nsIFrame* docRootFrame = presContext->PresShell()->GetRootFrame();
7291 // Compute a matrix that transforms from the popup widget to the toplevel
7292 // widget. We use the widgets because they're the simplest and most
7293 // accurate approach --- this should work no matter how the widget position
7294 // was chosen.
7295 nsIWidget* widget = GetView()->GetWidget();
7296 nsPresContext* rootPresContext = PresContext()->GetRootPresContext();
7297 // Maybe the widget hasn't been created yet? Popups without widgets are
7298 // treated as regular frames. That should work since they'll be rendered
7299 // as part of the page if they're rendered at all.
7300 if (widget && rootPresContext) {
7301 nsIWidget* toplevel = rootPresContext->GetNearestWidget();
7302 if (toplevel) {
7303 LayoutDeviceIntRect screenBounds = widget->GetClientBounds();
7304 LayoutDeviceIntRect toplevelScreenBounds = toplevel->GetClientBounds();
7305 LayoutDeviceIntPoint translation =
7306 screenBounds.TopLeft() - toplevelScreenBounds.TopLeft();
7308 Matrix4x4 transformToTop;
7309 transformToTop._41 = translation.x;
7310 transformToTop._42 = translation.y;
7312 *aOutAncestor = docRootFrame;
7313 Matrix4x4 docRootTransformToTop =
7314 nsLayoutUtils::GetTransformToAncestor(RelativeTo{docRootFrame},
7315 RelativeTo{nullptr})
7316 .GetMatrix();
7317 if (docRootTransformToTop.IsSingular()) {
7318 NS_WARNING(
7319 "Containing document is invisible, we can't compute a valid "
7320 "transform");
7321 } else {
7322 docRootTransformToTop.Invert();
7323 return transformToTop * docRootTransformToTop;
7329 *aOutAncestor = nsLayoutUtils::GetCrossDocParentFrameInProcess(this);
7331 /* Otherwise, we're not transformed. In that case, we'll walk up the frame
7332 * tree until we either hit the root frame or something that may be
7333 * transformed. We'll then change coordinates into that frame, since we're
7334 * guaranteed that nothing in-between can be transformed. First, however,
7335 * we have to check to see if we have a parent. If not, we'll set the
7336 * outparam to null (indicating that there's nothing left) and will hand back
7337 * the identity matrix.
7339 if (!*aOutAncestor) return Matrix4x4();
7341 /* Keep iterating while the frame can't possibly be transformed. */
7342 const nsIFrame* current = this;
7343 auto shouldStopAt = [](const nsIFrame* aCurrent, nsIFrame* aAncestor,
7344 uint32_t aFlags) {
7345 return aAncestor->IsTransformed() || nsLayoutUtils::IsPopup(aAncestor) ||
7346 ViewportUtils::IsZoomedContentRoot(aAncestor) ||
7347 ((aFlags & STOP_AT_STACKING_CONTEXT_AND_DISPLAY_PORT) &&
7348 (aAncestor->IsStackingContext() ||
7349 DisplayPortUtils::FrameHasDisplayPort(aAncestor, aCurrent)));
7351 while (*aOutAncestor != aStopAtAncestor.mFrame &&
7352 !shouldStopAt(current, *aOutAncestor, aFlags)) {
7353 /* If no parent, stop iterating. Otherwise, update the ancestor. */
7354 nsIFrame* parent =
7355 nsLayoutUtils::GetCrossDocParentFrameInProcess(*aOutAncestor);
7356 if (!parent) break;
7358 current = *aOutAncestor;
7359 *aOutAncestor = parent;
7362 NS_ASSERTION(*aOutAncestor, "Somehow ended up with a null ancestor...?");
7364 /* Translate from this frame to our ancestor, if it exists. That's the
7365 * entire transform, so we're done.
7367 nsPoint delta = GetOffsetToCrossDoc(*aOutAncestor);
7368 int32_t scaleFactor =
7369 ((aFlags & IN_CSS_UNITS) ? AppUnitsPerCSSPixel()
7370 : PresContext()->AppUnitsPerDevPixel());
7371 return Matrix4x4::Translation(NSAppUnitsToFloatPixels(delta.x, scaleFactor),
7372 NSAppUnitsToFloatPixels(delta.y, scaleFactor),
7373 0.0f);
7376 static void InvalidateRenderingObservers(nsIFrame* aDisplayRoot,
7377 nsIFrame* aFrame,
7378 bool aFrameChanged = true) {
7379 MOZ_ASSERT(aDisplayRoot == nsLayoutUtils::GetDisplayRootFrame(aFrame));
7380 SVGObserverUtils::InvalidateDirectRenderingObservers(aFrame);
7381 nsIFrame* parent = aFrame;
7382 while (parent != aDisplayRoot &&
7383 (parent = nsLayoutUtils::GetCrossDocParentFrameInProcess(parent)) &&
7384 !parent->HasAnyStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT)) {
7385 SVGObserverUtils::InvalidateDirectRenderingObservers(parent);
7388 if (!aFrameChanged) {
7389 return;
7392 aFrame->MarkNeedsDisplayItemRebuild();
7395 static void SchedulePaintInternal(
7396 nsIFrame* aDisplayRoot, nsIFrame* aFrame,
7397 nsIFrame::PaintType aType = nsIFrame::PAINT_DEFAULT) {
7398 MOZ_ASSERT(aDisplayRoot == nsLayoutUtils::GetDisplayRootFrame(aFrame));
7399 nsPresContext* pres = aDisplayRoot->PresContext()->GetRootPresContext();
7401 // No need to schedule a paint for an external document since they aren't
7402 // painted directly.
7403 if (!pres || (pres->Document() && pres->Document()->IsResourceDoc())) {
7404 return;
7406 if (!pres->GetContainerWeak()) {
7407 NS_WARNING("Shouldn't call SchedulePaint in a detached pres context");
7408 return;
7411 pres->PresShell()->ScheduleViewManagerFlush();
7413 if (aType == nsIFrame::PAINT_DEFAULT) {
7414 aDisplayRoot->AddStateBits(NS_FRAME_UPDATE_LAYER_TREE);
7418 static void InvalidateFrameInternal(nsIFrame* aFrame, bool aHasDisplayItem,
7419 bool aRebuildDisplayItems) {
7420 if (aHasDisplayItem) {
7421 aFrame->AddStateBits(NS_FRAME_NEEDS_PAINT);
7424 if (aRebuildDisplayItems) {
7425 aFrame->MarkNeedsDisplayItemRebuild();
7427 SVGObserverUtils::InvalidateDirectRenderingObservers(aFrame);
7428 bool needsSchedulePaint = false;
7429 if (nsLayoutUtils::IsPopup(aFrame)) {
7430 needsSchedulePaint = true;
7431 } else {
7432 nsIFrame* parent = nsLayoutUtils::GetCrossDocParentFrameInProcess(aFrame);
7433 while (parent &&
7434 !parent->HasAnyStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT)) {
7435 if (aHasDisplayItem && !parent->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
7436 parent->AddStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT);
7438 SVGObserverUtils::InvalidateDirectRenderingObservers(parent);
7440 // If we're inside a popup, then we need to make sure that we
7441 // call schedule paint so that the NS_FRAME_UPDATE_LAYER_TREE
7442 // flag gets added to the popup display root frame.
7443 if (nsLayoutUtils::IsPopup(parent)) {
7444 needsSchedulePaint = true;
7445 break;
7447 parent = nsLayoutUtils::GetCrossDocParentFrameInProcess(parent);
7449 if (!parent) {
7450 needsSchedulePaint = true;
7453 if (!aHasDisplayItem) {
7454 return;
7456 if (needsSchedulePaint) {
7457 nsIFrame* displayRoot = nsLayoutUtils::GetDisplayRootFrame(aFrame);
7458 SchedulePaintInternal(displayRoot, aFrame);
7460 if (aFrame->HasAnyStateBits(NS_FRAME_HAS_INVALID_RECT)) {
7461 aFrame->RemoveProperty(nsIFrame::InvalidationRect());
7462 aFrame->RemoveStateBits(NS_FRAME_HAS_INVALID_RECT);
7466 void nsIFrame::InvalidateFrameSubtree(bool aRebuildDisplayItems /* = true */) {
7467 InvalidateFrame(0, aRebuildDisplayItems);
7469 if (HasAnyStateBits(NS_FRAME_ALL_DESCENDANTS_NEED_PAINT)) {
7470 return;
7473 AddStateBits(NS_FRAME_ALL_DESCENDANTS_NEED_PAINT);
7475 for (const auto& childList : CrossDocChildLists()) {
7476 for (nsIFrame* child : childList.mList) {
7477 // Don't explicitly rebuild display items for our descendants,
7478 // since we should be marked and it implicitly includes all
7479 // descendants.
7480 child->InvalidateFrameSubtree(false);
7485 void nsIFrame::ClearInvalidationStateBits() {
7486 if (HasAnyStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT)) {
7487 for (const auto& childList : CrossDocChildLists()) {
7488 for (nsIFrame* child : childList.mList) {
7489 child->ClearInvalidationStateBits();
7494 RemoveStateBits(NS_FRAME_NEEDS_PAINT | NS_FRAME_DESCENDANT_NEEDS_PAINT |
7495 NS_FRAME_ALL_DESCENDANTS_NEED_PAINT);
7498 bool HasRetainedDataFor(const nsIFrame* aFrame, uint32_t aDisplayItemKey) {
7499 if (RefPtr<WebRenderUserData> data =
7500 GetWebRenderUserData<WebRenderFallbackData>(aFrame,
7501 aDisplayItemKey)) {
7502 return true;
7505 return false;
7508 void nsIFrame::InvalidateFrame(uint32_t aDisplayItemKey,
7509 bool aRebuildDisplayItems /* = true */) {
7510 bool hasDisplayItem =
7511 !aDisplayItemKey || HasRetainedDataFor(this, aDisplayItemKey);
7512 InvalidateFrameInternal(this, hasDisplayItem, aRebuildDisplayItems);
7515 void nsIFrame::InvalidateFrameWithRect(const nsRect& aRect,
7516 uint32_t aDisplayItemKey,
7517 bool aRebuildDisplayItems /* = true */) {
7518 if (aRect.IsEmpty()) {
7519 return;
7521 bool hasDisplayItem =
7522 !aDisplayItemKey || HasRetainedDataFor(this, aDisplayItemKey);
7523 bool alreadyInvalid = false;
7524 if (!HasAnyStateBits(NS_FRAME_NEEDS_PAINT)) {
7525 InvalidateFrameInternal(this, hasDisplayItem, aRebuildDisplayItems);
7526 } else {
7527 alreadyInvalid = true;
7530 if (!hasDisplayItem) {
7531 return;
7534 nsRect* rect;
7535 if (HasAnyStateBits(NS_FRAME_HAS_INVALID_RECT)) {
7536 rect = GetProperty(InvalidationRect());
7537 MOZ_ASSERT(rect);
7538 } else {
7539 if (alreadyInvalid) {
7540 return;
7542 rect = new nsRect();
7543 AddProperty(InvalidationRect(), rect);
7544 AddStateBits(NS_FRAME_HAS_INVALID_RECT);
7547 *rect = rect->Union(aRect);
7550 /*static*/
7551 uint8_t nsIFrame::sLayerIsPrerenderedDataKey;
7553 bool nsIFrame::IsInvalid(nsRect& aRect) {
7554 if (!HasAnyStateBits(NS_FRAME_NEEDS_PAINT)) {
7555 return false;
7558 if (HasAnyStateBits(NS_FRAME_HAS_INVALID_RECT)) {
7559 nsRect* rect = GetProperty(InvalidationRect());
7560 NS_ASSERTION(
7561 rect, "Must have an invalid rect if NS_FRAME_HAS_INVALID_RECT is set!");
7562 aRect = *rect;
7563 } else {
7564 aRect.SetEmpty();
7566 return true;
7569 void nsIFrame::SchedulePaint(PaintType aType, bool aFrameChanged) {
7570 nsIFrame* displayRoot = nsLayoutUtils::GetDisplayRootFrame(this);
7571 InvalidateRenderingObservers(displayRoot, this, aFrameChanged);
7572 SchedulePaintInternal(displayRoot, this, aType);
7575 void nsIFrame::SchedulePaintWithoutInvalidatingObservers(PaintType aType) {
7576 nsIFrame* displayRoot = nsLayoutUtils::GetDisplayRootFrame(this);
7577 SchedulePaintInternal(displayRoot, this, aType);
7580 void nsIFrame::InvalidateLayer(DisplayItemType aDisplayItemKey,
7581 const nsIntRect* aDamageRect,
7582 const nsRect* aFrameDamageRect,
7583 uint32_t aFlags /* = 0 */) {
7584 NS_ASSERTION(aDisplayItemKey > DisplayItemType::TYPE_ZERO, "Need a key");
7586 nsIFrame* displayRoot = nsLayoutUtils::GetDisplayRootFrame(this);
7587 InvalidateRenderingObservers(displayRoot, this, false);
7589 // Check if frame supports WebRender's async update
7590 if ((aFlags & UPDATE_IS_ASYNC) &&
7591 WebRenderUserData::SupportsAsyncUpdate(this)) {
7592 // WebRender does not use layer, then return nullptr.
7593 return;
7596 if (aFrameDamageRect && aFrameDamageRect->IsEmpty()) {
7597 return;
7600 // In the bug 930056, dialer app startup but not shown on the
7601 // screen because sometimes we don't have any retainned data
7602 // for remote type displayitem and thus Repaint event is not
7603 // triggered. So, always invalidate in this case.
7604 DisplayItemType displayItemKey = aDisplayItemKey;
7605 if (aDisplayItemKey == DisplayItemType::TYPE_REMOTE) {
7606 displayItemKey = DisplayItemType::TYPE_ZERO;
7609 if (aFrameDamageRect) {
7610 InvalidateFrameWithRect(*aFrameDamageRect,
7611 static_cast<uint32_t>(displayItemKey));
7612 } else {
7613 InvalidateFrame(static_cast<uint32_t>(displayItemKey));
7617 static nsRect ComputeEffectsRect(nsIFrame* aFrame, const nsRect& aOverflowRect,
7618 const nsSize& aNewSize) {
7619 nsRect r = aOverflowRect;
7621 if (aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
7622 // For SVG frames, we only need to account for filters.
7623 // TODO: We could also take account of clipPath and mask to reduce the
7624 // ink overflow, but that's not essential.
7625 if (aFrame->StyleEffects()->HasFilters()) {
7626 SetOrUpdateRectValuedProperty(aFrame, nsIFrame::PreEffectsBBoxProperty(),
7628 r = SVGUtils::GetPostFilterInkOverflowRect(aFrame, aOverflowRect);
7630 return r;
7633 // box-shadow
7634 r.UnionRect(r, nsLayoutUtils::GetBoxShadowRectForFrame(aFrame, aNewSize));
7636 // border-image-outset.
7637 // We need to include border-image-outset because it can cause the
7638 // border image to be drawn beyond the border box.
7640 // (1) It's important we not check whether there's a border-image
7641 // since the style hint for a change in border image doesn't cause
7642 // reflow, and that's probably more important than optimizing the
7643 // overflow areas for the silly case of border-image-outset without
7644 // border-image
7645 // (2) It's important that we not check whether the border-image
7646 // is actually loaded, since that would require us to reflow when
7647 // the image loads.
7648 const nsStyleBorder* styleBorder = aFrame->StyleBorder();
7649 nsMargin outsetMargin = styleBorder->GetImageOutset();
7651 if (outsetMargin != nsMargin(0, 0, 0, 0)) {
7652 nsRect outsetRect(nsPoint(0, 0), aNewSize);
7653 outsetRect.Inflate(outsetMargin);
7654 r.UnionRect(r, outsetRect);
7657 // Note that we don't remove the outlineInnerRect if a frame loses outline
7658 // style. That would require an extra property lookup for every frame,
7659 // or a new frame state bit to track whether a property had been stored,
7660 // or something like that. It's not worth doing that here. At most it's
7661 // only one heap-allocated rect per frame and it will be cleaned up when
7662 // the frame dies.
7664 if (SVGIntegrationUtils::UsingOverflowAffectingEffects(aFrame)) {
7665 SetOrUpdateRectValuedProperty(aFrame, nsIFrame::PreEffectsBBoxProperty(),
7667 r = SVGIntegrationUtils::ComputePostEffectsInkOverflowRect(aFrame, r);
7670 return r;
7673 void nsIFrame::MovePositionBy(const nsPoint& aTranslation) {
7674 nsPoint position = GetNormalPosition() + aTranslation;
7676 const nsMargin* computedOffsets = nullptr;
7677 if (IsRelativelyOrStickyPositioned()) {
7678 computedOffsets = GetProperty(nsIFrame::ComputedOffsetProperty());
7680 ReflowInput::ApplyRelativePositioning(
7681 this, computedOffsets ? *computedOffsets : nsMargin(), &position);
7682 SetPosition(position);
7685 nsRect nsIFrame::GetNormalRect() const {
7686 // It might be faster to first check
7687 // StyleDisplay()->IsRelativelyPositionedStyle().
7688 bool hasProperty;
7689 nsPoint normalPosition = GetProperty(NormalPositionProperty(), &hasProperty);
7690 if (hasProperty) {
7691 return nsRect(normalPosition, GetSize());
7693 return GetRect();
7696 nsRect nsIFrame::GetBoundingClientRect() {
7697 return nsLayoutUtils::GetAllInFlowRectsUnion(
7698 this, nsLayoutUtils::GetContainingBlockForClientRect(this),
7699 nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS);
7702 nsPoint nsIFrame::GetPositionIgnoringScrolling() const {
7703 return GetParent() ? GetParent()->GetPositionOfChildIgnoringScrolling(this)
7704 : GetPosition();
7707 nsRect nsIFrame::GetOverflowRect(OverflowType aType) const {
7708 // Note that in some cases the overflow area might not have been
7709 // updated (yet) to reflect any outline set on the frame or the area
7710 // of child frames. That's OK because any reflow that updates these
7711 // areas will invalidate the appropriate area, so any (mis)uses of
7712 // this method will be fixed up.
7714 if (mOverflow.mType == OverflowStorageType::Large) {
7715 // there is an overflow rect, and it's not stored as deltas but as
7716 // a separately-allocated rect
7717 return GetOverflowAreasProperty()->Overflow(aType);
7720 if (aType == OverflowType::Ink &&
7721 mOverflow.mType != OverflowStorageType::None) {
7722 return InkOverflowFromDeltas();
7725 return GetRectRelativeToSelf();
7728 OverflowAreas nsIFrame::GetOverflowAreas() const {
7729 if (mOverflow.mType == OverflowStorageType::Large) {
7730 // there is an overflow rect, and it's not stored as deltas but as
7731 // a separately-allocated rect
7732 return *GetOverflowAreasProperty();
7735 return OverflowAreas(InkOverflowFromDeltas(),
7736 nsRect(nsPoint(0, 0), GetSize()));
7739 OverflowAreas nsIFrame::GetOverflowAreasRelativeToSelf() const {
7740 if (IsTransformed()) {
7741 if (OverflowAreas* preTransformOverflows =
7742 GetProperty(PreTransformOverflowAreasProperty())) {
7743 return *preTransformOverflows;
7746 return GetOverflowAreas();
7749 OverflowAreas nsIFrame::GetOverflowAreasRelativeToParent() const {
7750 return GetOverflowAreas() + GetPosition();
7753 OverflowAreas nsIFrame::GetActualAndNormalOverflowAreasRelativeToParent()
7754 const {
7755 if (MOZ_LIKELY(!IsRelativelyOrStickyPositioned())) {
7756 return GetOverflowAreasRelativeToParent();
7759 const OverflowAreas overflows = GetOverflowAreas();
7760 OverflowAreas actualAndNormalOverflows = overflows + GetPosition();
7761 actualAndNormalOverflows.UnionWith(overflows + GetNormalPosition());
7762 return actualAndNormalOverflows;
7765 nsRect nsIFrame::ScrollableOverflowRectRelativeToParent() const {
7766 return ScrollableOverflowRect() + GetPosition();
7769 nsRect nsIFrame::InkOverflowRectRelativeToParent() const {
7770 return InkOverflowRect() + GetPosition();
7773 nsRect nsIFrame::ScrollableOverflowRectRelativeToSelf() const {
7774 if (IsTransformed()) {
7775 if (OverflowAreas* preTransformOverflows =
7776 GetProperty(PreTransformOverflowAreasProperty())) {
7777 return preTransformOverflows->ScrollableOverflow();
7780 return ScrollableOverflowRect();
7783 nsRect nsIFrame::InkOverflowRectRelativeToSelf() const {
7784 if (IsTransformed()) {
7785 if (OverflowAreas* preTransformOverflows =
7786 GetProperty(PreTransformOverflowAreasProperty())) {
7787 return preTransformOverflows->InkOverflow();
7790 return InkOverflowRect();
7793 nsRect nsIFrame::PreEffectsInkOverflowRect() const {
7794 nsRect* r = GetProperty(nsIFrame::PreEffectsBBoxProperty());
7795 return r ? *r : InkOverflowRectRelativeToSelf();
7798 bool nsIFrame::UpdateOverflow() {
7799 MOZ_ASSERT(FrameMaintainsOverflow(),
7800 "Non-display SVG do not maintain ink overflow rects");
7802 nsRect rect(nsPoint(0, 0), GetSize());
7803 OverflowAreas overflowAreas(rect, rect);
7805 if (!ComputeCustomOverflow(overflowAreas)) {
7806 // If updating overflow wasn't supported by this frame, then it should
7807 // have scheduled any necessary reflows. We can return false to say nothing
7808 // changed, and wait for reflow to correct it.
7809 return false;
7812 UnionChildOverflow(overflowAreas);
7814 if (FinishAndStoreOverflow(overflowAreas, GetSize())) {
7815 nsView* view = GetView();
7816 if (view) {
7817 ReflowChildFlags flags = GetXULLayoutFlags();
7818 if (!(flags & ReflowChildFlags::NoSizeView)) {
7819 // Make sure the frame's view is properly sized.
7820 nsViewManager* vm = view->GetViewManager();
7821 vm->ResizeView(view, overflowAreas.InkOverflow(), true);
7825 return true;
7828 // Frames that combine their 3d transform with their ancestors
7829 // only compute a pre-transform overflow rect, and then contribute
7830 // to the normal overflow rect of the preserve-3d root. Always return
7831 // true here so that we propagate changes up to the root for final
7832 // calculation.
7833 return Combines3DTransformWithAncestors();
7836 /* virtual */
7837 bool nsIFrame::ComputeCustomOverflow(OverflowAreas& aOverflowAreas) {
7838 return true;
7841 /* virtual */
7842 void nsIFrame::UnionChildOverflow(OverflowAreas& aOverflowAreas) {
7843 if (!DoesClipChildrenInBothAxes() &&
7844 !(IsXULCollapsed() && (IsXULBoxFrame() || ::IsXULBoxWrapped(this)))) {
7845 nsLayoutUtils::UnionChildOverflow(this, aOverflowAreas);
7849 // Return true if this form control element's preferred size property (but not
7850 // percentage max size property) contains a percentage value that should be
7851 // resolved against zero when calculating its min-content contribution in the
7852 // corresponding axis.
7854 // For proper replaced elements, the percentage value in both their max size
7855 // property or preferred size property should be resolved against zero. This is
7856 // handled in IsPercentageResolvedAgainstZero().
7857 inline static bool FormControlShrinksForPercentSize(const nsIFrame* aFrame) {
7858 if (!aFrame->IsFrameOfType(nsIFrame::eReplaced)) {
7859 // Quick test to reject most frames.
7860 return false;
7863 LayoutFrameType fType = aFrame->Type();
7864 if (fType == LayoutFrameType::Meter || fType == LayoutFrameType::Progress ||
7865 fType == LayoutFrameType::Range) {
7866 // progress, meter and range do have this shrinking behavior
7867 // FIXME: Maybe these should be nsIFormControlFrame?
7868 return true;
7871 if (!static_cast<nsIFormControlFrame*>(do_QueryFrame(aFrame))) {
7872 // Not a form control. This includes fieldsets, which do not
7873 // shrink.
7874 return false;
7877 if (fType == LayoutFrameType::GfxButtonControl ||
7878 fType == LayoutFrameType::HTMLButtonControl) {
7879 // Buttons don't have this shrinking behavior. (Note that color
7880 // inputs do, even though they inherit from button, so we can't use
7881 // do_QueryFrame here.)
7882 return false;
7885 return true;
7888 bool nsIFrame::IsPercentageResolvedAgainstZero(
7889 const StyleSize& aStyleSize, const StyleMaxSize& aStyleMaxSize) const {
7890 const bool sizeHasPercent = aStyleSize.HasPercent();
7891 return ((sizeHasPercent || aStyleMaxSize.HasPercent()) &&
7892 IsFrameOfType(nsIFrame::eReplacedSizing)) ||
7893 (sizeHasPercent && FormControlShrinksForPercentSize(this));
7896 // Summary of the Cyclic-Percentage Intrinsic Size Contribution Rules:
7898 // Element Type | Replaced | Non-replaced
7899 // Contribution Type | min-content max-content | min-content max-content
7900 // ---------------------------------------------------------------------------
7901 // min size | zero zero | zero zero
7902 // max & preferred size | zero initial | initial initial
7904 // https://drafts.csswg.org/css-sizing-3/#cyclic-percentage-contribution
7905 bool nsIFrame::IsPercentageResolvedAgainstZero(const LengthPercentage& aSize,
7906 SizeProperty aProperty) const {
7907 // Early return to avoid calling the virtual function, IsFrameOfType().
7908 if (aProperty == SizeProperty::MinSize) {
7909 return true;
7912 const bool hasPercentOnReplaced =
7913 aSize.HasPercent() && IsFrameOfType(nsIFrame::eReplacedSizing);
7914 if (aProperty == SizeProperty::MaxSize) {
7915 return hasPercentOnReplaced;
7918 MOZ_ASSERT(aProperty == SizeProperty::Size);
7919 return hasPercentOnReplaced ||
7920 (aSize.HasPercent() && FormControlShrinksForPercentSize(this));
7923 bool nsIFrame::IsBlockWrapper() const {
7924 auto pseudoType = Style()->GetPseudoType();
7925 return pseudoType == PseudoStyleType::mozBlockInsideInlineWrapper ||
7926 pseudoType == PseudoStyleType::buttonContent ||
7927 pseudoType == PseudoStyleType::cellContent ||
7928 pseudoType == PseudoStyleType::columnSpanWrapper;
7931 bool nsIFrame::IsBlockFrameOrSubclass() const {
7932 const nsBlockFrame* thisAsBlock = do_QueryFrame(this);
7933 return !!thisAsBlock;
7936 static nsIFrame* GetNearestBlockContainer(nsIFrame* frame) {
7937 // The block wrappers we use to wrap blocks inside inlines aren't
7938 // described in the CSS spec. We need to make them not be containing
7939 // blocks.
7940 // Since the parent of such a block is either a normal block or
7941 // another such pseudo, this shouldn't cause anything bad to happen.
7942 // Also the anonymous blocks inside table cells are not containing blocks.
7944 // If we ever start skipping table row groups from being containing blocks,
7945 // you need to remove the StickyScrollContainer hack referencing bug 1421660.
7946 while (frame->IsFrameOfType(nsIFrame::eLineParticipant) ||
7947 frame->IsBlockWrapper() ||
7948 // Table rows are not containing blocks either
7949 frame->IsTableRowFrame()) {
7950 frame = frame->GetParent();
7951 NS_ASSERTION(
7952 frame,
7953 "How come we got to the root frame without seeing a containing block?");
7955 return frame;
7958 nsIFrame* nsIFrame::GetContainingBlock(
7959 uint32_t aFlags, const nsStyleDisplay* aStyleDisplay) const {
7960 MOZ_ASSERT(aStyleDisplay == StyleDisplay());
7961 if (!GetParent()) {
7962 return nullptr;
7964 // MathML frames might have absolute positioning style, but they would
7965 // still be in-flow. So we have to check to make sure that the frame
7966 // is really out-of-flow too.
7967 nsIFrame* f;
7968 if (IsAbsolutelyPositioned(aStyleDisplay)) {
7969 f = GetParent(); // the parent is always the containing block
7970 } else {
7971 f = GetNearestBlockContainer(GetParent());
7974 if (aFlags & SKIP_SCROLLED_FRAME && f &&
7975 f->Style()->GetPseudoType() == PseudoStyleType::scrolledContent) {
7976 f = f->GetParent();
7978 return f;
7981 #ifdef DEBUG_FRAME_DUMP
7983 Maybe<uint32_t> nsIFrame::ContentIndexInContainer(const nsIFrame* aFrame) {
7984 if (nsIContent* content = aFrame->GetContent()) {
7985 return content->ComputeIndexInParentContent();
7987 return Nothing();
7990 nsAutoCString nsIFrame::ListTag() const {
7991 nsAutoString tmp;
7992 GetFrameName(tmp);
7994 nsAutoCString tag;
7995 tag += NS_ConvertUTF16toUTF8(tmp);
7996 tag += nsPrintfCString("@%p", static_cast<const void*>(this));
7997 return tag;
8000 std::string nsIFrame::ConvertToString(const LogicalRect& aRect,
8001 const WritingMode aWM, ListFlags aFlags) {
8002 if (aFlags.contains(ListFlag::DisplayInCSSPixels)) {
8003 // Abuse CSSRect to store all LogicalRect's dimensions in CSS pixels.
8004 return ToString(mozilla::CSSRect(CSSPixel::FromAppUnits(aRect.IStart(aWM)),
8005 CSSPixel::FromAppUnits(aRect.BStart(aWM)),
8006 CSSPixel::FromAppUnits(aRect.ISize(aWM)),
8007 CSSPixel::FromAppUnits(aRect.BSize(aWM))));
8009 return ToString(aRect);
8012 std::string nsIFrame::ConvertToString(const LogicalSize& aSize,
8013 const WritingMode aWM, ListFlags aFlags) {
8014 if (aFlags.contains(ListFlag::DisplayInCSSPixels)) {
8015 // Abuse CSSSize to store all LogicalSize's dimensions in CSS pixels.
8016 return ToString(CSSSize(CSSPixel::FromAppUnits(aSize.ISize(aWM)),
8017 CSSPixel::FromAppUnits(aSize.BSize(aWM))));
8019 return ToString(aSize);
8022 // Debugging
8023 void nsIFrame::ListGeneric(nsACString& aTo, const char* aPrefix,
8024 ListFlags aFlags) const {
8025 aTo += aPrefix;
8026 aTo += ListTag();
8027 if (HasView()) {
8028 aTo += nsPrintfCString(" [view=%p]", static_cast<void*>(GetView()));
8030 if (GetParent()) {
8031 aTo += nsPrintfCString(" parent=%p", static_cast<void*>(GetParent()));
8033 if (GetNextSibling()) {
8034 aTo += nsPrintfCString(" next=%p", static_cast<void*>(GetNextSibling()));
8036 if (GetPrevContinuation()) {
8037 bool fluid = GetPrevInFlow() == GetPrevContinuation();
8038 aTo += nsPrintfCString(" prev-%s=%p", fluid ? "in-flow" : "continuation",
8039 static_cast<void*>(GetPrevContinuation()));
8041 if (GetNextContinuation()) {
8042 bool fluid = GetNextInFlow() == GetNextContinuation();
8043 aTo += nsPrintfCString(" next-%s=%p", fluid ? "in-flow" : "continuation",
8044 static_cast<void*>(GetNextContinuation()));
8046 void* IBsibling = GetProperty(IBSplitSibling());
8047 if (IBsibling) {
8048 aTo += nsPrintfCString(" IBSplitSibling=%p", IBsibling);
8050 void* IBprevsibling = GetProperty(IBSplitPrevSibling());
8051 if (IBprevsibling) {
8052 aTo += nsPrintfCString(" IBSplitPrevSibling=%p", IBprevsibling);
8054 if (nsLayoutUtils::FontSizeInflationEnabled(PresContext())) {
8055 if (HasAnyStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT)) {
8056 aTo += nsPrintfCString(" FFR");
8057 if (nsFontInflationData* data =
8058 nsFontInflationData::FindFontInflationDataFor(this)) {
8059 aTo += nsPrintfCString(
8060 ",enabled=%s,UIS=%s", data->InflationEnabled() ? "yes" : "no",
8061 ConvertToString(data->UsableISize(), aFlags).c_str());
8064 if (HasAnyStateBits(NS_FRAME_FONT_INFLATION_CONTAINER)) {
8065 aTo += nsPrintfCString(" FIC");
8067 aTo += nsPrintfCString(" FI=%f", nsLayoutUtils::FontSizeInflationFor(this));
8069 aTo += nsPrintfCString(" %s", ConvertToString(mRect, aFlags).c_str());
8071 mozilla::WritingMode wm = GetWritingMode();
8072 if (wm.IsVertical() || wm.IsBidiRTL()) {
8073 aTo +=
8074 nsPrintfCString(" wm=%s logical-size=(%s)", ToString(wm).c_str(),
8075 ConvertToString(GetLogicalSize(), wm, aFlags).c_str());
8078 nsIFrame* parent = GetParent();
8079 if (parent) {
8080 WritingMode pWM = parent->GetWritingMode();
8081 if (pWM.IsVertical() || pWM.IsBidiRTL()) {
8082 nsSize containerSize = parent->mRect.Size();
8083 LogicalRect lr(pWM, mRect, containerSize);
8084 aTo += nsPrintfCString(" parent-wm=%s cs=(%s) logical-rect=%s",
8085 ToString(pWM).c_str(),
8086 ConvertToString(containerSize, aFlags).c_str(),
8087 ConvertToString(lr, pWM, aFlags).c_str());
8090 nsIFrame* f = const_cast<nsIFrame*>(this);
8091 if (f->HasOverflowAreas()) {
8092 nsRect vo = f->InkOverflowRect();
8093 if (!vo.IsEqualEdges(mRect)) {
8094 aTo += nsPrintfCString(" ink-overflow=%s",
8095 ConvertToString(vo, aFlags).c_str());
8097 nsRect so = f->ScrollableOverflowRect();
8098 if (!so.IsEqualEdges(mRect)) {
8099 aTo += nsPrintfCString(" scr-overflow=%s",
8100 ConvertToString(so, aFlags).c_str());
8103 bool hasNormalPosition;
8104 nsPoint normalPosition = GetNormalPosition(&hasNormalPosition);
8105 if (hasNormalPosition) {
8106 aTo += nsPrintfCString(" normal-position=%s",
8107 ConvertToString(normalPosition, aFlags).c_str());
8109 if (HasProperty(BidiDataProperty())) {
8110 FrameBidiData bidi = GetBidiData();
8111 aTo += nsPrintfCString(" bidi(%d,%d,%d)", bidi.baseLevel.Value(),
8112 bidi.embeddingLevel.Value(),
8113 bidi.precedingControl.Value());
8115 if (IsTransformed()) {
8116 aTo += nsPrintfCString(" transformed");
8118 if (ChildrenHavePerspective()) {
8119 aTo += nsPrintfCString(" perspective");
8121 if (Extend3DContext()) {
8122 aTo += nsPrintfCString(" extend-3d");
8124 if (Combines3DTransformWithAncestors()) {
8125 aTo += nsPrintfCString(" combines-3d-transform-with-ancestors");
8127 if (mContent) {
8128 aTo += nsPrintfCString(" [content=%p]", static_cast<void*>(mContent));
8130 aTo += nsPrintfCString(" [cs=%p", static_cast<void*>(mComputedStyle));
8131 if (mComputedStyle) {
8132 auto pseudoType = mComputedStyle->GetPseudoType();
8133 aTo += ToString(pseudoType).c_str();
8135 aTo += "]";
8137 if (IsFrameModified()) {
8138 aTo += nsPrintfCString(" modified");
8141 if (HasModifiedDescendants()) {
8142 aTo += nsPrintfCString(" has-modified-descendants");
8146 void nsIFrame::List(FILE* out, const char* aPrefix, ListFlags aFlags) const {
8147 nsCString str;
8148 ListGeneric(str, aPrefix, aFlags);
8149 fprintf_stderr(out, "%s\n", str.get());
8152 void nsIFrame::ListTextRuns(FILE* out) const {
8153 nsTHashSet<const void*> seen;
8154 ListTextRuns(out, seen);
8157 void nsIFrame::ListTextRuns(FILE* out, nsTHashSet<const void*>& aSeen) const {
8158 for (const auto& childList : ChildLists()) {
8159 for (const nsIFrame* kid : childList.mList) {
8160 kid->ListTextRuns(out, aSeen);
8165 void nsIFrame::ListMatchedRules(FILE* out, const char* aPrefix) const {
8166 nsTArray<const RawServoStyleRule*> rawRuleList;
8167 Servo_ComputedValues_GetStyleRuleList(mComputedStyle, &rawRuleList);
8168 for (const RawServoStyleRule* rawRule : rawRuleList) {
8169 nsAutoCString ruleText;
8170 Servo_StyleRule_GetCssText(rawRule, &ruleText);
8171 fprintf_stderr(out, "%s%s\n", aPrefix, ruleText.get());
8175 void nsIFrame::ListWithMatchedRules(FILE* out, const char* aPrefix) const {
8176 fprintf_stderr(out, "%s%s\n", aPrefix, ListTag().get());
8178 nsCString rulePrefix;
8179 rulePrefix += aPrefix;
8180 rulePrefix += " ";
8181 ListMatchedRules(out, rulePrefix.get());
8184 nsresult nsIFrame::GetFrameName(nsAString& aResult) const {
8185 return MakeFrameName(u"Frame"_ns, aResult);
8188 nsresult nsIFrame::MakeFrameName(const nsAString& aType,
8189 nsAString& aResult) const {
8190 aResult = aType;
8191 if (mContent && !mContent->IsText()) {
8192 nsAutoString buf;
8193 mContent->NodeInfo()->NameAtom()->ToString(buf);
8194 if (nsAtom* id = mContent->GetID()) {
8195 buf.AppendLiteral(" id=");
8196 buf.Append(nsDependentAtomString(id));
8198 if (IsSubDocumentFrame()) {
8199 nsAutoString src;
8200 mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::src, src);
8201 buf.AppendLiteral(" src=");
8202 buf.Append(src);
8204 aResult.Append('(');
8205 aResult.Append(buf);
8206 aResult.Append(')');
8208 aResult.Append('(');
8209 Maybe<uint32_t> index = ContentIndexInContainer(this);
8210 if (index.isSome()) {
8211 aResult.AppendInt(*index);
8212 } else {
8213 aResult.AppendInt(-1);
8215 aResult.Append(')');
8216 return NS_OK;
8219 void nsIFrame::DumpFrameTree() const {
8220 PresShell()->GetRootFrame()->List(stderr);
8223 void nsIFrame::DumpFrameTreeInCSSPixels() const {
8224 PresShell()->GetRootFrame()->List(stderr, "", ListFlag::DisplayInCSSPixels);
8227 void nsIFrame::DumpFrameTreeLimited() const { List(stderr); }
8228 void nsIFrame::DumpFrameTreeLimitedInCSSPixels() const {
8229 List(stderr, "", ListFlag::DisplayInCSSPixels);
8232 #endif
8234 bool nsIFrame::IsVisibleForPainting() { return StyleVisibility()->IsVisible(); }
8236 bool nsIFrame::IsVisibleOrCollapsedForPainting() {
8237 return StyleVisibility()->IsVisibleOrCollapsed();
8240 /* virtual */
8241 bool nsIFrame::IsEmpty() { return false; }
8243 bool nsIFrame::CachedIsEmpty() {
8244 MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_DIRTY),
8245 "Must only be called on reflowed lines");
8246 return IsEmpty();
8249 /* virtual */
8250 bool nsIFrame::IsSelfEmpty() { return false; }
8252 nsresult nsIFrame::GetSelectionController(nsPresContext* aPresContext,
8253 nsISelectionController** aSelCon) {
8254 if (!aPresContext || !aSelCon) return NS_ERROR_INVALID_ARG;
8256 nsIFrame* frame = this;
8257 while (frame && frame->HasAnyStateBits(NS_FRAME_INDEPENDENT_SELECTION)) {
8258 nsITextControlFrame* tcf = do_QueryFrame(frame);
8259 if (tcf) {
8260 return tcf->GetOwnedSelectionController(aSelCon);
8262 frame = frame->GetParent();
8265 *aSelCon = do_AddRef(aPresContext->PresShell()).take();
8266 return NS_OK;
8269 already_AddRefed<nsFrameSelection> nsIFrame::GetFrameSelection() {
8270 RefPtr<nsFrameSelection> fs =
8271 const_cast<nsFrameSelection*>(GetConstFrameSelection());
8272 return fs.forget();
8275 const nsFrameSelection* nsIFrame::GetConstFrameSelection() const {
8276 nsIFrame* frame = const_cast<nsIFrame*>(this);
8277 while (frame && frame->HasAnyStateBits(NS_FRAME_INDEPENDENT_SELECTION)) {
8278 nsITextControlFrame* tcf = do_QueryFrame(frame);
8279 if (tcf) {
8280 return tcf->GetOwnedFrameSelection();
8282 frame = frame->GetParent();
8285 return PresShell()->ConstFrameSelection();
8288 bool nsIFrame::IsFrameSelected() const {
8289 NS_ASSERTION(!GetContent() || GetContent()->IsMaybeSelected(),
8290 "use the public IsSelected() instead");
8291 return GetContent()->IsSelected(0, GetContent()->GetChildCount());
8294 nsresult nsIFrame::GetPointFromOffset(int32_t inOffset, nsPoint* outPoint) {
8295 MOZ_ASSERT(outPoint != nullptr, "Null parameter");
8296 nsRect contentRect = GetContentRectRelativeToSelf();
8297 nsPoint pt = contentRect.TopLeft();
8298 if (mContent) {
8299 nsIContent* newContent = mContent->GetParent();
8300 if (newContent) {
8301 const int32_t newOffset = newContent->ComputeIndexOf_Deprecated(mContent);
8303 // Find the direction of the frame from the EmbeddingLevelProperty,
8304 // which is the resolved bidi level set in
8305 // nsBidiPresUtils::ResolveParagraph (odd levels = right-to-left).
8306 // If the embedding level isn't set, just use the CSS direction
8307 // property.
8308 bool hasBidiData;
8309 FrameBidiData bidiData = GetProperty(BidiDataProperty(), &hasBidiData);
8310 bool isRTL = hasBidiData
8311 ? bidiData.embeddingLevel.IsRTL()
8312 : StyleVisibility()->mDirection == StyleDirection::Rtl;
8313 if ((!isRTL && inOffset > newOffset) ||
8314 (isRTL && inOffset <= newOffset)) {
8315 pt = contentRect.TopRight();
8319 *outPoint = pt;
8320 return NS_OK;
8323 nsresult nsIFrame::GetCharacterRectsInRange(int32_t aInOffset, int32_t aLength,
8324 nsTArray<nsRect>& aOutRect) {
8325 /* no text */
8326 return NS_ERROR_FAILURE;
8329 nsresult nsIFrame::GetChildFrameContainingOffset(int32_t inContentOffset,
8330 bool inHint,
8331 int32_t* outFrameContentOffset,
8332 nsIFrame** outChildFrame) {
8333 MOZ_ASSERT(outChildFrame && outFrameContentOffset, "Null parameter");
8334 *outFrameContentOffset = (int32_t)inHint;
8335 // the best frame to reflect any given offset would be a visible frame if
8336 // possible i.e. we are looking for a valid frame to place the blinking caret
8337 nsRect rect = GetRect();
8338 if (!rect.width || !rect.height) {
8339 // if we have a 0 width or height then lets look for another frame that
8340 // possibly has the same content. If we have no frames in flow then just
8341 // let us return 'this' frame
8342 nsIFrame* nextFlow = GetNextInFlow();
8343 if (nextFlow)
8344 return nextFlow->GetChildFrameContainingOffset(
8345 inContentOffset, inHint, outFrameContentOffset, outChildFrame);
8347 *outChildFrame = this;
8348 return NS_OK;
8352 // What I've pieced together about this routine:
8353 // Starting with a block frame (from which a line frame can be gotten)
8354 // and a line number, drill down and get the first/last selectable
8355 // frame on that line, depending on aPos->mDirection.
8356 // aOutSideLimit != 0 means ignore aLineStart, instead work from
8357 // the end (if > 0) or beginning (if < 0).
8359 nsresult nsIFrame::GetNextPrevLineFromeBlockFrame(nsPresContext* aPresContext,
8360 nsPeekOffsetStruct* aPos,
8361 nsIFrame* aBlockFrame,
8362 int32_t aLineStart,
8363 int8_t aOutSideLimit) {
8364 // magic numbers aLineStart will be -1 for end of block 0 will be start of
8365 // block
8366 if (!aBlockFrame || !aPos) return NS_ERROR_NULL_POINTER;
8368 aPos->mResultFrame = nullptr;
8369 aPos->mResultContent = nullptr;
8370 aPos->mAttach = aPos->mDirection == eDirNext ? CARET_ASSOCIATE_AFTER
8371 : CARET_ASSOCIATE_BEFORE;
8373 nsAutoLineIterator it = aBlockFrame->GetLineIterator();
8374 if (!it) {
8375 return NS_ERROR_FAILURE;
8377 int32_t searchingLine = aLineStart;
8378 int32_t countLines = it->GetNumLines();
8379 if (aOutSideLimit > 0) // start at end
8380 searchingLine = countLines;
8381 else if (aOutSideLimit < 0) // start at beginning
8382 searchingLine = -1; //"next" will be 0
8383 else if ((aPos->mDirection == eDirPrevious && searchingLine == 0) ||
8384 (aPos->mDirection == eDirNext &&
8385 searchingLine >= (countLines - 1))) {
8386 // we need to jump to new block frame.
8387 return NS_ERROR_FAILURE;
8389 nsIFrame* resultFrame = nullptr;
8390 nsIFrame* farStoppingFrame = nullptr; // we keep searching until we find a
8391 // "this" frame then we go to next line
8392 nsIFrame* nearStoppingFrame = nullptr; // if we are backing up from edge,
8393 // stop here
8394 nsIFrame* firstFrame;
8395 nsIFrame* lastFrame;
8396 bool isBeforeFirstFrame, isAfterLastFrame;
8397 bool found = false;
8399 nsresult result = NS_OK;
8400 while (!found) {
8401 if (aPos->mDirection == eDirPrevious)
8402 searchingLine--;
8403 else
8404 searchingLine++;
8405 if ((aPos->mDirection == eDirPrevious && searchingLine < 0) ||
8406 (aPos->mDirection == eDirNext && searchingLine >= countLines)) {
8407 // we need to jump to new block frame.
8408 return NS_ERROR_FAILURE;
8410 auto line = it->GetLine(searchingLine).unwrap();
8411 if (!line.mNumFramesOnLine) {
8412 continue;
8414 lastFrame = firstFrame = line.mFirstFrameOnLine;
8415 for (int32_t lineFrameCount = line.mNumFramesOnLine; lineFrameCount > 1;
8416 lineFrameCount--) {
8417 lastFrame = lastFrame->GetNextSibling();
8418 if (!lastFrame) {
8419 NS_ERROR("GetLine promised more frames than could be found");
8420 return NS_ERROR_FAILURE;
8423 GetLastLeaf(&lastFrame);
8425 if (aPos->mDirection == eDirNext) {
8426 nearStoppingFrame = firstFrame;
8427 farStoppingFrame = lastFrame;
8428 } else {
8429 nearStoppingFrame = lastFrame;
8430 farStoppingFrame = firstFrame;
8432 nsPoint offset;
8433 nsView* view; // used for call of get offset from view
8434 aBlockFrame->GetOffsetFromView(offset, &view);
8435 nsPoint newDesiredPos =
8436 aPos->mDesiredCaretPos -
8437 offset; // get desired position into blockframe coords
8438 result = it->FindFrameAt(searchingLine, newDesiredPos, &resultFrame,
8439 &isBeforeFirstFrame, &isAfterLastFrame);
8440 if (NS_FAILED(result)) {
8441 continue;
8444 if (resultFrame) {
8445 // check to see if this is ANOTHER blockframe inside the other one if so
8446 // then call into its lines
8447 if (resultFrame->CanProvideLineIterator()) {
8448 aPos->mResultFrame = resultFrame;
8449 return NS_OK;
8451 // resultFrame is not a block frame
8452 result = NS_ERROR_FAILURE;
8454 nsCOMPtr<nsIFrameEnumerator> frameTraversal;
8455 result = NS_NewFrameTraversal(getter_AddRefs(frameTraversal),
8456 aPresContext, resultFrame, ePostOrder,
8457 false, // aVisual
8458 aPos->mScrollViewStop,
8459 false, // aFollowOOFs
8460 false // aSkipPopupChecks
8462 if (NS_FAILED(result)) return result;
8464 auto FoundValidFrame = [aPos](const ContentOffsets& aOffsets,
8465 const nsIFrame* aFrame) {
8466 if (!aOffsets.content) {
8467 return false;
8469 if (!aFrame->IsSelectable(nullptr)) {
8470 return false;
8472 if (aPos->mForceEditableRegion && !aOffsets.content->IsEditable()) {
8473 return false;
8475 return true;
8478 nsIFrame* storeOldResultFrame = resultFrame;
8479 while (!found) {
8480 nsPoint point;
8481 nsRect tempRect = resultFrame->GetRect();
8482 nsPoint offset;
8483 nsView* view; // used for call of get offset from view
8484 resultFrame->GetOffsetFromView(offset, &view);
8485 if (!view) {
8486 return NS_ERROR_FAILURE;
8488 if (resultFrame->GetWritingMode().IsVertical()) {
8489 point.y = aPos->mDesiredCaretPos.y;
8490 point.x = tempRect.width + offset.x;
8491 } else {
8492 point.y = tempRect.height + offset.y;
8493 point.x = aPos->mDesiredCaretPos.x;
8496 // special check. if we allow non-text selection then we can allow a hit
8497 // location to fall before a table. otherwise there is no way to get and
8498 // click signal to fall before a table (it being a line iterator itself)
8499 mozilla::PresShell* presShell = aPresContext->GetPresShell();
8500 if (!presShell) {
8501 return NS_ERROR_FAILURE;
8503 int16_t isEditor = presShell->GetSelectionFlags();
8504 isEditor = isEditor == nsISelectionDisplay::DISPLAY_ALL;
8505 if (isEditor) {
8506 if (resultFrame->IsTableWrapperFrame()) {
8507 if (((point.x - offset.x + tempRect.x) < 0) ||
8508 ((point.x - offset.x + tempRect.x) >
8509 tempRect.width)) // off left/right side
8511 nsIContent* content = resultFrame->GetContent();
8512 if (content) {
8513 nsIContent* parent = content->GetParent();
8514 if (parent) {
8515 aPos->mResultContent = parent;
8516 aPos->mContentOffset =
8517 parent->ComputeIndexOf_Deprecated(content);
8518 aPos->mAttach = CARET_ASSOCIATE_BEFORE;
8519 if ((point.x - offset.x + tempRect.x) > tempRect.width) {
8520 aPos->mContentOffset++; // go to end of this frame
8521 aPos->mAttach = CARET_ASSOCIATE_AFTER;
8523 // result frame is the result frames parent.
8524 aPos->mResultFrame = resultFrame->GetParent();
8525 return NS_POSITION_BEFORE_TABLE;
8532 if (!resultFrame->HasView()) {
8533 nsView* view;
8534 nsPoint offset;
8535 resultFrame->GetOffsetFromView(offset, &view);
8536 ContentOffsets offsets =
8537 resultFrame->GetContentOffsetsFromPoint(point - offset);
8538 aPos->mResultContent = offsets.content;
8539 aPos->mContentOffset = offsets.offset;
8540 aPos->mAttach = offsets.associate;
8541 if (FoundValidFrame(offsets, resultFrame)) {
8542 found = true;
8543 break;
8547 if (aPos->mDirection == eDirPrevious &&
8548 (resultFrame == farStoppingFrame))
8549 break;
8550 if (aPos->mDirection == eDirNext && (resultFrame == nearStoppingFrame))
8551 break;
8552 // always try previous on THAT line if that fails go the other way
8553 resultFrame = frameTraversal->Traverse(/* aForward = */ false);
8554 if (!resultFrame) return NS_ERROR_FAILURE;
8557 if (!found) {
8558 resultFrame = storeOldResultFrame;
8560 result = NS_NewFrameTraversal(getter_AddRefs(frameTraversal),
8561 aPresContext, resultFrame, eLeaf,
8562 false, // aVisual
8563 aPos->mScrollViewStop,
8564 false, // aFollowOOFs
8565 false // aSkipPopupChecks
8568 while (!found) {
8569 nsPoint point = aPos->mDesiredCaretPos;
8570 nsView* view;
8571 nsPoint offset;
8572 resultFrame->GetOffsetFromView(offset, &view);
8573 ContentOffsets offsets =
8574 resultFrame->GetContentOffsetsFromPoint(point - offset);
8575 aPos->mResultContent = offsets.content;
8576 aPos->mContentOffset = offsets.offset;
8577 aPos->mAttach = offsets.associate;
8578 if (FoundValidFrame(offsets, resultFrame)) {
8579 found = true;
8580 if (resultFrame == farStoppingFrame)
8581 aPos->mAttach = CARET_ASSOCIATE_BEFORE;
8582 else
8583 aPos->mAttach = CARET_ASSOCIATE_AFTER;
8584 break;
8586 if (aPos->mDirection == eDirPrevious &&
8587 (resultFrame == nearStoppingFrame))
8588 break;
8589 if (aPos->mDirection == eDirNext && (resultFrame == farStoppingFrame))
8590 break;
8591 // previous didnt work now we try "next"
8592 nsIFrame* tempFrame = frameTraversal->Traverse(/* aForward = */ true);
8593 if (!tempFrame) break;
8594 resultFrame = tempFrame;
8596 aPos->mResultFrame = resultFrame;
8597 } else {
8598 // we need to jump to new block frame.
8599 aPos->mAmount = eSelectLine;
8600 aPos->mStartOffset = 0;
8601 aPos->mAttach = aPos->mDirection == eDirNext ? CARET_ASSOCIATE_BEFORE
8602 : CARET_ASSOCIATE_AFTER;
8603 if (aPos->mDirection == eDirPrevious)
8604 aPos->mStartOffset = -1; // start from end
8605 return aBlockFrame->PeekOffset(aPos);
8608 return NS_OK;
8611 nsIFrame::CaretPosition nsIFrame::GetExtremeCaretPosition(bool aStart) {
8612 CaretPosition result;
8614 FrameTarget targetFrame = DrillDownToSelectionFrame(this, !aStart, 0);
8615 FrameContentRange range = GetRangeForFrame(targetFrame.frame);
8616 result.mResultContent = range.content;
8617 result.mContentOffset = aStart ? range.start : range.end;
8618 return result;
8621 // If this is a preformatted text frame, see if it ends with a newline
8622 static nsContentAndOffset FindLineBreakInText(nsIFrame* aFrame,
8623 nsDirection aDirection) {
8624 nsContentAndOffset result;
8626 if (aFrame->IsGeneratedContentFrame() ||
8627 !aFrame->HasSignificantTerminalNewline()) {
8628 return result;
8631 int32_t endOffset = aFrame->GetOffsets().second;
8632 result.mContent = aFrame->GetContent();
8633 result.mOffset = endOffset - (aDirection == eDirPrevious ? 0 : 1);
8634 return result;
8637 // Find the first (or last) descendant of the given frame
8638 // which is either a block-level frame or a BRFrame, or some other kind of break
8639 // which stops the line.
8640 static nsContentAndOffset FindLineBreakingFrame(nsIFrame* aFrame,
8641 nsDirection aDirection) {
8642 nsContentAndOffset result;
8644 if (aFrame->IsGeneratedContentFrame()) {
8645 return result;
8648 // Treat form controls as inline leaves
8649 // XXX we really need a way to determine whether a frame is inline-level
8650 if (static_cast<nsIFormControlFrame*>(do_QueryFrame(aFrame))) {
8651 return result;
8654 // Check the frame itself
8655 // Fall through block-in-inline split frames because their mContent is
8656 // the content of the inline frames they were created from. The
8657 // first/last child of such frames is the real block frame we're
8658 // looking for.
8659 if ((aFrame->IsBlockOutside() &&
8660 !aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) ||
8661 aFrame->IsBrFrame()) {
8662 nsIContent* content = aFrame->GetContent();
8663 result.mContent = content->GetParent();
8664 // In some cases (bug 310589, bug 370174) we end up here with a null
8665 // content. This probably shouldn't ever happen, but since it sometimes
8666 // does, we want to avoid crashing here.
8667 NS_ASSERTION(result.mContent, "Unexpected orphan content");
8668 if (result.mContent) {
8669 result.mOffset = result.mContent->ComputeIndexOf_Deprecated(content) +
8670 (aDirection == eDirPrevious ? 1 : 0);
8672 return result;
8675 result = FindLineBreakInText(aFrame, aDirection);
8676 if (result.mContent) {
8677 return result;
8680 // Iterate over children and call ourselves recursively
8681 if (aDirection == eDirPrevious) {
8682 nsIFrame* child =
8683 aFrame->GetChildList(nsIFrame::kPrincipalList).LastChild();
8684 while (child && !result.mContent) {
8685 result = FindLineBreakingFrame(child, aDirection);
8686 child = child->GetPrevSibling();
8688 } else { // eDirNext
8689 nsIFrame* child = aFrame->PrincipalChildList().FirstChild();
8690 while (child && !result.mContent) {
8691 result = FindLineBreakingFrame(child, aDirection);
8692 child = child->GetNextSibling();
8695 return result;
8698 nsresult nsIFrame::PeekOffsetForParagraph(nsPeekOffsetStruct* aPos) {
8699 nsIFrame* frame = this;
8700 nsContentAndOffset blockFrameOrBR;
8701 blockFrameOrBR.mContent = nullptr;
8702 bool reachedLimit = frame->IsBlockOutside() || IsEditingHost(frame);
8704 auto traverse = [&aPos](nsIFrame* current) {
8705 return aPos->mDirection == eDirPrevious ? current->GetPrevSibling()
8706 : current->GetNextSibling();
8709 // Go through containing frames until reaching a block frame.
8710 // In each step, search the previous (or next) siblings for the closest
8711 // "stop frame" (a block frame or a BRFrame).
8712 // If found, set it to be the selection boundary and abort.
8713 while (!reachedLimit) {
8714 nsIFrame* parent = frame->GetParent();
8715 // Treat a frame associated with the root content as if it were a block
8716 // frame.
8717 if (!frame->mContent || !frame->mContent->GetParent()) {
8718 reachedLimit = true;
8719 break;
8722 if (aPos->mDirection == eDirNext) {
8723 // Try to find our own line-break before looking at our siblings.
8724 blockFrameOrBR = FindLineBreakInText(frame, eDirNext);
8727 nsIFrame* sibling = traverse(frame);
8728 while (sibling && !blockFrameOrBR.mContent) {
8729 blockFrameOrBR = FindLineBreakingFrame(sibling, aPos->mDirection);
8730 sibling = traverse(sibling);
8732 if (blockFrameOrBR.mContent) {
8733 aPos->mResultContent = blockFrameOrBR.mContent;
8734 aPos->mContentOffset = blockFrameOrBR.mOffset;
8735 break;
8737 frame = parent;
8738 reachedLimit = frame && (frame->IsBlockOutside() || IsEditingHost(frame));
8741 if (reachedLimit) { // no "stop frame" found
8742 aPos->mResultContent = frame->GetContent();
8743 if (aPos->mDirection == eDirPrevious) {
8744 aPos->mContentOffset = 0;
8745 } else if (aPos->mResultContent) {
8746 aPos->mContentOffset = aPos->mResultContent->GetChildCount();
8749 return NS_OK;
8752 // Determine movement direction relative to frame
8753 static bool IsMovingInFrameDirection(const nsIFrame* frame,
8754 nsDirection aDirection, bool aVisual) {
8755 bool isReverseDirection =
8756 aVisual && nsBidiPresUtils::IsReversedDirectionFrame(frame);
8757 return aDirection == (isReverseDirection ? eDirPrevious : eDirNext);
8760 // Determines "are we looking for a boundary between whitespace and
8761 // non-whitespace (in the direction we're moving in)". It is true when moving
8762 // forward and looking for a beginning of a word, or when moving backwards and
8763 // looking for an end of a word.
8764 static bool ShouldWordSelectionEatSpace(const nsPeekOffsetStruct& aPos) {
8765 if (aPos.mWordMovementType != eDefaultBehavior) {
8766 // aPos->mWordMovementType possible values:
8767 // eEndWord: eat the space if we're moving backwards
8768 // eStartWord: eat the space if we're moving forwards
8769 return (aPos.mWordMovementType == eEndWord) ==
8770 (aPos.mDirection == eDirPrevious);
8772 // Use the hidden preference which is based on operating system
8773 // behavior. This pref only affects whether moving forward by word
8774 // should go to the end of this word or start of the next word. When
8775 // going backwards, the start of the word is always used, on every
8776 // operating system.
8777 return aPos.mDirection == eDirNext &&
8778 StaticPrefs::layout_word_select_eat_space_to_next_word();
8781 enum class OffsetIsAtLineEdge : bool { No, Yes };
8783 static void SetPeekResultFromFrame(nsPeekOffsetStruct& aPos, nsIFrame* aFrame,
8784 int32_t aOffset,
8785 OffsetIsAtLineEdge aAtLineEdge) {
8786 FrameContentRange range = GetRangeForFrame(aFrame);
8787 aPos.mResultFrame = aFrame;
8788 aPos.mResultContent = range.content;
8789 // Output offset is relative to content, not frame
8790 aPos.mContentOffset =
8791 aOffset < 0 ? range.end + aOffset + 1 : range.start + aOffset;
8792 if (aAtLineEdge == OffsetIsAtLineEdge::Yes) {
8793 aPos.mAttach = aPos.mContentOffset == range.start ? CARET_ASSOCIATE_AFTER
8794 : CARET_ASSOCIATE_BEFORE;
8798 void nsIFrame::SelectablePeekReport::TransferTo(
8799 nsPeekOffsetStruct& aPos) const {
8800 return SetPeekResultFromFrame(aPos, mFrame, mOffset, OffsetIsAtLineEdge::No);
8803 nsIFrame::SelectablePeekReport::SelectablePeekReport(
8804 const mozilla::GenericErrorResult<nsresult>&& aErr) {
8805 MOZ_ASSERT(NS_FAILED(aErr.operator nsresult()));
8806 // Return an empty report
8809 nsresult nsIFrame::PeekOffsetForCharacter(nsPeekOffsetStruct* aPos,
8810 int32_t aOffset) {
8811 SelectablePeekReport current{this, aOffset};
8813 nsIFrame::FrameSearchResult peekSearchState = CONTINUE;
8815 while (peekSearchState != FOUND) {
8816 bool movingInFrameDirection = IsMovingInFrameDirection(
8817 current.mFrame, aPos->mDirection, aPos->mVisual);
8819 if (current.mJumpedLine) {
8820 // If we jumped lines, it's as if we found a character, but we still need
8821 // to eat non-renderable content on the new line.
8822 peekSearchState = current.PeekOffsetNoAmount(movingInFrameDirection);
8823 } else {
8824 PeekOffsetCharacterOptions options;
8825 options.mRespectClusters = aPos->mAmount == eSelectCluster;
8826 peekSearchState =
8827 current.PeekOffsetCharacter(movingInFrameDirection, options);
8830 current.mMovedOverNonSelectableText |=
8831 peekSearchState == CONTINUE_UNSELECTABLE;
8833 if (peekSearchState != FOUND) {
8834 SelectablePeekReport next = current.mFrame->GetFrameFromDirection(*aPos);
8835 if (next.Failed()) {
8836 return NS_ERROR_FAILURE;
8838 next.mJumpedLine |= current.mJumpedLine;
8839 next.mMovedOverNonSelectableText |= current.mMovedOverNonSelectableText;
8840 next.mHasSelectableFrame |= current.mHasSelectableFrame;
8841 current = next;
8844 // Found frame, but because we moved over non selectable text we want
8845 // the offset to be at the frame edge. Note that if we are extending the
8846 // selection, this doesn't matter.
8847 if (peekSearchState == FOUND && current.mMovedOverNonSelectableText &&
8848 (!aPos->mExtend || current.mHasSelectableFrame)) {
8849 auto [start, end] = current.mFrame->GetOffsets();
8850 current.mOffset = aPos->mDirection == eDirNext ? 0 : end - start;
8854 // Set outputs
8855 current.TransferTo(*aPos);
8856 // If we're dealing with a text frame and moving backward positions us at
8857 // the end of that line, decrease the offset by one to make sure that
8858 // we're placed before the linefeed character on the previous line.
8859 if (current.mOffset < 0 && current.mJumpedLine &&
8860 aPos->mDirection == eDirPrevious &&
8861 current.mFrame->HasSignificantTerminalNewline() &&
8862 !current.mIgnoredBrFrame) {
8863 --aPos->mContentOffset;
8865 return NS_OK;
8868 nsresult nsIFrame::PeekOffsetForWord(nsPeekOffsetStruct* aPos,
8869 int32_t aOffset) {
8870 SelectablePeekReport current{this, aOffset};
8871 bool shouldStopAtHardBreak =
8872 aPos->mWordMovementType == eDefaultBehavior &&
8873 StaticPrefs::layout_word_select_eat_space_to_next_word();
8874 bool wordSelectEatSpace = ShouldWordSelectionEatSpace(*aPos);
8876 PeekWordState state;
8877 while (true) {
8878 bool movingInFrameDirection = IsMovingInFrameDirection(
8879 current.mFrame, aPos->mDirection, aPos->mVisual);
8881 FrameSearchResult searchResult = current.mFrame->PeekOffsetWord(
8882 movingInFrameDirection, wordSelectEatSpace, aPos->mIsKeyboardSelect,
8883 &current.mOffset, &state, aPos->mTrimSpaces);
8884 if (searchResult == FOUND) {
8885 break;
8888 SelectablePeekReport next = current.mFrame->GetFrameFromDirection(*aPos);
8889 if (next.Failed()) {
8890 // If we've crossed the line boundary, check to make sure that we
8891 // have not consumed a trailing newline as whitespace if it's
8892 // significant.
8893 if (next.mJumpedLine && wordSelectEatSpace &&
8894 current.mFrame->HasSignificantTerminalNewline() &&
8895 current.mFrame->StyleText()->mWhiteSpace !=
8896 StyleWhiteSpace::PreLine) {
8897 current.mOffset -= 1;
8899 break;
8902 if (next.mJumpedLine && !wordSelectEatSpace && state.mSawBeforeType) {
8903 // We can't jump lines if we're looking for whitespace following
8904 // non-whitespace, and we already encountered non-whitespace.
8905 break;
8908 if (shouldStopAtHardBreak && next.mJumpedHardBreak) {
8910 * Prev, always: Jump and stop right there
8911 * Next, saw inline: just stop
8912 * Next, no inline: Jump and consume whitespaces
8914 if (aPos->mDirection == eDirPrevious) {
8915 // Try moving to the previous line if exists
8916 current.TransferTo(*aPos);
8917 current.mFrame->PeekOffsetForCharacter(aPos, current.mOffset);
8918 return NS_OK;
8920 if (state.mSawInlineCharacter || current.mJumpedHardBreak) {
8921 if (current.mFrame->HasSignificantTerminalNewline()) {
8922 current.mOffset -= 1;
8924 current.TransferTo(*aPos);
8925 return NS_OK;
8927 // Mark the state as whitespace and continue
8928 state.Update(false, true);
8931 if (next.mJumpedLine) {
8932 state.mContext.Truncate();
8934 current = next;
8935 // Jumping a line is equivalent to encountering whitespace
8936 // This affects only when it already met an actual character
8937 if (wordSelectEatSpace && next.mJumpedLine) {
8938 state.SetSawBeforeType();
8942 // Set outputs
8943 current.TransferTo(*aPos);
8944 return NS_OK;
8947 nsresult nsIFrame::PeekOffsetForLine(nsPeekOffsetStruct* aPos) {
8948 nsIFrame* blockFrame = this;
8949 nsresult result = NS_ERROR_FAILURE;
8951 while (NS_FAILED(result)) {
8952 auto [newBlock, lineFrame] =
8953 blockFrame->GetContainingBlockForLine(aPos->mScrollViewStop);
8954 if (!newBlock) {
8955 return NS_ERROR_FAILURE;
8957 blockFrame = newBlock;
8958 nsAutoLineIterator iter = blockFrame->GetLineIterator();
8959 int32_t thisLine = iter->FindLineContaining(lineFrame);
8960 if (NS_WARN_IF(thisLine < 0)) {
8961 return NS_ERROR_FAILURE;
8964 int edgeCase = 0; // no edge case. this should look at thisLine
8966 bool doneLooping = false; // tells us when no more block frames hit.
8967 // this part will find a frame or a block frame. if it's a block frame
8968 // it will "drill down" to find a viable frame or it will return an
8969 // error.
8970 nsIFrame* lastFrame = this;
8971 do {
8972 result = nsIFrame::GetNextPrevLineFromeBlockFrame(
8973 PresContext(), aPos, blockFrame, thisLine,
8974 edgeCase); // start from thisLine
8976 // we came back to same spot! keep going
8977 if (NS_SUCCEEDED(result) &&
8978 (!aPos->mResultFrame || aPos->mResultFrame == lastFrame)) {
8979 aPos->mResultFrame = nullptr;
8980 if (aPos->mDirection == eDirPrevious) {
8981 thisLine--;
8982 } else {
8983 thisLine++;
8985 } else { // if failure or success with different frame.
8986 doneLooping = true; // do not continue with while loop
8989 lastFrame = aPos->mResultFrame; // set last frame
8991 // make sure block element is not the same as the one we had before
8992 if (NS_SUCCEEDED(result) && aPos->mResultFrame &&
8993 blockFrame != aPos->mResultFrame) {
8994 /* SPECIAL CHECK FOR TABLE NAVIGATION
8995 tables need to navigate also and the frame that supports it is
8996 nsTableRowGroupFrame which is INSIDE nsTableWrapperFrame.
8997 If we have stumbled onto an nsTableWrapperFrame we need to drill
8998 into nsTableRowGroup if we hit a header or footer that's ok just
8999 go into them.
9001 bool searchTableBool = false;
9002 if (aPos->mResultFrame->IsTableWrapperFrame() ||
9003 aPos->mResultFrame->IsTableCellFrame()) {
9004 nsIFrame* frame =
9005 aPos->mResultFrame->PrincipalChildList().FirstChild();
9006 // got the table frame now
9007 // ok time to drill down to find iterator
9008 while (frame) {
9009 if (frame->CanProvideLineIterator()) {
9010 aPos->mResultFrame = frame;
9011 searchTableBool = true;
9012 result = NS_OK;
9013 break; // while(frame)
9015 result = NS_ERROR_FAILURE;
9016 frame = frame->PrincipalChildList().FirstChild();
9020 if (!searchTableBool) {
9021 result = aPos->mResultFrame->CanProvideLineIterator()
9022 ? NS_OK
9023 : NS_ERROR_FAILURE;
9026 // we've struck another block element!
9027 if (NS_SUCCEEDED(result)) {
9028 doneLooping = false;
9029 if (aPos->mDirection == eDirPrevious) {
9030 edgeCase = 1; // far edge, search from end backwards
9031 } else {
9032 edgeCase = -1; // near edge search from beginning onwards
9034 thisLine = 0; // this line means nothing now.
9035 // everything else means something so keep looking "inside" the
9036 // block
9037 blockFrame = aPos->mResultFrame;
9038 } else {
9039 // THIS is to mean that everything is ok to the containing while
9040 // loop
9041 result = NS_OK;
9042 break;
9045 } while (!doneLooping);
9047 return result;
9050 nsresult nsIFrame::PeekOffsetForLineEdge(nsPeekOffsetStruct* aPos) {
9051 // Adjusted so that the caret can't get confused when content changes
9052 nsIFrame* frame = AdjustFrameForSelectionStyles(this);
9053 Element* editingHost = frame->GetContent()->GetEditingHost();
9055 auto [blockFrame, lineFrame] =
9056 frame->GetContainingBlockForLine(aPos->mScrollViewStop);
9057 if (!blockFrame) {
9058 return NS_ERROR_FAILURE;
9060 nsAutoLineIterator it = blockFrame->GetLineIterator();
9061 int32_t thisLine = it->FindLineContaining(lineFrame);
9062 if (thisLine < 0) {
9063 return NS_ERROR_FAILURE;
9066 nsIFrame* baseFrame = nullptr;
9067 bool endOfLine = (eSelectEndLine == aPos->mAmount);
9069 if (aPos->mVisual && PresContext()->BidiEnabled()) {
9070 nsIFrame* firstFrame;
9071 bool isReordered;
9072 nsIFrame* lastFrame;
9073 MOZ_TRY(
9074 it->CheckLineOrder(thisLine, &isReordered, &firstFrame, &lastFrame));
9075 baseFrame = endOfLine ? lastFrame : firstFrame;
9076 } else {
9077 auto line = it->GetLine(thisLine).unwrap();
9079 nsIFrame* frame = line.mFirstFrameOnLine;
9080 bool lastFrameWasEditable = false;
9081 for (int32_t count = line.mNumFramesOnLine; count;
9082 --count, frame = frame->GetNextSibling()) {
9083 if (frame->IsGeneratedContentFrame()) {
9084 continue;
9086 // When jumping to the end of the line with the "end" key,
9087 // try to skip over brFrames
9088 if (endOfLine && line.mNumFramesOnLine > 1 && frame->IsBrFrame() &&
9089 lastFrameWasEditable == frame->GetContent()->IsEditable()) {
9090 continue;
9092 lastFrameWasEditable =
9093 frame->GetContent() && frame->GetContent()->IsEditable();
9094 baseFrame = frame;
9095 if (!endOfLine) {
9096 break;
9100 if (!baseFrame) {
9101 return NS_ERROR_FAILURE;
9103 // Make sure we are not leaving our inline editing host if exists
9104 if (editingHost) {
9105 if (nsIFrame* frame = editingHost->GetPrimaryFrame()) {
9106 if (frame->IsInlineOutside() &&
9107 !editingHost->Contains(baseFrame->GetContent())) {
9108 baseFrame = frame;
9109 if (endOfLine) {
9110 baseFrame = baseFrame->LastContinuation();
9115 FrameTarget targetFrame = DrillDownToSelectionFrame(baseFrame, endOfLine, 0);
9116 SetPeekResultFromFrame(*aPos, targetFrame.frame, endOfLine ? -1 : 0,
9117 OffsetIsAtLineEdge::Yes);
9118 if (endOfLine && targetFrame.frame->HasSignificantTerminalNewline()) {
9119 // Do not position the caret after the terminating newline if we're
9120 // trying to move to the end of line (see bug 596506)
9121 --aPos->mContentOffset;
9123 if (!aPos->mResultContent) {
9124 return NS_ERROR_FAILURE;
9126 return NS_OK;
9129 nsresult nsIFrame::PeekOffset(nsPeekOffsetStruct* aPos) {
9130 MOZ_ASSERT(aPos);
9132 if (NS_WARN_IF(HasAnyStateBits(NS_FRAME_IS_DIRTY))) {
9133 // FIXME(Bug 1654362): <caption> currently can remain dirty.
9134 return NS_ERROR_UNEXPECTED;
9137 // Translate content offset to be relative to frame
9138 int32_t offset = aPos->mStartOffset - GetRangeForFrame(this).start;
9140 switch (aPos->mAmount) {
9141 case eSelectCharacter:
9142 case eSelectCluster:
9143 return PeekOffsetForCharacter(aPos, offset);
9144 case eSelectWordNoSpace:
9145 // eSelectWordNoSpace means that we should not be eating any whitespace
9146 // when moving to the adjacent word. This means that we should set aPos->
9147 // mWordMovementType to eEndWord if we're moving forwards, and to
9148 // eStartWord if we're moving backwards.
9149 if (aPos->mDirection == eDirPrevious) {
9150 aPos->mWordMovementType = eStartWord;
9151 } else {
9152 aPos->mWordMovementType = eEndWord;
9154 // Intentionally fall through the eSelectWord case.
9155 [[fallthrough]];
9156 case eSelectWord:
9157 return PeekOffsetForWord(aPos, offset);
9158 case eSelectLine:
9159 return PeekOffsetForLine(aPos);
9160 case eSelectBeginLine:
9161 case eSelectEndLine:
9162 return PeekOffsetForLineEdge(aPos);
9163 case eSelectParagraph:
9164 return PeekOffsetForParagraph(aPos);
9165 default: {
9166 NS_ASSERTION(false, "Invalid amount");
9167 return NS_ERROR_FAILURE;
9172 nsIFrame::FrameSearchResult nsIFrame::PeekOffsetNoAmount(bool aForward,
9173 int32_t* aOffset) {
9174 NS_ASSERTION(aOffset && *aOffset <= 1, "aOffset out of range");
9175 // Sure, we can stop right here.
9176 return FOUND;
9179 nsIFrame::FrameSearchResult nsIFrame::PeekOffsetCharacter(
9180 bool aForward, int32_t* aOffset, PeekOffsetCharacterOptions aOptions) {
9181 NS_ASSERTION(aOffset && *aOffset <= 1, "aOffset out of range");
9182 int32_t startOffset = *aOffset;
9183 // A negative offset means "end of frame", which in our case means offset 1.
9184 if (startOffset < 0) startOffset = 1;
9185 if (aForward == (startOffset == 0)) {
9186 // We're before the frame and moving forward, or after it and moving
9187 // backwards: skip to the other side and we're done.
9188 *aOffset = 1 - startOffset;
9189 return FOUND;
9191 return CONTINUE;
9194 nsIFrame::FrameSearchResult nsIFrame::PeekOffsetWord(
9195 bool aForward, bool aWordSelectEatSpace, bool aIsKeyboardSelect,
9196 int32_t* aOffset, PeekWordState* aState, bool /*aTrimSpaces*/) {
9197 NS_ASSERTION(aOffset && *aOffset <= 1, "aOffset out of range");
9198 int32_t startOffset = *aOffset;
9199 // This isn't text, so truncate the context
9200 aState->mContext.Truncate();
9201 if (startOffset < 0) startOffset = 1;
9202 if (aForward == (startOffset == 0)) {
9203 // We're before the frame and moving forward, or after it and moving
9204 // backwards. If we're looking for non-whitespace, we found it (without
9205 // skipping this frame).
9206 if (!aState->mAtStart) {
9207 if (aState->mLastCharWasPunctuation) {
9208 // We're not punctuation, so this is a punctuation boundary.
9209 if (BreakWordBetweenPunctuation(aState, aForward, false, false,
9210 aIsKeyboardSelect))
9211 return FOUND;
9212 } else {
9213 // This is not a punctuation boundary.
9214 if (aWordSelectEatSpace && aState->mSawBeforeType) return FOUND;
9217 // Otherwise skip to the other side and note that we encountered
9218 // non-whitespace.
9219 *aOffset = 1 - startOffset;
9220 aState->Update(false, // not punctuation
9221 false // not whitespace
9223 if (!aWordSelectEatSpace) aState->SetSawBeforeType();
9225 return CONTINUE;
9228 // static
9229 bool nsIFrame::BreakWordBetweenPunctuation(const PeekWordState* aState,
9230 bool aForward, bool aPunctAfter,
9231 bool aWhitespaceAfter,
9232 bool aIsKeyboardSelect) {
9233 NS_ASSERTION(aPunctAfter != aState->mLastCharWasPunctuation,
9234 "Call this only at punctuation boundaries");
9235 if (aState->mLastCharWasWhitespace) {
9236 // We always stop between whitespace and punctuation
9237 return true;
9239 if (!StaticPrefs::layout_word_select_stop_at_punctuation()) {
9240 // When this pref is false, we never stop at a punctuation boundary unless
9241 // it's followed by whitespace (in the relevant direction).
9242 return aWhitespaceAfter;
9244 if (!aIsKeyboardSelect) {
9245 // mouse caret movement (e.g. word selection) always stops at every
9246 // punctuation boundary
9247 return true;
9249 bool afterPunct = aForward ? aState->mLastCharWasPunctuation : aPunctAfter;
9250 if (!afterPunct) {
9251 // keyboard caret movement only stops after punctuation (in content order)
9252 return false;
9254 // Stop only if we've seen some non-punctuation since the last whitespace;
9255 // don't stop after punctuation that follows whitespace.
9256 return aState->mSeenNonPunctuationSinceWhitespace;
9259 nsresult nsIFrame::CheckVisibility(nsPresContext*, int32_t, int32_t, bool,
9260 bool*, bool*) {
9261 return NS_ERROR_NOT_IMPLEMENTED;
9264 std::pair<nsIFrame*, nsIFrame*> nsIFrame::GetContainingBlockForLine(
9265 bool aLockScroll) const {
9266 const nsIFrame* parentFrame = this;
9267 const nsIFrame* frame;
9268 while (parentFrame) {
9269 frame = parentFrame;
9270 if (frame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
9271 // if we are searching for a frame that is not in flow we will not find
9272 // it. we must instead look for its placeholder
9273 if (frame->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
9274 // abspos continuations don't have placeholders, get the fif
9275 frame = frame->FirstInFlow();
9277 frame = frame->GetPlaceholderFrame();
9278 if (!frame) {
9279 return std::pair(nullptr, nullptr);
9282 parentFrame = frame->GetParent();
9283 if (parentFrame) {
9284 if (aLockScroll && parentFrame->IsScrollFrame()) {
9285 return std::pair(nullptr, nullptr);
9287 if (parentFrame->CanProvideLineIterator()) {
9288 return std::pair(const_cast<nsIFrame*>(parentFrame),
9289 const_cast<nsIFrame*>(frame));
9293 return std::pair(nullptr, nullptr);
9296 Result<bool, nsresult> nsIFrame::IsVisuallyAtLineEdge(
9297 nsILineIterator* aLineIterator, int32_t aLine, nsDirection aDirection) {
9298 nsIFrame* firstFrame;
9299 nsIFrame* lastFrame;
9301 bool lineIsRTL = aLineIterator->GetDirection();
9302 bool isReordered;
9304 MOZ_TRY(aLineIterator->CheckLineOrder(aLine, &isReordered, &firstFrame,
9305 &lastFrame));
9307 nsIFrame** framePtr = aDirection == eDirPrevious ? &firstFrame : &lastFrame;
9308 if (!*framePtr) {
9309 return true;
9312 bool frameIsRTL = (nsBidiPresUtils::FrameDirection(*framePtr) ==
9313 mozilla::intl::BidiDirection::RTL);
9314 if ((frameIsRTL == lineIsRTL) == (aDirection == eDirPrevious)) {
9315 nsIFrame::GetFirstLeaf(framePtr);
9316 } else {
9317 nsIFrame::GetLastLeaf(framePtr);
9319 return *framePtr == this;
9322 Result<bool, nsresult> nsIFrame::IsLogicallyAtLineEdge(
9323 nsILineIterator* aLineIterator, int32_t aLine, nsDirection aDirection) {
9324 auto line = aLineIterator->GetLine(aLine).unwrap();
9326 if (aDirection == eDirPrevious) {
9327 nsIFrame* firstFrame = line.mFirstFrameOnLine;
9328 nsIFrame::GetFirstLeaf(&firstFrame);
9329 return firstFrame == this;
9332 // eDirNext
9333 nsIFrame* lastFrame = line.mFirstFrameOnLine;
9334 for (int32_t lineFrameCount = line.mNumFramesOnLine; lineFrameCount > 1;
9335 lineFrameCount--) {
9336 lastFrame = lastFrame->GetNextSibling();
9337 if (!lastFrame) {
9338 NS_ERROR("should not be reached nsIFrame");
9339 return Err(NS_ERROR_FAILURE);
9342 nsIFrame::GetLastLeaf(&lastFrame);
9343 return lastFrame == this;
9346 nsIFrame::SelectablePeekReport nsIFrame::GetFrameFromDirection(
9347 nsDirection aDirection, bool aVisual, bool aJumpLines, bool aScrollViewStop,
9348 bool aForceEditableRegion) {
9349 SelectablePeekReport result;
9351 nsPresContext* presContext = PresContext();
9352 bool needsVisualTraversal = aVisual && presContext->BidiEnabled();
9353 nsCOMPtr<nsIFrameEnumerator> frameTraversal;
9354 MOZ_TRY(NS_NewFrameTraversal(getter_AddRefs(frameTraversal), presContext,
9355 this, eLeaf, needsVisualTraversal,
9356 aScrollViewStop,
9357 true, // aFollowOOFs
9358 false // aSkipPopupChecks
9361 // Find the prev/next selectable frame
9362 bool selectable = false;
9363 nsIFrame* traversedFrame = this;
9364 while (!selectable) {
9365 auto [blockFrame, lineFrame] =
9366 traversedFrame->GetContainingBlockForLine(aScrollViewStop);
9367 if (!blockFrame) {
9368 return result;
9371 nsAutoLineIterator it = blockFrame->GetLineIterator();
9372 int32_t thisLine = it->FindLineContaining(lineFrame);
9373 if (thisLine < 0) {
9374 return result;
9377 bool atLineEdge;
9378 MOZ_TRY_VAR(
9379 atLineEdge,
9380 needsVisualTraversal
9381 ? traversedFrame->IsVisuallyAtLineEdge(it, thisLine, aDirection)
9382 : traversedFrame->IsLogicallyAtLineEdge(it, thisLine, aDirection));
9383 if (atLineEdge) {
9384 result.mJumpedLine = true;
9385 if (!aJumpLines) {
9386 return result; // we are done. cannot jump lines
9388 int32_t lineToCheckWrap =
9389 aDirection == eDirPrevious ? thisLine - 1 : thisLine;
9390 if (lineToCheckWrap < 0 ||
9391 !it->GetLine(lineToCheckWrap).unwrap().mIsWrapped) {
9392 result.mJumpedHardBreak = true;
9396 traversedFrame = frameTraversal->Traverse(aDirection == eDirNext);
9397 if (!traversedFrame) {
9398 return result;
9401 auto IsSelectable = [aForceEditableRegion](const nsIFrame* aFrame) {
9402 if (!aFrame->IsSelectable(nullptr)) {
9403 return false;
9405 return !aForceEditableRegion || aFrame->GetContent()->IsEditable();
9408 // Skip br frames, but only if we can select something before hitting the
9409 // end of the line or a non-selectable region.
9410 if (atLineEdge && aDirection == eDirPrevious &&
9411 traversedFrame->IsBrFrame()) {
9412 for (nsIFrame* current = traversedFrame->GetPrevSibling(); current;
9413 current = current->GetPrevSibling()) {
9414 if (!current->IsBlockOutside() && IsSelectable(current)) {
9415 if (!current->IsBrFrame()) {
9416 result.mIgnoredBrFrame = true;
9418 break;
9421 if (result.mIgnoredBrFrame) {
9422 continue;
9426 selectable = IsSelectable(traversedFrame);
9427 if (!selectable) {
9428 if (traversedFrame->IsSelectable(nullptr)) {
9429 result.mHasSelectableFrame = true;
9431 result.mMovedOverNonSelectableText = true;
9433 } // while (!selectable)
9435 result.mOffset = (aDirection == eDirNext) ? 0 : -1;
9437 if (aVisual && nsBidiPresUtils::IsReversedDirectionFrame(traversedFrame)) {
9438 // The new frame is reverse-direction, go to the other end
9439 result.mOffset = -1 - result.mOffset;
9441 result.mFrame = traversedFrame;
9442 return result;
9445 nsIFrame::SelectablePeekReport nsIFrame::GetFrameFromDirection(
9446 const nsPeekOffsetStruct& aPos) {
9447 return GetFrameFromDirection(aPos.mDirection, aPos.mVisual, aPos.mJumpLines,
9448 aPos.mScrollViewStop, aPos.mForceEditableRegion);
9451 nsView* nsIFrame::GetClosestView(nsPoint* aOffset) const {
9452 nsPoint offset(0, 0);
9453 for (const nsIFrame* f = this; f; f = f->GetParent()) {
9454 if (f->HasView()) {
9455 if (aOffset) *aOffset = offset;
9456 return f->GetView();
9458 offset += f->GetPosition();
9461 MOZ_ASSERT_UNREACHABLE("No view on any parent? How did that happen?");
9462 return nullptr;
9465 /* virtual */
9466 void nsIFrame::ChildIsDirty(nsIFrame* aChild) {
9467 MOZ_ASSERT_UNREACHABLE(
9468 "should never be called on a frame that doesn't "
9469 "inherit from nsContainerFrame");
9472 #ifdef ACCESSIBILITY
9473 a11y::AccType nsIFrame::AccessibleType() {
9474 if (IsTableCaption() && !GetRect().IsEmpty()) {
9475 return a11y::eHTMLCaptionType;
9477 return a11y::eNoType;
9479 #endif
9481 bool nsIFrame::ClearOverflowRects() {
9482 if (mOverflow.mType == OverflowStorageType::None) {
9483 return false;
9485 if (mOverflow.mType == OverflowStorageType::Large) {
9486 RemoveProperty(OverflowAreasProperty());
9488 mOverflow.mType = OverflowStorageType::None;
9489 return true;
9492 bool nsIFrame::SetOverflowAreas(const OverflowAreas& aOverflowAreas) {
9493 if (mOverflow.mType == OverflowStorageType::Large) {
9494 OverflowAreas* overflow = GetOverflowAreasProperty();
9495 bool changed = *overflow != aOverflowAreas;
9496 *overflow = aOverflowAreas;
9498 // Don't bother with converting to the deltas form if we already
9499 // have a property.
9500 return changed;
9503 const nsRect& vis = aOverflowAreas.InkOverflow();
9504 uint32_t l = -vis.x, // left edge: positive delta is leftwards
9505 t = -vis.y, // top: positive is upwards
9506 r = vis.XMost() - mRect.width, // right: positive is rightwards
9507 b = vis.YMost() - mRect.height; // bottom: positive is downwards
9508 if (aOverflowAreas.ScrollableOverflow().IsEqualEdges(
9509 nsRect(nsPoint(0, 0), GetSize())) &&
9510 l <= InkOverflowDeltas::kMax && t <= InkOverflowDeltas::kMax &&
9511 r <= InkOverflowDeltas::kMax && b <= InkOverflowDeltas::kMax &&
9512 // we have to check these against zero because we *never* want to
9513 // set a frame as having no overflow in this function. This is
9514 // because FinishAndStoreOverflow calls this function prior to
9515 // SetRect based on whether the overflow areas match aNewSize.
9516 // In the case where the overflow areas exactly match mRect but
9517 // do not match aNewSize, we need to store overflow in a property
9518 // so that our eventual SetRect/SetSize will know that it has to
9519 // reset our overflow areas.
9520 (l | t | r | b) != 0) {
9521 InkOverflowDeltas oldDeltas = mOverflow.mInkOverflowDeltas;
9522 // It's a "small" overflow area so we store the deltas for each edge
9523 // directly in the frame, rather than allocating a separate rect.
9524 // If they're all zero, that's fine; we're setting things to
9525 // no-overflow.
9526 mOverflow.mInkOverflowDeltas.mLeft = l;
9527 mOverflow.mInkOverflowDeltas.mTop = t;
9528 mOverflow.mInkOverflowDeltas.mRight = r;
9529 mOverflow.mInkOverflowDeltas.mBottom = b;
9530 // There was no scrollable overflow before, and there isn't now.
9531 return oldDeltas != mOverflow.mInkOverflowDeltas;
9532 } else {
9533 bool changed =
9534 !aOverflowAreas.ScrollableOverflow().IsEqualEdges(
9535 nsRect(nsPoint(0, 0), GetSize())) ||
9536 !aOverflowAreas.InkOverflow().IsEqualEdges(InkOverflowFromDeltas());
9538 // it's a large overflow area that we need to store as a property
9539 mOverflow.mType = OverflowStorageType::Large;
9540 AddProperty(OverflowAreasProperty(), new OverflowAreas(aOverflowAreas));
9541 return changed;
9545 enum class ApplyTransform : bool { No, Yes };
9548 * Compute the outline inner rect (so without outline-width and outline-offset)
9549 * of aFrame, maybe iterating over its descendants, in aFrame's coordinate space
9550 * or its post-transform coordinate space (depending on aApplyTransform).
9552 static nsRect ComputeOutlineInnerRect(
9553 nsIFrame* aFrame, ApplyTransform aApplyTransform, bool& aOutValid,
9554 const nsSize* aSizeOverride = nullptr,
9555 const OverflowAreas* aOverflowOverride = nullptr) {
9556 const nsRect bounds(nsPoint(0, 0),
9557 aSizeOverride ? *aSizeOverride : aFrame->GetSize());
9559 // The SVG container frames besides SVGTextFrame do not maintain
9560 // an accurate mRect. It will make the outline be larger than
9561 // we expect, we need to make them narrow to their children's outline.
9562 // aOutValid is set to false if the returned nsRect is not valid
9563 // and should not be included in the outline rectangle.
9564 aOutValid = !aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT) ||
9565 !aFrame->IsFrameOfType(nsIFrame::eSVGContainer) ||
9566 aFrame->IsSVGTextFrame();
9568 nsRect u;
9570 if (!aFrame->FrameMaintainsOverflow()) {
9571 return u;
9574 // Start from our border-box, transformed. See comment below about
9575 // transform of children.
9576 bool doTransform =
9577 aApplyTransform == ApplyTransform::Yes && aFrame->IsTransformed();
9578 TransformReferenceBox boundsRefBox(nullptr, bounds);
9579 if (doTransform) {
9580 u = nsDisplayTransform::TransformRect(bounds, aFrame, boundsRefBox);
9581 } else {
9582 u = bounds;
9585 if (aOutValid && !StaticPrefs::layout_outline_include_overflow()) {
9586 return u;
9589 // Only iterate through the children if the overflow areas suggest
9590 // that we might need to, and if the frame doesn't clip its overflow
9591 // anyway.
9592 if (aOverflowOverride) {
9593 if (!doTransform && bounds.IsEqualEdges(aOverflowOverride->InkOverflow()) &&
9594 bounds.IsEqualEdges(aOverflowOverride->ScrollableOverflow())) {
9595 return u;
9597 } else {
9598 if (!doTransform && bounds.IsEqualEdges(aFrame->InkOverflowRect()) &&
9599 bounds.IsEqualEdges(aFrame->ScrollableOverflowRect())) {
9600 return u;
9603 const nsStyleDisplay* disp = aFrame->StyleDisplay();
9604 LayoutFrameType fType = aFrame->Type();
9605 auto overflowClipAxes = aFrame->ShouldApplyOverflowClipping(disp);
9606 if (overflowClipAxes == nsIFrame::PhysicalAxes::Both ||
9607 fType == LayoutFrameType::Scroll ||
9608 fType == LayoutFrameType::ListControl ||
9609 fType == LayoutFrameType::SVGOuterSVG) {
9610 return u;
9613 const nsStyleEffects* effects = aFrame->StyleEffects();
9614 Maybe<nsRect> clipPropClipRect =
9615 aFrame->GetClipPropClipRect(disp, effects, bounds.Size());
9617 // Iterate over all children except pop-up, absolutely-positioned,
9618 // float, and overflow ones.
9619 const nsIFrame::ChildListIDs skip = {
9620 nsIFrame::kPopupList, nsIFrame::kSelectPopupList,
9621 nsIFrame::kAbsoluteList, nsIFrame::kFixedList,
9622 nsIFrame::kFloatList, nsIFrame::kOverflowList};
9623 for (const auto& [list, listID] : aFrame->ChildLists()) {
9624 if (skip.contains(listID)) {
9625 continue;
9628 for (nsIFrame* child : list) {
9629 if (child->IsPlaceholderFrame()) {
9630 continue;
9633 // Note that passing ApplyTransform::Yes when
9634 // child->Combines3DTransformWithAncestors() returns true is incorrect if
9635 // our aApplyTransform is No... but the opposite would be as well.
9636 // This is because elements within a preserve-3d scene are always
9637 // transformed up to the top of the scene. This means we don't have a
9638 // mechanism for getting a transform up to an intermediate point within
9639 // the scene. We choose to over-transform rather than under-transform
9640 // because this is consistent with other overflow areas.
9641 bool validRect = true;
9642 nsRect childRect =
9643 ComputeOutlineInnerRect(child, ApplyTransform::Yes, validRect) +
9644 child->GetPosition();
9646 if (!validRect) {
9647 continue;
9650 if (clipPropClipRect) {
9651 // Intersect with the clip before transforming.
9652 childRect.IntersectRect(childRect, *clipPropClipRect);
9655 // Note that we transform each child separately according to
9656 // aFrame's transform, and then union, which gives a different
9657 // (smaller) result from unioning and then transforming the
9658 // union. This doesn't match the way we handle overflow areas
9659 // with 2-D transforms, though it does match the way we handle
9660 // overflow areas in preserve-3d 3-D scenes.
9661 if (doTransform && !child->Combines3DTransformWithAncestors()) {
9662 childRect =
9663 nsDisplayTransform::TransformRect(childRect, aFrame, boundsRefBox);
9666 // If a SVGContainer has a non-SVGContainer child, we assign
9667 // its child's outline to this SVGContainer directly.
9668 if (!aOutValid && validRect) {
9669 u = childRect;
9670 aOutValid = true;
9671 } else {
9672 u = u.UnionEdges(childRect);
9677 if (overflowClipAxes & nsIFrame::PhysicalAxes::Vertical) {
9678 u.y = bounds.y;
9679 u.height = bounds.height;
9681 if (overflowClipAxes & nsIFrame::PhysicalAxes::Horizontal) {
9682 u.x = bounds.x;
9683 u.width = bounds.width;
9686 return u;
9689 static void ComputeAndIncludeOutlineArea(nsIFrame* aFrame,
9690 OverflowAreas& aOverflowAreas,
9691 const nsSize& aNewSize) {
9692 const nsStyleOutline* outline = aFrame->StyleOutline();
9693 if (!outline->ShouldPaintOutline()) {
9694 return;
9697 // When the outline property is set on a :-moz-block-inside-inline-wrapper
9698 // pseudo-element, it inherited that outline from the inline that was broken
9699 // because it contained a block. In that case, we don't want a really wide
9700 // outline if the block inside the inline is narrow, so union the actual
9701 // contents of the anonymous blocks.
9702 nsIFrame* frameForArea = aFrame;
9703 do {
9704 PseudoStyleType pseudoType = frameForArea->Style()->GetPseudoType();
9705 if (pseudoType != PseudoStyleType::mozBlockInsideInlineWrapper) break;
9706 // If we're done, we really want it and all its later siblings.
9707 frameForArea = frameForArea->PrincipalChildList().FirstChild();
9708 NS_ASSERTION(frameForArea, "anonymous block with no children?");
9709 } while (frameForArea);
9711 // Find the union of the border boxes of all descendants, or in
9712 // the block-in-inline case, all descendants we care about.
9714 // Note that the interesting perspective-related cases are taken
9715 // care of by the code that handles those issues for overflow
9716 // calling FinishAndStoreOverflow again, which in turn calls this
9717 // function again. We still need to deal with preserve-3d a bit.
9718 nsRect innerRect;
9719 bool validRect = false;
9720 if (frameForArea == aFrame) {
9721 innerRect = ComputeOutlineInnerRect(aFrame, ApplyTransform::No, validRect,
9722 &aNewSize, &aOverflowAreas);
9723 } else {
9724 for (; frameForArea; frameForArea = frameForArea->GetNextSibling()) {
9725 nsRect r =
9726 ComputeOutlineInnerRect(frameForArea, ApplyTransform::Yes, validRect);
9728 // Adjust for offsets transforms up to aFrame's pre-transform
9729 // (i.e., normal) coordinate space; see comments in
9730 // UnionBorderBoxes for some of the subtlety here.
9731 for (nsIFrame *f = frameForArea, *parent = f->GetParent();
9732 /* see middle of loop */; f = parent, parent = f->GetParent()) {
9733 r += f->GetPosition();
9734 if (parent == aFrame) {
9735 break;
9737 if (parent->IsTransformed() && !f->Combines3DTransformWithAncestors()) {
9738 TransformReferenceBox refBox(parent);
9739 r = nsDisplayTransform::TransformRect(r, parent, refBox);
9743 innerRect.UnionRect(innerRect, r);
9747 // Keep this code in sync with nsDisplayOutline::GetInnerRect.
9748 if (innerRect == aFrame->GetRectRelativeToSelf()) {
9749 aFrame->RemoveProperty(nsIFrame::OutlineInnerRectProperty());
9750 } else {
9751 SetOrUpdateRectValuedProperty(aFrame, nsIFrame::OutlineInnerRectProperty(),
9752 innerRect);
9754 const nscoord offset = outline->mOutlineOffset.ToAppUnits();
9755 nsRect outerRect(innerRect);
9756 bool useOutlineAuto = false;
9757 if (StaticPrefs::layout_css_outline_style_auto_enabled()) {
9758 useOutlineAuto = outline->mOutlineStyle.IsAuto();
9759 if (MOZ_UNLIKELY(useOutlineAuto)) {
9760 nsPresContext* presContext = aFrame->PresContext();
9761 nsITheme* theme = presContext->Theme();
9762 if (theme->ThemeSupportsWidget(presContext, aFrame,
9763 StyleAppearance::FocusOutline)) {
9764 outerRect.Inflate(offset);
9765 theme->GetWidgetOverflow(presContext->DeviceContext(), aFrame,
9766 StyleAppearance::FocusOutline, &outerRect);
9767 } else {
9768 useOutlineAuto = false;
9772 if (MOZ_LIKELY(!useOutlineAuto)) {
9773 nscoord width = outline->GetOutlineWidth();
9774 outerRect.Inflate(width + offset);
9777 nsRect& vo = aOverflowAreas.InkOverflow();
9778 vo = vo.UnionEdges(innerRect.Union(outerRect));
9781 bool nsIFrame::FinishAndStoreOverflow(OverflowAreas& aOverflowAreas,
9782 nsSize aNewSize, nsSize* aOldSize,
9783 const nsStyleDisplay* aStyleDisplay) {
9784 MOZ_ASSERT(FrameMaintainsOverflow(),
9785 "Don't call - overflow rects not maintained on these SVG frames");
9787 const nsStyleDisplay* disp = StyleDisplayWithOptionalParam(aStyleDisplay);
9788 bool hasTransform = IsTransformed();
9790 nsRect bounds(nsPoint(0, 0), aNewSize);
9791 // Store the passed in overflow area if we are a preserve-3d frame or we have
9792 // a transform, and it's not just the frame bounds.
9793 if (hasTransform || Combines3DTransformWithAncestors()) {
9794 if (!aOverflowAreas.InkOverflow().IsEqualEdges(bounds) ||
9795 !aOverflowAreas.ScrollableOverflow().IsEqualEdges(bounds)) {
9796 OverflowAreas* initial = GetProperty(nsIFrame::InitialOverflowProperty());
9797 if (!initial) {
9798 AddProperty(nsIFrame::InitialOverflowProperty(),
9799 new OverflowAreas(aOverflowAreas));
9800 } else if (initial != &aOverflowAreas) {
9801 *initial = aOverflowAreas;
9803 } else {
9804 RemoveProperty(nsIFrame::InitialOverflowProperty());
9806 #ifdef DEBUG
9807 SetProperty(nsIFrame::DebugInitialOverflowPropertyApplied(), true);
9808 #endif
9809 } else {
9810 #ifdef DEBUG
9811 RemoveProperty(nsIFrame::DebugInitialOverflowPropertyApplied());
9812 #endif
9815 nsSize oldSize = mRect.Size();
9816 bool sizeChanged = ((aOldSize ? *aOldSize : oldSize) != aNewSize);
9818 // Our frame size may not have been computed and set yet, but code under
9819 // functions such as ComputeEffectsRect (which we're about to call) use the
9820 // values that are stored in our frame rect to compute their results. We
9821 // need the results from those functions to be based on the frame size that
9822 // we *will* have, so we temporarily set our frame size here before calling
9823 // those functions.
9825 // XXX Someone should document here why we revert the frame size before we
9826 // return rather than just leaving it set.
9828 // We pass false here to avoid invalidating display items for this temporary
9829 // change. We sometimes reflow frames multiple times, with the final size
9830 // being the same as the initial. The single call to SetSize after reflow is
9831 // done will take care of invalidating display items if the size has actually
9832 // changed.
9833 SetSize(aNewSize, false);
9835 const auto overflowClipAxes = ShouldApplyOverflowClipping(disp);
9837 if (ChildrenHavePerspective(disp) && sizeChanged) {
9838 RecomputePerspectiveChildrenOverflow(this);
9840 if (overflowClipAxes != PhysicalAxes::Both) {
9841 aOverflowAreas.SetAllTo(bounds);
9842 DebugOnly<bool> ok = ComputeCustomOverflow(aOverflowAreas);
9844 // ComputeCustomOverflow() should not return false, when
9845 // FrameMaintainsOverflow() returns true.
9846 MOZ_ASSERT(ok, "FrameMaintainsOverflow() != ComputeCustomOverflow()");
9848 UnionChildOverflow(aOverflowAreas);
9852 // This is now called FinishAndStoreOverflow() instead of
9853 // StoreOverflow() because frame-generic ways of adding overflow
9854 // can happen here, e.g. CSS2 outline and native theme.
9855 // If the overflow area width or height is nscoord_MAX, then a
9856 // saturating union may have encounted an overflow, so the overflow may not
9857 // contain the frame border-box. Don't warn in that case.
9858 // Don't warn for SVG either, since SVG doesn't need the overflow area
9859 // to contain the frame bounds.
9860 for (const auto otype : AllOverflowTypes()) {
9861 DebugOnly<nsRect*> r = &aOverflowAreas.Overflow(otype);
9862 NS_ASSERTION(aNewSize.width == 0 || aNewSize.height == 0 ||
9863 r->width == nscoord_MAX || r->height == nscoord_MAX ||
9864 (mState & NS_FRAME_SVG_LAYOUT) ||
9865 r->Contains(nsRect(nsPoint(0, 0), aNewSize)),
9866 "Computed overflow area must contain frame bounds");
9869 // If we clip our children, clear accumulated overflow area in the affected
9870 // dimension(s). The children are actually clipped to the padding-box, but
9871 // since the overflow area should include the entire border-box, just set it
9872 // to the border-box size here.
9873 if (overflowClipAxes != PhysicalAxes::None) {
9874 nsRect& ink = aOverflowAreas.InkOverflow();
9875 nsRect& scrollable = aOverflowAreas.ScrollableOverflow();
9876 if (overflowClipAxes & PhysicalAxes::Vertical) {
9877 ink.y = bounds.y;
9878 scrollable.y = bounds.y;
9879 ink.height = bounds.height;
9880 scrollable.height = bounds.height;
9882 if (overflowClipAxes & PhysicalAxes::Horizontal) {
9883 ink.x = bounds.x;
9884 scrollable.x = bounds.x;
9885 ink.width = bounds.width;
9886 scrollable.width = bounds.width;
9890 // Overflow area must always include the frame's top-left and bottom-right,
9891 // even if the frame rect is empty (so we can scroll to those positions).
9892 // Pending a real fix for bug 426879, don't do this for inline frames
9893 // with zero width.
9894 // Do not do this for SVG either, since it will usually massively increase
9895 // the area unnecessarily.
9896 if ((aNewSize.width != 0 || !IsInlineFrame()) &&
9897 !HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
9898 for (const auto otype : AllOverflowTypes()) {
9899 nsRect& o = aOverflowAreas.Overflow(otype);
9900 o = o.UnionEdges(bounds);
9904 // Note that StyleOverflow::Clip doesn't clip the frame
9905 // background, so we add theme background overflow here so it's not clipped.
9906 if (!::IsXULBoxWrapped(this) && IsThemed(disp)) {
9907 nsRect r(bounds);
9908 nsPresContext* presContext = PresContext();
9909 if (presContext->Theme()->GetWidgetOverflow(
9910 presContext->DeviceContext(), this, disp->EffectiveAppearance(),
9911 &r)) {
9912 nsRect& vo = aOverflowAreas.InkOverflow();
9913 vo = vo.UnionEdges(r);
9917 ComputeAndIncludeOutlineArea(this, aOverflowAreas, aNewSize);
9919 // Nothing in here should affect scrollable overflow.
9920 aOverflowAreas.InkOverflow() =
9921 ComputeEffectsRect(this, aOverflowAreas.InkOverflow(), aNewSize);
9923 // Absolute position clipping
9924 const nsStyleEffects* effects = StyleEffects();
9925 Maybe<nsRect> clipPropClipRect = GetClipPropClipRect(disp, effects, aNewSize);
9926 if (clipPropClipRect) {
9927 for (const auto otype : AllOverflowTypes()) {
9928 nsRect& o = aOverflowAreas.Overflow(otype);
9929 o.IntersectRect(o, *clipPropClipRect);
9933 /* If we're transformed, transform the overflow rect by the current
9934 * transformation. */
9935 if (hasTransform) {
9936 SetProperty(nsIFrame::PreTransformOverflowAreasProperty(),
9937 new OverflowAreas(aOverflowAreas));
9939 if (Combines3DTransformWithAncestors()) {
9940 /* If we're a preserve-3d leaf frame, then our pre-transform overflow
9941 * should be correct. Our post-transform overflow is empty though, because
9942 * we only contribute to the overflow area of the preserve-3d root frame.
9943 * If we're an intermediate frame then the pre-transform overflow should
9944 * contain all our non-preserve-3d children, which is what we want. Again
9945 * we have no post-transform overflow.
9947 aOverflowAreas.SetAllTo(nsRect());
9948 } else {
9949 TransformReferenceBox refBox(this);
9950 for (const auto otype : AllOverflowTypes()) {
9951 nsRect& o = aOverflowAreas.Overflow(otype);
9952 o = nsDisplayTransform::TransformRect(o, this, refBox);
9955 /* If we're the root of the 3d context, then we want to include the
9956 * overflow areas of all the participants. This won't have happened yet as
9957 * the code above set their overflow area to empty. Manually collect these
9958 * overflow areas now.
9960 if (Extend3DContext(disp, effects)) {
9961 ComputePreserve3DChildrenOverflow(aOverflowAreas);
9964 } else {
9965 RemoveProperty(nsIFrame::PreTransformOverflowAreasProperty());
9968 /* Revert the size change in case some caller is depending on this. */
9969 SetSize(oldSize, false);
9971 bool anyOverflowChanged;
9972 if (aOverflowAreas != OverflowAreas(bounds, bounds)) {
9973 anyOverflowChanged = SetOverflowAreas(aOverflowAreas);
9974 } else {
9975 anyOverflowChanged = ClearOverflowRects();
9978 if (anyOverflowChanged) {
9979 SVGObserverUtils::InvalidateDirectRenderingObservers(this);
9980 if (IsBlockFrameOrSubclass() &&
9981 TextOverflow::CanHaveOverflowMarkers(this)) {
9982 DiscardDisplayItems(this, [](nsDisplayItem* aItem) {
9983 return aItem->GetType() == DisplayItemType::TYPE_TEXT_OVERFLOW;
9985 SchedulePaint(PAINT_DEFAULT);
9988 return anyOverflowChanged;
9991 void nsIFrame::RecomputePerspectiveChildrenOverflow(
9992 const nsIFrame* aStartFrame) {
9993 for (const auto& childList : ChildLists()) {
9994 for (nsIFrame* child : childList.mList) {
9995 if (!child->FrameMaintainsOverflow()) {
9996 continue; // frame does not maintain overflow rects
9998 if (child->HasPerspective()) {
9999 OverflowAreas* overflow =
10000 child->GetProperty(nsIFrame::InitialOverflowProperty());
10001 nsRect bounds(nsPoint(0, 0), child->GetSize());
10002 if (overflow) {
10003 OverflowAreas overflowCopy = *overflow;
10004 child->FinishAndStoreOverflow(overflowCopy, bounds.Size());
10005 } else {
10006 OverflowAreas boundsOverflow;
10007 boundsOverflow.SetAllTo(bounds);
10008 child->FinishAndStoreOverflow(boundsOverflow, bounds.Size());
10010 } else if (child->GetContent() == aStartFrame->GetContent() ||
10011 child->GetClosestFlattenedTreeAncestorPrimaryFrame() ==
10012 aStartFrame) {
10013 // If a frame is using perspective, then the size used to compute
10014 // perspective-origin is the size of the frame belonging to its parent
10015 // style. We must find any descendant frames using our size
10016 // (by recursing into frames that have the same containing block)
10017 // to update their overflow rects too.
10018 child->RecomputePerspectiveChildrenOverflow(aStartFrame);
10024 void nsIFrame::ComputePreserve3DChildrenOverflow(
10025 OverflowAreas& aOverflowAreas) {
10026 // Find all descendants that participate in the 3d context, and include their
10027 // overflow. These descendants have an empty overflow, so won't have been
10028 // included in the normal overflow calculation. Any children that don't
10029 // participate have normal overflow, so will have been included already.
10031 nsRect childVisual;
10032 nsRect childScrollable;
10033 for (const auto& childList : ChildLists()) {
10034 for (nsIFrame* child : childList.mList) {
10035 // If this child participates in the 3d context, then take the
10036 // pre-transform region (which contains all descendants that aren't
10037 // participating in the 3d context) and transform it into the 3d context
10038 // root coordinate space.
10039 if (child->Combines3DTransformWithAncestors()) {
10040 OverflowAreas childOverflow = child->GetOverflowAreasRelativeToSelf();
10041 TransformReferenceBox refBox(child);
10042 for (const auto otype : AllOverflowTypes()) {
10043 nsRect& o = childOverflow.Overflow(otype);
10044 o = nsDisplayTransform::TransformRect(o, child, refBox);
10047 aOverflowAreas.UnionWith(childOverflow);
10049 // If this child also extends the 3d context, then recurse into it
10050 // looking for more participants.
10051 if (child->Extend3DContext()) {
10052 child->ComputePreserve3DChildrenOverflow(aOverflowAreas);
10059 bool nsIFrame::ZIndexApplies() const {
10060 return StyleDisplay()->IsPositionedStyle() || IsFlexOrGridItem();
10063 Maybe<int32_t> nsIFrame::ZIndex() const {
10064 if (!ZIndexApplies()) {
10065 return Nothing();
10067 const auto& zIndex = StylePosition()->mZIndex;
10068 if (zIndex.IsAuto()) {
10069 return Nothing();
10071 return Some(zIndex.AsInteger());
10074 bool nsIFrame::IsScrollAnchor(ScrollAnchorContainer** aOutContainer) {
10075 if (!mInScrollAnchorChain) {
10076 return false;
10079 nsIFrame* f = this;
10081 // FIXME(emilio, bug 1629280): We should find a non-null anchor if we have the
10082 // flag set, but bug 1629280 makes it so that we cannot really assert it /
10083 // make this just a `while (true)`, and uncomment the below assertion.
10084 while (auto* container = ScrollAnchorContainer::FindFor(f)) {
10085 // MOZ_ASSERT(f->IsInScrollAnchorChain());
10086 if (nsIFrame* anchor = container->AnchorNode()) {
10087 if (anchor != this) {
10088 return false;
10090 if (aOutContainer) {
10091 *aOutContainer = container;
10093 return true;
10096 f = container->Frame();
10099 return false;
10102 bool nsIFrame::IsInScrollAnchorChain() const { return mInScrollAnchorChain; }
10104 void nsIFrame::SetInScrollAnchorChain(bool aInChain) {
10105 mInScrollAnchorChain = aInChain;
10108 uint32_t nsIFrame::GetDepthInFrameTree() const {
10109 uint32_t result = 0;
10110 for (nsContainerFrame* ancestor = GetParent(); ancestor;
10111 ancestor = ancestor->GetParent()) {
10112 result++;
10114 return result;
10118 * This function takes a frame that is part of a block-in-inline split,
10119 * and _if_ that frame is an anonymous block created by an ib split it
10120 * returns the block's preceding inline. This is needed because the
10121 * split inline's style is the parent of the anonymous block's style.
10123 * If aFrame is not an anonymous block, null is returned.
10125 static nsIFrame* GetIBSplitSiblingForAnonymousBlock(const nsIFrame* aFrame) {
10126 MOZ_ASSERT(aFrame, "Must have a non-null frame!");
10127 NS_ASSERTION(aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT),
10128 "GetIBSplitSibling should only be called on ib-split frames");
10130 if (aFrame->Style()->GetPseudoType() !=
10131 PseudoStyleType::mozBlockInsideInlineWrapper) {
10132 // it's not an anonymous block
10133 return nullptr;
10136 // Find the first continuation of the frame. (Ugh. This ends up
10137 // being O(N^2) when it is called O(N) times.)
10138 aFrame = aFrame->FirstContinuation();
10141 * Now look up the nsGkAtoms::IBSplitPrevSibling
10142 * property.
10144 nsIFrame* ibSplitSibling =
10145 aFrame->GetProperty(nsIFrame::IBSplitPrevSibling());
10146 NS_ASSERTION(ibSplitSibling, "Broken frame tree?");
10147 return ibSplitSibling;
10151 * Get the parent, corrected for the mangled frame tree resulting from
10152 * having a block within an inline. The result only differs from the
10153 * result of |GetParent| when |GetParent| returns an anonymous block
10154 * that was created for an element that was 'display: inline' because
10155 * that element contained a block.
10157 * Also skip anonymous scrolled-content parents; inherit directly from the
10158 * outer scroll frame.
10160 * Also skip NAC parents if the child frame is NAC.
10162 static nsIFrame* GetCorrectedParent(const nsIFrame* aFrame) {
10163 nsIFrame* parent = aFrame->GetParent();
10164 if (!parent) {
10165 return nullptr;
10168 // For a table caption we want the _inner_ table frame (unless it's anonymous)
10169 // as the style parent.
10170 if (aFrame->IsTableCaption()) {
10171 nsIFrame* innerTable = parent->PrincipalChildList().FirstChild();
10172 if (!innerTable->Style()->IsAnonBox()) {
10173 return innerTable;
10177 // Table wrappers are always anon boxes; if we're in here for an outer
10178 // table, that actually means its the _inner_ table that wants to
10179 // know its parent. So get the pseudo of the inner in that case.
10180 auto pseudo = aFrame->Style()->GetPseudoType();
10181 if (pseudo == PseudoStyleType::tableWrapper) {
10182 pseudo =
10183 aFrame->PrincipalChildList().FirstChild()->Style()->GetPseudoType();
10186 // Prevent a NAC pseudo-element from inheriting from its NAC parent, and
10187 // inherit from the NAC generator element instead.
10188 if (pseudo != PseudoStyleType::NotPseudo) {
10189 MOZ_ASSERT(aFrame->GetContent());
10190 Element* element = Element::FromNode(aFrame->GetContent());
10191 // Make sure to avoid doing the fixup for non-element-backed pseudos like
10192 // ::first-line and such.
10193 if (element && !element->IsRootOfNativeAnonymousSubtree() &&
10194 element->GetPseudoElementType() == aFrame->Style()->GetPseudoType()) {
10195 while (parent->GetContent() &&
10196 !parent->GetContent()->IsRootOfNativeAnonymousSubtree()) {
10197 parent = parent->GetInFlowParent();
10199 parent = parent->GetInFlowParent();
10203 return nsIFrame::CorrectStyleParentFrame(parent, pseudo);
10206 /* static */
10207 nsIFrame* nsIFrame::CorrectStyleParentFrame(nsIFrame* aProspectiveParent,
10208 PseudoStyleType aChildPseudo) {
10209 MOZ_ASSERT(aProspectiveParent, "Must have a prospective parent");
10211 if (aChildPseudo != PseudoStyleType::NotPseudo) {
10212 // Non-inheriting anon boxes have no style parent frame at all.
10213 if (PseudoStyle::IsNonInheritingAnonBox(aChildPseudo)) {
10214 return nullptr;
10217 // Other anon boxes are parented to their actual parent already, except
10218 // for non-elements. Those should not be treated as an anon box.
10219 if (PseudoStyle::IsAnonBox(aChildPseudo) &&
10220 !nsCSSAnonBoxes::IsNonElement(aChildPseudo)) {
10221 NS_ASSERTION(aChildPseudo != PseudoStyleType::mozBlockInsideInlineWrapper,
10222 "Should have dealt with kids that have "
10223 "NS_FRAME_PART_OF_IBSPLIT elsewhere");
10224 return aProspectiveParent;
10228 // Otherwise, walk up out of all anon boxes. For placeholder frames, walk out
10229 // of all pseudo-elements as well. Otherwise ReparentComputedStyle could
10230 // cause style data to be out of sync with the frame tree.
10231 nsIFrame* parent = aProspectiveParent;
10232 do {
10233 if (parent->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) {
10234 nsIFrame* sibling = GetIBSplitSiblingForAnonymousBlock(parent);
10236 if (sibling) {
10237 // |parent| was a block in an {ib} split; use the inline as
10238 // |the style parent.
10239 parent = sibling;
10243 if (!parent->Style()->IsPseudoOrAnonBox()) {
10244 return parent;
10247 if (!parent->Style()->IsAnonBox() && aChildPseudo != PseudoStyleType::MAX) {
10248 // nsPlaceholderFrame passes in PseudoStyleType::MAX for
10249 // aChildPseudo (even though that's not a valid pseudo-type) just to
10250 // trigger this behavior of walking up to the nearest non-pseudo
10251 // ancestor.
10252 return parent;
10255 parent = parent->GetInFlowParent();
10256 } while (parent);
10258 if (aProspectiveParent->Style()->GetPseudoType() ==
10259 PseudoStyleType::viewportScroll) {
10260 // aProspectiveParent is the scrollframe for a viewport
10261 // and the kids are the anonymous scrollbars
10262 return aProspectiveParent;
10265 // We can get here if the root element is absolutely positioned.
10266 // We can't test for this very accurately, but it can only happen
10267 // when the prospective parent is a canvas frame.
10268 NS_ASSERTION(aProspectiveParent->IsCanvasFrame(),
10269 "Should have found a parent before this");
10270 return nullptr;
10273 ComputedStyle* nsIFrame::DoGetParentComputedStyle(
10274 nsIFrame** aProviderFrame) const {
10275 *aProviderFrame = nullptr;
10277 // Handle display:contents and the root frame, when there's no parent frame
10278 // to inherit from.
10279 if (MOZ_LIKELY(mContent)) {
10280 Element* parentElement = mContent->GetFlattenedTreeParentElement();
10281 if (MOZ_LIKELY(parentElement)) {
10282 auto pseudo = Style()->GetPseudoType();
10283 if (pseudo == PseudoStyleType::NotPseudo || !mContent->IsElement() ||
10284 (!PseudoStyle::IsAnonBox(pseudo) &&
10285 // Ensure that we don't return the display:contents style
10286 // of the parent content for pseudos that have the same content
10287 // as their primary frame (like -moz-list-bullets do):
10288 IsPrimaryFrame()) ||
10289 /* if next is true then it's really a request for the table frame's
10290 parent context, see nsTable[Outer]Frame::GetParentComputedStyle. */
10291 pseudo == PseudoStyleType::tableWrapper) {
10292 // In some edge cases involving display: contents, we may end up here
10293 // for something that's pending to be reframed. In this case we return
10294 // the wrong style from here (because we've already lost track of it!),
10295 // but it's not a big deal as we're going to be reframed anyway.
10296 if (MOZ_LIKELY(parentElement->HasServoData()) &&
10297 Servo_Element_IsDisplayContents(parentElement)) {
10298 RefPtr<ComputedStyle> style =
10299 ServoStyleSet::ResolveServoStyle(*parentElement);
10300 // NOTE(emilio): we return a weak reference because the element also
10301 // holds the style context alive. This is a bit silly (we could've
10302 // returned a weak ref directly), but it's probably not worth
10303 // optimizing, given this function has just one caller which is rare,
10304 // and this path is rare itself.
10305 return style;
10308 } else {
10309 if (Style()->GetPseudoType() == PseudoStyleType::NotPseudo) {
10310 // We're a frame for the root. We have no style parent.
10311 return nullptr;
10316 if (!(mState & NS_FRAME_OUT_OF_FLOW)) {
10318 * If this frame is an anonymous block created when an inline with a block
10319 * inside it got split, then the parent style is on its preceding inline. We
10320 * can get to it using GetIBSplitSiblingForAnonymousBlock.
10322 if (mState & NS_FRAME_PART_OF_IBSPLIT) {
10323 nsIFrame* ibSplitSibling = GetIBSplitSiblingForAnonymousBlock(this);
10324 if (ibSplitSibling) {
10325 return (*aProviderFrame = ibSplitSibling)->Style();
10329 // If this frame is one of the blocks that split an inline, we must
10330 // return the "special" inline parent, i.e., the parent that this
10331 // frame would have if we didn't mangle the frame structure.
10332 *aProviderFrame = GetCorrectedParent(this);
10333 return *aProviderFrame ? (*aProviderFrame)->Style() : nullptr;
10336 // We're an out-of-flow frame. For out-of-flow frames, we must
10337 // resolve underneath the placeholder's parent. The placeholder is
10338 // reached from the first-in-flow.
10339 nsPlaceholderFrame* placeholder = FirstInFlow()->GetPlaceholderFrame();
10340 if (!placeholder) {
10341 MOZ_ASSERT_UNREACHABLE("no placeholder frame for out-of-flow frame");
10342 *aProviderFrame = GetCorrectedParent(this);
10343 return *aProviderFrame ? (*aProviderFrame)->Style() : nullptr;
10345 return placeholder->GetParentComputedStyleForOutOfFlow(aProviderFrame);
10348 void nsIFrame::GetLastLeaf(nsIFrame** aFrame) {
10349 if (!aFrame || !*aFrame) return;
10350 nsIFrame* child = *aFrame;
10351 // if we are a block frame then go for the last line of 'this'
10352 while (1) {
10353 child = child->PrincipalChildList().FirstChild();
10354 if (!child) return; // nothing to do
10355 nsIFrame* siblingFrame;
10356 nsIContent* content;
10357 // ignore anonymous elements, e.g. mozTableAdd* mozTableRemove*
10358 // see bug 278197 comment #12 #13 for details
10359 while ((siblingFrame = child->GetNextSibling()) &&
10360 (content = siblingFrame->GetContent()) &&
10361 !content->IsRootOfNativeAnonymousSubtree())
10362 child = siblingFrame;
10363 *aFrame = child;
10367 void nsIFrame::GetFirstLeaf(nsIFrame** aFrame) {
10368 if (!aFrame || !*aFrame) return;
10369 nsIFrame* child = *aFrame;
10370 while (1) {
10371 child = child->PrincipalChildList().FirstChild();
10372 if (!child) return; // nothing to do
10373 *aFrame = child;
10377 bool nsIFrame::IsFocusableDueToScrollFrame() {
10378 if (!IsScrollFrame()) {
10379 if (nsFieldSetFrame* fieldset = do_QueryFrame(this)) {
10380 // TODO: Do we have similar special-cases like this where we can have
10381 // anonymous scrollable boxes hanging off a primary frame?
10382 if (nsIFrame* inner = fieldset->GetInner()) {
10383 return inner->IsFocusableDueToScrollFrame();
10386 return false;
10388 if (!mContent->IsHTMLElement()) {
10389 return false;
10391 if (mContent->IsRootOfNativeAnonymousSubtree()) {
10392 return false;
10394 if (!mContent->GetParent()) {
10395 return false;
10397 if (mContent->AsElement()->HasAttr(nsGkAtoms::tabindex)) {
10398 return false;
10400 // Elements with scrollable view are focusable with script & tabbable
10401 // Otherwise you couldn't scroll them with keyboard, which is an accessibility
10402 // issue (e.g. Section 508 rules) However, we don't make them to be focusable
10403 // with the mouse, because the extra focus outlines are considered
10404 // unnecessarily ugly. When clicked on, the selection position within the
10405 // element will be enough to make them keyboard scrollable.
10406 nsIScrollableFrame* scrollFrame = do_QueryFrame(this);
10407 if (!scrollFrame) {
10408 return false;
10410 if (scrollFrame->IsForTextControlWithNoScrollbars()) {
10411 return false;
10413 if (scrollFrame->GetScrollStyles().IsHiddenInBothDirections()) {
10414 return false;
10416 if (scrollFrame->GetScrollRange().IsEqualEdges(nsRect(0, 0, 0, 0))) {
10417 return false;
10419 return true;
10422 nsIFrame::Focusable nsIFrame::IsFocusable(bool aWithMouse) {
10423 // cannot focus content in print preview mode. Only the root can be focused,
10424 // but that's handled elsewhere.
10425 if (PresContext()->Type() == nsPresContext::eContext_PrintPreview) {
10426 return {};
10429 if (!mContent || !mContent->IsElement()) {
10430 return {};
10433 if (!IsVisibleConsideringAncestors()) {
10434 return {};
10437 const nsStyleUI& ui = *StyleUI();
10438 if (ui.IsInert()) {
10439 return {};
10442 PseudoStyleType pseudo = Style()->GetPseudoType();
10443 if (pseudo == PseudoStyleType::anonymousFlexItem ||
10444 pseudo == PseudoStyleType::anonymousGridItem) {
10445 return {};
10448 int32_t tabIndex = -1;
10449 if (ui.UserFocus() != StyleUserFocus::Ignore &&
10450 ui.UserFocus() != StyleUserFocus::None) {
10451 // Pass in default tabindex of -1 for nonfocusable and 0 for focusable
10452 tabIndex = 0;
10455 if (mContent->IsFocusable(&tabIndex, aWithMouse)) {
10456 // If the content is focusable, then we're done.
10457 return {true, tabIndex};
10460 // If we're focusing with the mouse we never focus scroll areas.
10461 if (!aWithMouse && IsFocusableDueToScrollFrame()) {
10462 return {true, 0};
10465 return {false, tabIndex};
10469 * @return true if this text frame ends with a newline character which is
10470 * treated as preformatted. It should return false if this is not a text frame.
10472 bool nsIFrame::HasSignificantTerminalNewline() const { return false; }
10474 static StyleVerticalAlignKeyword ConvertSVGDominantBaselineToVerticalAlign(
10475 StyleDominantBaseline aDominantBaseline) {
10476 // Most of these are approximate mappings.
10477 switch (aDominantBaseline) {
10478 case StyleDominantBaseline::Hanging:
10479 case StyleDominantBaseline::TextBeforeEdge:
10480 return StyleVerticalAlignKeyword::TextTop;
10481 case StyleDominantBaseline::TextAfterEdge:
10482 case StyleDominantBaseline::Ideographic:
10483 return StyleVerticalAlignKeyword::TextBottom;
10484 case StyleDominantBaseline::Central:
10485 case StyleDominantBaseline::Middle:
10486 case StyleDominantBaseline::Mathematical:
10487 return StyleVerticalAlignKeyword::Middle;
10488 case StyleDominantBaseline::Auto:
10489 case StyleDominantBaseline::Alphabetic:
10490 return StyleVerticalAlignKeyword::Baseline;
10491 default:
10492 MOZ_ASSERT_UNREACHABLE("unexpected aDominantBaseline value");
10493 return StyleVerticalAlignKeyword::Baseline;
10497 Maybe<StyleVerticalAlignKeyword> nsIFrame::VerticalAlignEnum() const {
10498 if (SVGUtils::IsInSVGTextSubtree(this)) {
10499 StyleDominantBaseline dominantBaseline = StyleSVG()->mDominantBaseline;
10500 return Some(ConvertSVGDominantBaselineToVerticalAlign(dominantBaseline));
10503 const auto& verticalAlign = StyleDisplay()->mVerticalAlign;
10504 if (verticalAlign.IsKeyword()) {
10505 return Some(verticalAlign.AsKeyword());
10508 return Nothing();
10511 NS_IMETHODIMP
10512 nsIFrame::RefreshSizeCache(nsBoxLayoutState& aState) {
10513 // XXXbz this comment needs some rewriting to make sense in the
10514 // post-reflow-branch world.
10516 // Ok we need to compute our minimum, preferred, and maximum sizes.
10517 // 1) Maximum size. This is easy. Its infinite unless it is overloaded by CSS.
10518 // 2) Preferred size. This is a little harder. This is the size the
10519 // block would be if it were laid out on an infinite canvas. So we can
10520 // get this by reflowing the block with and INTRINSIC width and height. We
10521 // can also do a nice optimization for incremental reflow. If the reflow is
10522 // incremental then we can pass a flag to have the block compute the
10523 // preferred width for us! Preferred height can just be the minimum height;
10524 // 3) Minimum size. This is a toughy. We can pass the block a flag asking for
10525 // the max element size. That would give us the width. Unfortunately you
10526 // can only ask for a maxElementSize during an incremental reflow. So on
10527 // other reflows we will just have to use 0. The min height on the other
10528 // hand is fairly easy we need to get the largest line height. This can be
10529 // done with the line iterator.
10531 // if we do have a rendering context
10532 gfxContext* rendContext = aState.GetRenderingContext();
10533 if (rendContext) {
10534 nsPresContext* presContext = aState.PresContext();
10536 // If we don't have any HTML constraints and it's a resize, then nothing in
10537 // the block could have changed, so no refresh is necessary.
10538 nsBoxLayoutMetrics* metrics = BoxMetrics();
10539 if (!XULNeedsRecalc(metrics->mBlockPrefSize)) {
10540 return NS_OK;
10543 // the rect we plan to size to.
10544 nsRect rect = GetRect();
10546 nsMargin bp(0, 0, 0, 0);
10547 GetXULBorderAndPadding(bp);
10550 // If we're a container for font size inflation, then shrink
10551 // wrapping inside of us should not apply font size inflation.
10552 AutoMaybeDisableFontInflation an(this);
10554 metrics->mBlockPrefSize.width =
10555 GetPrefISize(rendContext) + bp.LeftRight();
10556 metrics->mBlockMinSize.width = GetMinISize(rendContext) + bp.LeftRight();
10559 // do the nasty.
10560 const WritingMode wm = aState.OuterReflowInput()
10561 ? aState.OuterReflowInput()->GetWritingMode()
10562 : GetWritingMode();
10563 ReflowOutput desiredSize(wm);
10564 BoxReflow(aState, presContext, desiredSize, rendContext, rect.x, rect.y,
10565 metrics->mBlockPrefSize.width, NS_UNCONSTRAINEDSIZE);
10567 metrics->mBlockMinSize.height = 0;
10568 // ok we need the max ascent of the items on the line. So to do this
10569 // ask the block for its line iterator. Get the max ascent.
10570 nsAutoLineIterator lines = GetLineIterator();
10571 if (lines) {
10572 metrics->mBlockMinSize.height = 0;
10573 int32_t lineCount = lines->GetNumLines();
10574 for (int32_t i = 0; i < lineCount; ++i) {
10575 auto line = lines->GetLine(i).unwrap();
10577 if (line.mLineBounds.height > metrics->mBlockMinSize.height) {
10578 metrics->mBlockMinSize.height = line.mLineBounds.height;
10581 } else {
10582 metrics->mBlockMinSize.height = desiredSize.Height();
10585 metrics->mBlockPrefSize.height = metrics->mBlockMinSize.height;
10587 if (desiredSize.BlockStartAscent() == ReflowOutput::ASK_FOR_BASELINE) {
10588 if (!nsLayoutUtils::GetFirstLineBaseline(wm, this,
10589 &metrics->mBlockAscent))
10590 metrics->mBlockAscent = GetLogicalBaseline(wm);
10591 } else {
10592 metrics->mBlockAscent = desiredSize.BlockStartAscent();
10595 #ifdef DEBUG_adaptor
10596 printf("min=(%d,%d), pref=(%d,%d), ascent=%d\n",
10597 metrics->mBlockMinSize.width, metrics->mBlockMinSize.height,
10598 metrics->mBlockPrefSize.width, metrics->mBlockPrefSize.height,
10599 metrics->mBlockAscent);
10600 #endif
10603 return NS_OK;
10606 nsSize nsIFrame::GetXULPrefSize(nsBoxLayoutState& aState) {
10607 nsSize size(0, 0);
10608 DISPLAY_PREF_SIZE(this, size);
10609 // If the size is cached, and there are no HTML constraints that we might
10610 // be depending on, then we just return the cached size.
10611 nsBoxLayoutMetrics* metrics = BoxMetrics();
10612 if (!XULNeedsRecalc(metrics->mPrefSize)) {
10613 size = metrics->mPrefSize;
10614 return size;
10617 if (IsXULCollapsed()) return size;
10619 // get our size in CSS.
10620 bool widthSet, heightSet;
10621 bool completelyRedefined =
10622 nsIFrame::AddXULPrefSize(this, size, widthSet, heightSet);
10624 // Refresh our caches with new sizes.
10625 if (!completelyRedefined) {
10626 RefreshSizeCache(aState);
10627 nsSize blockSize = metrics->mBlockPrefSize;
10629 // notice we don't need to add our borders or padding
10630 // in. That's because the block did it for us.
10631 if (!widthSet) size.width = blockSize.width;
10632 if (!heightSet) size.height = blockSize.height;
10635 metrics->mPrefSize = size;
10636 return size;
10639 nsSize nsIFrame::GetXULMinSize(nsBoxLayoutState& aState) {
10640 nsSize size(0, 0);
10641 DISPLAY_MIN_SIZE(this, size);
10642 // Don't use the cache if we have HTMLReflowInput constraints --- they might
10643 // have changed
10644 nsBoxLayoutMetrics* metrics = BoxMetrics();
10645 if (!XULNeedsRecalc(metrics->mMinSize)) {
10646 size = metrics->mMinSize;
10647 return size;
10650 if (IsXULCollapsed()) return size;
10652 // get our size in CSS.
10653 bool widthSet, heightSet;
10654 bool completelyRedefined =
10655 nsIFrame::AddXULMinSize(this, size, widthSet, heightSet);
10657 // Refresh our caches with new sizes.
10658 if (!completelyRedefined) {
10659 RefreshSizeCache(aState);
10660 nsSize blockSize = metrics->mBlockMinSize;
10662 if (!widthSet) size.width = blockSize.width;
10663 if (!heightSet) size.height = blockSize.height;
10666 metrics->mMinSize = size;
10667 return size;
10670 nsSize nsIFrame::GetXULMaxSize(nsBoxLayoutState& aState) {
10671 nsSize size(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
10672 DISPLAY_MAX_SIZE(this, size);
10673 // Don't use the cache if we have HTMLReflowInput constraints --- they might
10674 // have changed
10675 nsBoxLayoutMetrics* metrics = BoxMetrics();
10676 if (!XULNeedsRecalc(metrics->mMaxSize)) {
10677 size = metrics->mMaxSize;
10678 return size;
10681 if (IsXULCollapsed()) return size;
10683 size = nsIFrame::GetUncachedXULMaxSize(aState);
10684 metrics->mMaxSize = size;
10686 return size;
10689 nscoord nsIFrame::GetXULFlex() {
10690 nsBoxLayoutMetrics* metrics = BoxMetrics();
10691 if (XULNeedsRecalc(metrics->mFlex)) {
10692 nsIFrame::AddXULFlex(this, metrics->mFlex);
10695 return metrics->mFlex;
10698 nscoord nsIFrame::GetXULBoxAscent(nsBoxLayoutState& aState) {
10699 nsBoxLayoutMetrics* metrics = BoxMetrics();
10700 if (!XULNeedsRecalc(metrics->mAscent)) {
10701 return metrics->mAscent;
10704 if (IsXULCollapsed()) {
10705 metrics->mAscent = 0;
10706 } else {
10707 // Refresh our caches with new sizes.
10708 RefreshSizeCache(aState);
10709 metrics->mAscent = metrics->mBlockAscent;
10712 return metrics->mAscent;
10715 nsresult nsIFrame::DoXULLayout(nsBoxLayoutState& aState) {
10716 nsRect ourRect(mRect);
10718 gfxContext* rendContext = aState.GetRenderingContext();
10719 nsPresContext* presContext = aState.PresContext();
10720 WritingMode ourWM = GetWritingMode();
10721 const WritingMode outerWM = aState.OuterReflowInput()
10722 ? aState.OuterReflowInput()->GetWritingMode()
10723 : ourWM;
10724 ReflowOutput desiredSize(outerWM);
10725 LogicalSize ourSize = GetLogicalSize(outerWM);
10727 if (rendContext) {
10728 BoxReflow(aState, presContext, desiredSize, rendContext, ourRect.x,
10729 ourRect.y, ourRect.width, ourRect.height);
10731 if (IsXULCollapsed()) {
10732 SetSize(nsSize(0, 0));
10733 } else {
10734 // if our child needs to be bigger. This might happend with
10735 // wrapping text. There is no way to predict its height until we
10736 // reflow it. Now that we know the height reshuffle upward.
10737 if (desiredSize.ISize(outerWM) > ourSize.ISize(outerWM) ||
10738 desiredSize.BSize(outerWM) > ourSize.BSize(outerWM)) {
10739 #ifdef DEBUG_GROW
10740 XULDumpBox(stdout);
10741 printf(" GREW from (%d,%d) -> (%d,%d)\n", ourSize.ISize(outerWM),
10742 ourSize.BSize(outerWM), desiredSize.ISize(outerWM),
10743 desiredSize.BSize(outerWM));
10744 #endif
10746 if (desiredSize.ISize(outerWM) > ourSize.ISize(outerWM)) {
10747 ourSize.ISize(outerWM) = desiredSize.ISize(outerWM);
10750 if (desiredSize.BSize(outerWM) > ourSize.BSize(outerWM)) {
10751 ourSize.BSize(outerWM) = desiredSize.BSize(outerWM);
10755 // ensure our size is what we think is should be. Someone could have
10756 // reset the frame to be smaller or something dumb like that.
10757 SetSize(ourSize.ConvertTo(ourWM, outerWM));
10761 // Should we do this if IsXULCollapsed() is true?
10762 LogicalSize size(GetLogicalSize(outerWM));
10763 desiredSize.ISize(outerWM) = size.ISize(outerWM);
10764 desiredSize.BSize(outerWM) = size.BSize(outerWM);
10765 desiredSize.UnionOverflowAreasWithDesiredBounds();
10767 if (HasAbsolutelyPositionedChildren()) {
10768 // Set up a |reflowInput| to pass into ReflowAbsoluteFrames
10769 ReflowInput reflowInput(aState.PresContext(), this,
10770 aState.GetRenderingContext(),
10771 LogicalSize(ourWM, ISize(), NS_UNCONSTRAINEDSIZE),
10772 ReflowInput::InitFlag::DummyParentReflowInput);
10774 AddStateBits(NS_FRAME_IN_REFLOW);
10775 // Set up a |reflowStatus| to pass into ReflowAbsoluteFrames
10776 // (just a dummy value; hopefully that's OK)
10777 nsReflowStatus reflowStatus;
10778 ReflowAbsoluteFrames(aState.PresContext(), desiredSize, reflowInput,
10779 reflowStatus);
10780 RemoveStateBits(NS_FRAME_IN_REFLOW);
10783 nsSize oldSize(ourRect.Size());
10784 FinishAndStoreOverflow(desiredSize.mOverflowAreas,
10785 size.GetPhysicalSize(outerWM), &oldSize);
10787 SyncXULLayout(aState);
10789 return NS_OK;
10792 void nsIFrame::BoxReflow(nsBoxLayoutState& aState, nsPresContext* aPresContext,
10793 ReflowOutput& aDesiredSize,
10794 gfxContext* aRenderingContext, nscoord aX, nscoord aY,
10795 nscoord aWidth, nscoord aHeight, bool aMoveFrame) {
10796 DO_GLOBAL_REFLOW_COUNT("nsBoxToBlockAdaptor");
10798 nsBoxLayoutMetrics* metrics = BoxMetrics();
10799 if (MOZ_UNLIKELY(!metrics)) {
10800 // Can't proceed without BoxMetrics. This should only happen if something
10801 // is seriously broken, e.g. if we try to do XUL layout on a non-XUL frame.
10802 // (If this is a content process, we'll abort even in release builds,
10803 // because XUL layout mixup is extra surprising in content, and aborts are
10804 // less catastrophic in content vs. in chrome.)
10805 MOZ_RELEASE_ASSERT(!XRE_IsContentProcess(),
10806 "Starting XUL BoxReflow w/o BoxMetrics (in content)?");
10807 MOZ_ASSERT_UNREACHABLE("Starting XUL BoxReflow w/o BoxMetrics?");
10808 return;
10811 nsReflowStatus status;
10813 bool needsReflow = IsSubtreeDirty();
10815 // if we don't need a reflow then
10816 // lets see if we are already that size. Yes? then don't even reflow. We are
10817 // done.
10818 if (!needsReflow) {
10819 if (aWidth != NS_UNCONSTRAINEDSIZE && aHeight != NS_UNCONSTRAINEDSIZE) {
10820 // if the new calculated size has a 0 width or a 0 height
10821 if ((metrics->mLastSize.width == 0 || metrics->mLastSize.height == 0) &&
10822 (aWidth == 0 || aHeight == 0)) {
10823 needsReflow = false;
10824 aDesiredSize.Width() = aWidth;
10825 aDesiredSize.Height() = aHeight;
10826 SetSize(aDesiredSize.Size(GetWritingMode()));
10827 } else {
10828 aDesiredSize.Width() = metrics->mLastSize.width;
10829 aDesiredSize.Height() = metrics->mLastSize.height;
10831 // remove the margin. The rect of our child does not include it but our
10832 // calculated size does. don't reflow if we are already the right size
10833 if (metrics->mLastSize.width == aWidth &&
10834 metrics->mLastSize.height == aHeight)
10835 needsReflow = false;
10836 else
10837 needsReflow = true;
10839 } else {
10840 // if the width or height are intrinsic alway reflow because
10841 // we don't know what it should be.
10842 needsReflow = true;
10846 // ok now reflow the child into the spacers calculated space
10847 if (needsReflow) {
10848 aDesiredSize.ClearSize();
10850 // create a reflow input to tell our child to flow at the given size.
10852 // Construct a bogus parent reflow input so that there's a usable reflow
10853 // input for the containing block.
10854 nsMargin margin(0, 0, 0, 0);
10855 GetXULMargin(margin);
10857 nsSize parentSize(aWidth, aHeight);
10858 if (parentSize.height != NS_UNCONSTRAINEDSIZE)
10859 parentSize.height += margin.TopBottom();
10860 if (parentSize.width != NS_UNCONSTRAINEDSIZE)
10861 parentSize.width += margin.LeftRight();
10863 nsIFrame* parentFrame = GetParent();
10864 WritingMode parentWM = parentFrame->GetWritingMode();
10865 ReflowInput parentReflowInput(
10866 aPresContext, parentFrame, aRenderingContext,
10867 LogicalSize(parentWM, parentSize),
10868 ReflowInput::InitFlag::DummyParentReflowInput);
10870 // This may not do very much useful, but it's probably worth trying.
10871 if (parentSize.width != NS_UNCONSTRAINEDSIZE)
10872 parentReflowInput.SetComputedWidth(std::max(parentSize.width, 0));
10873 if (parentSize.height != NS_UNCONSTRAINEDSIZE)
10874 parentReflowInput.SetComputedHeight(std::max(parentSize.height, 0));
10875 parentReflowInput.SetComputedLogicalMargin(parentWM,
10876 LogicalMargin(parentWM));
10877 // XXX use box methods
10878 nsMargin padding;
10879 parentFrame->GetXULPadding(padding);
10880 parentReflowInput.SetComputedLogicalPadding(
10881 parentWM, LogicalMargin(parentWM, padding));
10882 nsMargin border;
10883 parentFrame->GetXULBorder(border);
10884 parentReflowInput.SetComputedLogicalBorderPadding(
10885 parentWM, LogicalMargin(parentWM, border + padding));
10887 // Construct the parent chain manually since constructing it normally
10888 // messes up dimensions.
10889 const ReflowInput* outerReflowInput = aState.OuterReflowInput();
10890 NS_ASSERTION(!outerReflowInput || outerReflowInput->mFrame != this,
10891 "in and out of XUL on a single frame?");
10892 const ReflowInput* parentRI;
10893 if (outerReflowInput && outerReflowInput->mFrame == parentFrame) {
10894 // We're a frame (such as a text control frame) that jumps into
10895 // box reflow and then straight out of it on the child frame.
10896 // This means we actually have a real parent reflow input.
10897 // nsLayoutUtils::InflationMinFontSizeFor used to need this to be
10898 // linked up correctly for text control frames, so do so here).
10899 parentRI = outerReflowInput;
10900 } else {
10901 parentRI = &parentReflowInput;
10904 // XXX Is it OK that this reflow input has only one ancestor?
10905 // (It used to have a bogus parent, skipping all the boxes).
10906 WritingMode wm = GetWritingMode();
10907 LogicalSize logicalSize(wm, nsSize(aWidth, aHeight));
10908 logicalSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
10909 ReflowInput reflowInput(aPresContext, *parentRI, this, logicalSize,
10910 Nothing(),
10911 ReflowInput::InitFlag::DummyParentReflowInput);
10913 // XXX_jwir3: This is somewhat fishy. If this is actually changing the value
10914 // here (which it might be), then we should make sure that it's
10915 // correct the first time around, rather than changing it later.
10916 reflowInput.mCBReflowInput = parentRI;
10918 reflowInput.mReflowDepth = aState.GetReflowDepth();
10920 // mComputedWidth and mComputedHeight are content-box, not
10921 // border-box
10922 if (aWidth != NS_UNCONSTRAINEDSIZE) {
10923 nscoord computedWidth =
10924 aWidth - reflowInput.ComputedPhysicalBorderPadding().LeftRight();
10925 computedWidth = std::max(computedWidth, 0);
10926 reflowInput.SetComputedWidth(computedWidth);
10929 // Most child frames of box frames (e.g. subdocument or scroll frames)
10930 // need to be constrained to the provided size and overflow as necessary.
10931 // The one exception are block frames, because we need to know their
10932 // natural height excluding any overflow area which may be caused by
10933 // various CSS effects such as shadow or outline.
10934 if (!IsBlockFrameOrSubclass()) {
10935 if (aHeight != NS_UNCONSTRAINEDSIZE) {
10936 nscoord computedHeight =
10937 aHeight - reflowInput.ComputedPhysicalBorderPadding().TopBottom();
10938 computedHeight = std::max(computedHeight, 0);
10939 reflowInput.SetComputedHeight(computedHeight);
10940 } else {
10941 reflowInput.SetComputedHeight(
10942 ComputeSize(
10943 aRenderingContext, wm, logicalSize, logicalSize.ISize(wm),
10944 reflowInput.ComputedLogicalMargin(wm).Size(wm),
10945 reflowInput.ComputedLogicalBorderPadding(wm).Size(wm), {}, {})
10946 .mLogicalSize.Height(wm));
10950 // Box layout calls SetRect before XULLayout, whereas non-box layout
10951 // calls SetRect after Reflow.
10952 // XXX Perhaps we should be doing this by twiddling the rect back to
10953 // mLastSize before calling Reflow and then switching it back, but
10954 // However, mLastSize can also be the size passed to BoxReflow by
10955 // RefreshSizeCache, so that doesn't really make sense.
10956 if (metrics->mLastSize.width != aWidth) {
10957 reflowInput.SetHResize(true);
10959 // When font size inflation is enabled, a horizontal resize
10960 // requires a full reflow. See ReflowInput::InitResizeFlags
10961 // for more details.
10962 if (nsLayoutUtils::FontSizeInflationEnabled(aPresContext)) {
10963 this->MarkSubtreeDirty();
10966 if (metrics->mLastSize.height != aHeight) {
10967 reflowInput.SetVResize(true);
10970 // place the child and reflow
10972 Reflow(aPresContext, aDesiredSize, reflowInput, status);
10974 NS_ASSERTION(status.IsComplete(), "bad status");
10976 ReflowChildFlags layoutFlags = aState.LayoutFlags();
10977 nsContainerFrame::FinishReflowChild(
10978 this, aPresContext, aDesiredSize, &reflowInput, aX, aY,
10979 layoutFlags | ReflowChildFlags::NoMoveFrame);
10981 // Save the ascent. (bug 103925)
10982 if (IsXULCollapsed()) {
10983 metrics->mAscent = 0;
10984 } else {
10985 if (aDesiredSize.BlockStartAscent() == ReflowOutput::ASK_FOR_BASELINE) {
10986 if (!nsLayoutUtils::GetFirstLineBaseline(wm, this, &metrics->mAscent))
10987 metrics->mAscent = GetLogicalBaseline(wm);
10988 } else
10989 metrics->mAscent = aDesiredSize.BlockStartAscent();
10992 } else {
10993 aDesiredSize.SetBlockStartAscent(metrics->mBlockAscent);
10996 metrics->mLastSize.width = aDesiredSize.Width();
10997 metrics->mLastSize.height = aDesiredSize.Height();
11000 nsBoxLayoutMetrics* nsIFrame::BoxMetrics() const {
11001 nsBoxLayoutMetrics* metrics = GetProperty(BoxMetricsProperty());
11002 NS_ASSERTION(
11003 metrics,
11004 "A box layout method was called but InitBoxMetrics was never called");
11005 return metrics;
11008 void nsIFrame::UpdateStyleOfChildAnonBox(nsIFrame* aChildFrame,
11009 ServoRestyleState& aRestyleState) {
11010 #ifdef DEBUG
11011 nsIFrame* parent = aChildFrame->GetInFlowParent();
11012 if (aChildFrame->IsTableFrame()) {
11013 parent = parent->GetParent();
11015 if (parent->IsLineFrame()) {
11016 parent = parent->GetParent();
11018 MOZ_ASSERT(nsLayoutUtils::FirstContinuationOrIBSplitSibling(parent) == this,
11019 "This should only be used for children!");
11020 #endif // DEBUG
11021 MOZ_ASSERT(!GetContent() || !aChildFrame->GetContent() ||
11022 aChildFrame->GetContent() == GetContent(),
11023 "What content node is it a frame for?");
11024 MOZ_ASSERT(!aChildFrame->GetPrevContinuation(),
11025 "Only first continuations should end up here");
11027 // We could force the caller to pass in the pseudo, since some callers know it
11028 // statically... But this API is a bit nicer.
11029 auto pseudo = aChildFrame->Style()->GetPseudoType();
11030 MOZ_ASSERT(PseudoStyle::IsAnonBox(pseudo), "Child is not an anon box?");
11031 MOZ_ASSERT(!PseudoStyle::IsNonInheritingAnonBox(pseudo),
11032 "Why did the caller bother calling us?");
11034 // Anon boxes inherit from their parent; that's us.
11035 RefPtr<ComputedStyle> newContext =
11036 aRestyleState.StyleSet().ResolveInheritingAnonymousBoxStyle(pseudo,
11037 Style());
11039 nsChangeHint childHint =
11040 UpdateStyleOfOwnedChildFrame(aChildFrame, newContext, aRestyleState);
11042 // Now that we've updated the style on aChildFrame, check whether it itself
11043 // has anon boxes to deal with.
11044 ServoRestyleState childrenState(*aChildFrame, aRestyleState, childHint,
11045 ServoRestyleState::Type::InFlow);
11046 aChildFrame->UpdateStyleOfOwnedAnonBoxes(childrenState);
11048 // Assuming anon boxes don't have ::backdrop associated with them... if that
11049 // ever changes, we'd need to handle that here, like we do in
11050 // RestyleManager::ProcessPostTraversal
11052 // We do need to handle block pseudo-elements here, though. Especially list
11053 // bullets.
11054 if (nsBlockFrame* block = do_QueryFrame(aChildFrame)) {
11055 block->UpdatePseudoElementStyles(childrenState);
11059 /* static */
11060 nsChangeHint nsIFrame::UpdateStyleOfOwnedChildFrame(
11061 nsIFrame* aChildFrame, ComputedStyle* aNewComputedStyle,
11062 ServoRestyleState& aRestyleState,
11063 const Maybe<ComputedStyle*>& aContinuationComputedStyle) {
11064 MOZ_ASSERT(!aChildFrame->GetAdditionalComputedStyle(0),
11065 "We don't handle additional styles here");
11067 // Figure out whether we have an actual change. It's important that we do
11068 // this, for several reasons:
11070 // 1) Even if all the child's changes are due to properties it inherits from
11071 // us, it's possible that no one ever asked us for those style structs and
11072 // hence changes to them aren't reflected in the changes handled at all.
11074 // 2) Content can change stylesheets that change the styles of pseudos, and
11075 // extensions can add/remove stylesheets that change the styles of
11076 // anonymous boxes directly.
11077 uint32_t equalStructs; // Not used, actually.
11078 nsChangeHint childHint = aChildFrame->Style()->CalcStyleDifference(
11079 *aNewComputedStyle, &equalStructs);
11081 // If aChildFrame is out of flow, then aRestyleState's "changes handled by the
11082 // parent" doesn't apply to it, because it may have some other parent in the
11083 // frame tree.
11084 if (!aChildFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
11085 childHint = NS_RemoveSubsumedHints(
11086 childHint, aRestyleState.ChangesHandledFor(aChildFrame));
11088 if (childHint) {
11089 if (childHint & nsChangeHint_ReconstructFrame) {
11090 // If we generate a reconstruct here, remove any non-reconstruct hints we
11091 // may have already generated for this content.
11092 aRestyleState.ChangeList().PopChangesForContent(
11093 aChildFrame->GetContent());
11095 aRestyleState.ChangeList().AppendChange(
11096 aChildFrame, aChildFrame->GetContent(), childHint);
11099 aChildFrame->SetComputedStyle(aNewComputedStyle);
11100 ComputedStyle* continuationStyle = aContinuationComputedStyle
11101 ? *aContinuationComputedStyle
11102 : aNewComputedStyle;
11103 for (nsIFrame* kid = aChildFrame->GetNextContinuation(); kid;
11104 kid = kid->GetNextContinuation()) {
11105 MOZ_ASSERT(!kid->GetAdditionalComputedStyle(0));
11106 kid->SetComputedStyle(continuationStyle);
11109 return childHint;
11112 /* static */
11113 void nsIFrame::AddInPopupStateBitToDescendants(nsIFrame* aFrame) {
11114 if (!aFrame->HasAnyStateBits(NS_FRAME_IN_POPUP) &&
11115 aFrame->TrackingVisibility()) {
11116 // Assume all frames in popups are visible.
11117 aFrame->IncApproximateVisibleCount();
11120 aFrame->AddStateBits(NS_FRAME_IN_POPUP);
11122 for (const auto& childList : aFrame->CrossDocChildLists()) {
11123 for (nsIFrame* child : childList.mList) {
11124 AddInPopupStateBitToDescendants(child);
11129 /* static */
11130 void nsIFrame::RemoveInPopupStateBitFromDescendants(nsIFrame* aFrame) {
11131 if (!aFrame->HasAnyStateBits(NS_FRAME_IN_POPUP) ||
11132 nsLayoutUtils::IsPopup(aFrame)) {
11133 return;
11136 aFrame->RemoveStateBits(NS_FRAME_IN_POPUP);
11138 if (aFrame->TrackingVisibility()) {
11139 // We assume all frames in popups are visible, so this decrement balances
11140 // out the increment in AddInPopupStateBitToDescendants above.
11141 aFrame->DecApproximateVisibleCount();
11143 for (const auto& childList : aFrame->CrossDocChildLists()) {
11144 for (nsIFrame* child : childList.mList) {
11145 RemoveInPopupStateBitFromDescendants(child);
11150 void nsIFrame::SetParent(nsContainerFrame* aParent) {
11151 // If our parent is a wrapper anon box, our new parent should be too. We
11152 // _can_ change parent if our parent is a wrapper anon box, because some
11153 // wrapper anon boxes can have continuations.
11154 MOZ_ASSERT_IF(ParentIsWrapperAnonBox(),
11155 aParent->Style()->IsInheritingAnonBox());
11157 // Note that the current mParent may already be destroyed at this point.
11158 mParent = aParent;
11159 MOZ_DIAGNOSTIC_ASSERT(!mParent || PresShell() == mParent->PresShell());
11160 if (::IsXULBoxWrapped(this)) {
11161 ::InitBoxMetrics(this, true);
11162 } else {
11163 // We could call Properties().Delete(BoxMetricsProperty()); here but
11164 // that's kind of slow and re-parenting in such a way that we were
11165 // IsXULBoxWrapped() before but not now should be very rare, so we'll just
11166 // keep this unused frame property until this frame dies instead.
11169 if (HasAnyStateBits(NS_FRAME_HAS_VIEW | NS_FRAME_HAS_CHILD_WITH_VIEW)) {
11170 for (nsIFrame* f = aParent;
11171 f && !f->HasAnyStateBits(NS_FRAME_HAS_CHILD_WITH_VIEW);
11172 f = f->GetParent()) {
11173 f->AddStateBits(NS_FRAME_HAS_CHILD_WITH_VIEW);
11177 if (HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)) {
11178 for (nsIFrame* f = aParent; f; f = f->GetParent()) {
11179 if (f->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)) {
11180 break;
11182 f->AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
11186 if (HasAnyStateBits(NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE)) {
11187 for (nsIFrame* f = aParent; f; f = f->GetParent()) {
11188 if (f->HasAnyStateBits(
11189 NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE)) {
11190 break;
11192 f->AddStateBits(NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE);
11196 if (HasInvalidFrameInSubtree()) {
11197 for (nsIFrame* f = aParent;
11198 f && !f->HasAnyStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT |
11199 NS_FRAME_IS_NONDISPLAY);
11200 f = nsLayoutUtils::GetCrossDocParentFrameInProcess(f)) {
11201 f->AddStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT);
11205 if (aParent->HasAnyStateBits(NS_FRAME_IN_POPUP)) {
11206 AddInPopupStateBitToDescendants(this);
11207 } else {
11208 RemoveInPopupStateBitFromDescendants(this);
11211 // If our new parent only has invalid children, then we just invalidate
11212 // ourselves too. This is probably faster than clearing the flag all
11213 // the way up the frame tree.
11214 if (aParent->HasAnyStateBits(NS_FRAME_ALL_DESCENDANTS_NEED_PAINT)) {
11215 InvalidateFrame();
11216 } else {
11217 SchedulePaint();
11221 void nsIFrame::CreateOwnLayerIfNeeded(nsDisplayListBuilder* aBuilder,
11222 nsDisplayList* aList, uint16_t aType,
11223 bool* aCreatedContainerItem) {
11224 if (GetContent() && GetContent()->IsXULElement() &&
11225 GetContent()->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::layer)) {
11226 aList->AppendNewToTopWithIndex<nsDisplayOwnLayer>(
11227 aBuilder, this, /* aIndex = */ aType, aList,
11228 aBuilder->CurrentActiveScrolledRoot(), nsDisplayOwnLayerFlags::None,
11229 ScrollbarData{}, true, false);
11230 if (aCreatedContainerItem) {
11231 *aCreatedContainerItem = true;
11236 bool nsIFrame::IsStackingContext(const nsStyleDisplay* aStyleDisplay,
11237 const nsStyleEffects* aStyleEffects) {
11238 // Properties that influence the output of this function should be handled in
11239 // change_bits_for_longhand as well.
11240 if (HasOpacity(aStyleDisplay, aStyleEffects, nullptr)) {
11241 return true;
11243 if (IsTransformed()) {
11244 return true;
11246 auto willChange = aStyleDisplay->mWillChange.bits;
11247 if (aStyleDisplay->IsContainPaint() || aStyleDisplay->IsContainLayout() ||
11248 willChange & StyleWillChangeBits::CONTAIN) {
11249 if (IsFrameOfType(eSupportsContainLayoutAndPaint)) {
11250 return true;
11253 // strictly speaking, 'perspective' doesn't require visual atomicity,
11254 // but the spec says it acts like the rest of these
11255 if (aStyleDisplay->HasPerspectiveStyle() ||
11256 willChange & StyleWillChangeBits::PERSPECTIVE) {
11257 if (IsFrameOfType(eSupportsCSSTransforms)) {
11258 return true;
11261 if (!StylePosition()->mZIndex.IsAuto() ||
11262 willChange & StyleWillChangeBits::Z_INDEX) {
11263 if (ZIndexApplies()) {
11264 return true;
11267 return aStyleEffects->mMixBlendMode != StyleBlend::Normal ||
11268 SVGIntegrationUtils::UsingEffectsForFrame(this) ||
11269 aStyleDisplay->IsPositionForcingStackingContext() ||
11270 aStyleDisplay->mIsolation != StyleIsolation::Auto ||
11271 willChange & StyleWillChangeBits::STACKING_CONTEXT_UNCONDITIONAL;
11274 bool nsIFrame::IsStackingContext() {
11275 return IsStackingContext(StyleDisplay(), StyleEffects());
11278 static bool IsFrameScrolledOutOfView(const nsIFrame* aTarget,
11279 const nsRect& aTargetRect,
11280 const nsIFrame* aParent) {
11281 // The ancestor frame we are checking if it clips out aTargetRect relative to
11282 // aTarget.
11283 nsIFrame* clipParent = nullptr;
11285 // find the first scrollable frame or root frame if we are in a fixed pos
11286 // subtree
11287 for (nsIFrame* f = const_cast<nsIFrame*>(aParent); f;
11288 f = nsLayoutUtils::GetCrossDocParentFrameInProcess(f)) {
11289 nsIScrollableFrame* scrollableFrame = do_QueryFrame(f);
11290 if (scrollableFrame) {
11291 clipParent = f;
11292 break;
11294 if (f->StyleDisplay()->mPosition == StylePositionProperty::Fixed &&
11295 nsLayoutUtils::IsReallyFixedPos(f)) {
11296 clipParent = f->GetParent();
11297 break;
11301 if (!clipParent) {
11302 // Even if we couldn't find the nearest scrollable frame, it might mean we
11303 // are in an out-of-process iframe, try to see if |aTarget| frame is
11304 // scrolled out of view in an scrollable frame in a cross-process ancestor
11305 // document.
11306 return nsLayoutUtils::FrameIsScrolledOutOfViewInCrossProcess(aTarget);
11309 nsRect clipRect = clipParent->InkOverflowRectRelativeToSelf();
11310 // We consider that the target is scrolled out if the scrollable (or root)
11311 // frame is empty.
11312 if (clipRect.IsEmpty()) {
11313 return true;
11316 nsRect transformedRect = nsLayoutUtils::TransformFrameRectToAncestor(
11317 aTarget, aTargetRect, clipParent);
11319 if (transformedRect.IsEmpty()) {
11320 // If the transformed rect is empty it represents a line or a point that we
11321 // should check is outside the the scrollable rect.
11322 if (transformedRect.x > clipRect.XMost() ||
11323 transformedRect.y > clipRect.YMost() ||
11324 clipRect.x > transformedRect.XMost() ||
11325 clipRect.y > transformedRect.YMost()) {
11326 return true;
11328 } else if (!transformedRect.Intersects(clipRect)) {
11329 return true;
11332 nsIFrame* parent = clipParent->GetParent();
11333 if (!parent) {
11334 return false;
11337 return IsFrameScrolledOutOfView(aTarget, aTargetRect, parent);
11340 bool nsIFrame::IsScrolledOutOfView() const {
11341 nsRect rect = InkOverflowRectRelativeToSelf();
11342 return IsFrameScrolledOutOfView(this, rect, this);
11345 gfx::Matrix nsIFrame::ComputeWidgetTransform() {
11346 const nsStyleUIReset* uiReset = StyleUIReset();
11347 if (uiReset->mMozWindowTransform.IsNone()) {
11348 return gfx::Matrix();
11351 TransformReferenceBox refBox(nullptr, nsRect(nsPoint(), GetSize()));
11353 nsPresContext* presContext = PresContext();
11354 int32_t appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
11355 gfx::Matrix4x4 matrix = nsStyleTransformMatrix::ReadTransforms(
11356 uiReset->mMozWindowTransform, refBox, float(appUnitsPerDevPixel));
11358 // Apply the -moz-window-transform-origin translation to the matrix.
11359 const StyleTransformOrigin& origin = uiReset->mWindowTransformOrigin;
11360 Point transformOrigin = nsStyleTransformMatrix::Convert2DPosition(
11361 origin.horizontal, origin.vertical, refBox, appUnitsPerDevPixel);
11362 matrix.ChangeBasis(Point3D(transformOrigin.x, transformOrigin.y, 0));
11364 gfx::Matrix result2d;
11365 if (!matrix.CanDraw2D(&result2d)) {
11366 // FIXME: It would be preferable to reject non-2D transforms at parse time.
11367 NS_WARNING(
11368 "-moz-window-transform does not describe a 2D transform, "
11369 "but only 2d transforms are supported");
11370 return gfx::Matrix();
11373 return result2d;
11376 void nsIFrame::DoUpdateStyleOfOwnedAnonBoxes(ServoRestyleState& aRestyleState) {
11377 // As a special case, we check for {ib}-split block frames here, rather
11378 // than have an nsInlineFrame::AppendDirectlyOwnedAnonBoxes implementation
11379 // that returns them.
11381 // (If we did handle them in AppendDirectlyOwnedAnonBoxes, we would have to
11382 // return *all* of the in-flow {ib}-split block frames, not just the first
11383 // one. For restyling, we really just need the first in flow, and the other
11384 // user of the AppendOwnedAnonBoxes API, AllChildIterator, doesn't need to
11385 // know about them at all, since these block frames never create NAC. So we
11386 // avoid any unncessary hashtable lookups for the {ib}-split frames by calling
11387 // UpdateStyleOfOwnedAnonBoxesForIBSplit directly here.)
11388 if (IsInlineFrame()) {
11389 if (HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) {
11390 static_cast<nsInlineFrame*>(this)->UpdateStyleOfOwnedAnonBoxesForIBSplit(
11391 aRestyleState);
11393 return;
11396 AutoTArray<OwnedAnonBox, 4> frames;
11397 AppendDirectlyOwnedAnonBoxes(frames);
11398 for (OwnedAnonBox& box : frames) {
11399 if (box.mUpdateStyleFn) {
11400 box.mUpdateStyleFn(this, box.mAnonBoxFrame, aRestyleState);
11401 } else {
11402 UpdateStyleOfChildAnonBox(box.mAnonBoxFrame, aRestyleState);
11407 /* virtual */
11408 void nsIFrame::AppendDirectlyOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult) {
11409 MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_OWNS_ANON_BOXES));
11410 MOZ_ASSERT(false, "Why did this get called?");
11413 void nsIFrame::DoAppendOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult) {
11414 size_t i = aResult.Length();
11415 AppendDirectlyOwnedAnonBoxes(aResult);
11417 // After appending the directly owned anonymous boxes of this frame to
11418 // aResult above, we need to check each of them to see if they own
11419 // any anonymous boxes themselves. Note that we keep progressing
11420 // through aResult, looking for additional entries in aResult from these
11421 // subsequent AppendDirectlyOwnedAnonBoxes calls. (Thus we can't
11422 // use a ranged for loop here.)
11424 while (i < aResult.Length()) {
11425 nsIFrame* f = aResult[i].mAnonBoxFrame;
11426 if (f->HasAnyStateBits(NS_FRAME_OWNS_ANON_BOXES)) {
11427 f->AppendDirectlyOwnedAnonBoxes(aResult);
11429 ++i;
11433 nsIFrame::CaretPosition::CaretPosition() : mContentOffset(0) {}
11435 nsIFrame::CaretPosition::~CaretPosition() = default;
11437 bool nsIFrame::HasCSSAnimations() {
11438 auto collection =
11439 AnimationCollection<CSSAnimation>::GetAnimationCollection(this);
11440 return collection && collection->mAnimations.Length() > 0;
11443 bool nsIFrame::HasCSSTransitions() {
11444 auto collection =
11445 AnimationCollection<CSSTransition>::GetAnimationCollection(this);
11446 return collection && collection->mAnimations.Length() > 0;
11449 void nsIFrame::AddSizeOfExcludingThisForTree(nsWindowSizes& aSizes) const {
11450 aSizes.mLayoutFramePropertiesSize +=
11451 mProperties.SizeOfExcludingThis(aSizes.mState.mMallocSizeOf);
11453 // We don't do this for Gecko because this stuff is stored in the nsPresArena
11454 // and so measured elsewhere.
11455 if (!aSizes.mState.HaveSeenPtr(mComputedStyle)) {
11456 mComputedStyle->AddSizeOfIncludingThis(aSizes,
11457 &aSizes.mLayoutComputedValuesNonDom);
11460 // And our additional styles.
11461 int32_t index = 0;
11462 while (auto* extra = GetAdditionalComputedStyle(index++)) {
11463 if (!aSizes.mState.HaveSeenPtr(extra)) {
11464 extra->AddSizeOfIncludingThis(aSizes,
11465 &aSizes.mLayoutComputedValuesNonDom);
11469 for (const auto& childList : ChildLists()) {
11470 for (const nsIFrame* f : childList.mList) {
11471 f->AddSizeOfExcludingThisForTree(aSizes);
11476 nsRect nsIFrame::GetCompositorHitTestArea(nsDisplayListBuilder* aBuilder) {
11477 nsRect area;
11479 nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetScrollableFrameFor(this);
11480 if (scrollFrame) {
11481 // If the frame is content of a scrollframe, then we need to pick up the
11482 // area corresponding to the overflow rect as well. Otherwise the parts of
11483 // the overflow that are not occupied by descendants get skipped and the
11484 // APZ code sends touch events to the content underneath instead.
11485 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1127773#c15.
11486 area = ScrollableOverflowRect();
11487 } else {
11488 area = GetRectRelativeToSelf();
11491 if (!area.IsEmpty()) {
11492 return area + aBuilder->ToReferenceFrame(this);
11495 return area;
11498 CompositorHitTestInfo nsIFrame::GetCompositorHitTestInfo(
11499 nsDisplayListBuilder* aBuilder) {
11500 CompositorHitTestInfo result = CompositorHitTestInvisibleToHit;
11502 if (aBuilder->IsInsidePointerEventsNoneDoc()) {
11503 // Somewhere up the parent document chain is a subdocument with pointer-
11504 // events:none set on it.
11505 return result;
11507 if (!GetParent()) {
11508 MOZ_ASSERT(IsViewportFrame());
11509 // Viewport frames are never event targets, other frames, like canvas
11510 // frames, are the event targets for any regions viewport frames may cover.
11511 return result;
11513 if (Style()->PointerEvents() == StylePointerEvents::None) {
11514 return result;
11516 if (!StyleVisibility()->IsVisible()) {
11517 return result;
11520 // Anything that didn't match the above conditions is visible to hit-testing.
11521 result = CompositorHitTestFlags::eVisibleToHitTest;
11522 if (SVGIntegrationUtils::UsingMaskOrClipPathForFrame(this)) {
11523 // If WebRender is enabled, simple clip-paths can be converted into WR
11524 // clips that WR knows how to hit-test against, so we don't need to mark
11525 // it as an irregular area.
11526 if (!gfxVars::UseWebRender() ||
11527 !SVGIntegrationUtils::UsingSimpleClipPathForFrame(this)) {
11528 result += CompositorHitTestFlags::eIrregularArea;
11532 if (aBuilder->IsBuildingNonLayerizedScrollbar()) {
11533 // Scrollbars may be painted into a layer below the actual layer they will
11534 // scroll, and therefore wheel events may be dispatched to the outer frame
11535 // instead of the intended scrollframe. To address this, we force a d-t-c
11536 // region on scrollbar frames that won't be placed in their own layer. See
11537 // bug 1213324 for details.
11538 result += CompositorHitTestFlags::eInactiveScrollframe;
11539 } else if (aBuilder->GetAncestorHasApzAwareEventHandler()) {
11540 result += CompositorHitTestFlags::eApzAwareListeners;
11541 } else if (IsRangeFrame()) {
11542 // Range frames handle touch events directly without having a touch listener
11543 // so we need to let APZ know that this area cares about events.
11544 result += CompositorHitTestFlags::eApzAwareListeners;
11547 if (aBuilder->IsTouchEventPrefEnabledDoc()) {
11548 // Inherit the touch-action flags from the parent, if there is one. We do
11549 // this because of how the touch-action on a frame combines the touch-action
11550 // from ancestor DOM elements. Refer to the documentation in
11551 // TouchActionHelper.cpp for details; this code is meant to be equivalent to
11552 // that code, but woven into the top-down recursive display list building
11553 // process.
11554 CompositorHitTestInfo inheritedTouchAction =
11555 aBuilder->GetCompositorHitTestInfo() & CompositorHitTestTouchActionMask;
11557 nsIFrame* touchActionFrame = this;
11558 if (nsIScrollableFrame* scrollFrame =
11559 nsLayoutUtils::GetScrollableFrameFor(this)) {
11560 ScrollStyles ss = scrollFrame->GetScrollStyles();
11561 if (ss.mVertical != StyleOverflow::Hidden ||
11562 ss.mHorizontal != StyleOverflow::Hidden) {
11563 touchActionFrame = do_QueryFrame(scrollFrame);
11564 // On scrollframes, stop inheriting the pan-x and pan-y flags; instead,
11565 // reset them back to zero to allow panning on the scrollframe unless we
11566 // encounter an element that disables it that's inside the scrollframe.
11567 // This is equivalent to the |considerPanning| variable in
11568 // TouchActionHelper.cpp, but for a top-down traversal.
11569 CompositorHitTestInfo panMask(
11570 CompositorHitTestFlags::eTouchActionPanXDisabled,
11571 CompositorHitTestFlags::eTouchActionPanYDisabled);
11572 inheritedTouchAction -= panMask;
11576 result += inheritedTouchAction;
11578 const StyleTouchAction touchAction = touchActionFrame->UsedTouchAction();
11579 // The CSS allows the syntax auto | none | [pan-x || pan-y] | manipulation
11580 // so we can eliminate some combinations of things.
11581 if (touchAction == StyleTouchAction::AUTO) {
11582 // nothing to do
11583 } else if (touchAction & StyleTouchAction::MANIPULATION) {
11584 result += CompositorHitTestFlags::eTouchActionDoubleTapZoomDisabled;
11585 } else {
11586 // This path handles the cases none | [pan-x || pan-y || pinch-zoom] so
11587 // double-tap is disabled in here.
11588 if (!(touchAction & StyleTouchAction::PINCH_ZOOM)) {
11589 result += CompositorHitTestFlags::eTouchActionPinchZoomDisabled;
11592 result += CompositorHitTestFlags::eTouchActionDoubleTapZoomDisabled;
11594 if (!(touchAction & StyleTouchAction::PAN_X)) {
11595 result += CompositorHitTestFlags::eTouchActionPanXDisabled;
11597 if (!(touchAction & StyleTouchAction::PAN_Y)) {
11598 result += CompositorHitTestFlags::eTouchActionPanYDisabled;
11600 if (touchAction & StyleTouchAction::NONE) {
11601 // all the touch-action disabling flags will already have been set above
11602 MOZ_ASSERT(result.contains(CompositorHitTestTouchActionMask));
11607 const Maybe<ScrollDirection> scrollDirection =
11608 aBuilder->GetCurrentScrollbarDirection();
11609 if (scrollDirection.isSome()) {
11610 if (GetContent()->IsXULElement(nsGkAtoms::thumb)) {
11611 const bool thumbGetsLayer = aBuilder->GetCurrentScrollbarTarget() !=
11612 layers::ScrollableLayerGuid::NULL_SCROLL_ID;
11613 if (thumbGetsLayer) {
11614 result += CompositorHitTestFlags::eScrollbarThumb;
11615 } else {
11616 result += CompositorHitTestFlags::eInactiveScrollframe;
11620 if (*scrollDirection == ScrollDirection::eVertical) {
11621 result += CompositorHitTestFlags::eScrollbarVertical;
11624 // includes the ScrollbarFrame, SliderFrame, anything else that
11625 // might be inside the xul:scrollbar
11626 result += CompositorHitTestFlags::eScrollbar;
11629 return result;
11632 // Returns true if we can guarantee there is no visible descendants.
11633 static bool HasNoVisibleDescendants(const nsIFrame* aFrame) {
11634 for (const auto& childList : aFrame->ChildLists()) {
11635 for (nsIFrame* f : childList.mList) {
11636 if (nsPlaceholderFrame::GetRealFrameFor(f)
11637 ->IsVisibleOrMayHaveVisibleDescendants()) {
11638 return false;
11642 return true;
11645 void nsIFrame::UpdateVisibleDescendantsState() {
11646 if (StyleVisibility()->IsVisible()) {
11647 // Notify invisible ancestors that a visible descendant exists now.
11648 nsIFrame* ancestor;
11649 for (ancestor = GetInFlowParent();
11650 ancestor && !ancestor->StyleVisibility()->IsVisible();
11651 ancestor = ancestor->GetInFlowParent()) {
11652 ancestor->mAllDescendantsAreInvisible = false;
11654 } else {
11655 mAllDescendantsAreInvisible = HasNoVisibleDescendants(this);
11659 nsIFrame::PhysicalAxes nsIFrame::ShouldApplyOverflowClipping(
11660 const nsStyleDisplay* aDisp) const {
11661 MOZ_ASSERT(aDisp == StyleDisplay(), "Wrong display struct");
11663 // 'contain:paint', which we handle as 'overflow:clip' here. Except for
11664 // scrollframes we don't need contain:paint to add any clipping, because
11665 // the scrollable frame will already clip overflowing content, and because
11666 // 'contain:paint' should prevent all means of escaping that clipping
11667 // (e.g. because it forms a fixed-pos containing block).
11668 if (aDisp->IsContainPaint() && !IsScrollFrame() &&
11669 IsFrameOfType(eSupportsContainLayoutAndPaint)) {
11670 return PhysicalAxes::Both;
11673 // and overflow:hidden that we should interpret as clip
11674 if (aDisp->mOverflowX == StyleOverflow::Hidden &&
11675 aDisp->mOverflowY == StyleOverflow::Hidden) {
11676 // REVIEW: these are the frame types that set up clipping.
11677 LayoutFrameType type = Type();
11678 switch (type) {
11679 case LayoutFrameType::Table:
11680 case LayoutFrameType::TableCell:
11681 case LayoutFrameType::SVGOuterSVG:
11682 case LayoutFrameType::SVGInnerSVG:
11683 case LayoutFrameType::SVGSymbol:
11684 case LayoutFrameType::SVGForeignObject:
11685 return PhysicalAxes::Both;
11686 default:
11687 if (IsFrameOfType(nsIFrame::eReplacedContainsBlock)) {
11688 if (type == mozilla::LayoutFrameType::TextInput) {
11689 // It has an anonymous scroll frame that handles any overflow.
11690 return PhysicalAxes::None;
11692 return PhysicalAxes::Both;
11697 // clip overflow:clip, except for nsListControlFrame which is
11698 // an nsHTMLScrollFrame sub-class.
11699 if (MOZ_UNLIKELY((aDisp->mOverflowX == mozilla::StyleOverflow::Clip ||
11700 aDisp->mOverflowY == mozilla::StyleOverflow::Clip) &&
11701 !IsListControlFrame())) {
11702 // FIXME: we could use GetViewportScrollStylesOverrideElement() here instead
11703 // if that worked correctly in a print context. (see bug 1654667)
11704 const auto* element = Element::FromNodeOrNull(GetContent());
11705 if (!element ||
11706 !PresContext()->ElementWouldPropagateScrollStyles(*element)) {
11707 uint8_t axes = uint8_t(PhysicalAxes::None);
11708 if (aDisp->mOverflowX == mozilla::StyleOverflow::Clip) {
11709 axes |= uint8_t(PhysicalAxes::Horizontal);
11711 if (aDisp->mOverflowY == mozilla::StyleOverflow::Clip) {
11712 axes |= uint8_t(PhysicalAxes::Vertical);
11714 return PhysicalAxes(axes);
11718 if (HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
11719 return PhysicalAxes::None;
11722 // If we're paginated and a block, and have NS_BLOCK_CLIP_PAGINATED_OVERFLOW
11723 // set, then we want to clip our overflow.
11724 bool clip = HasAnyStateBits(NS_BLOCK_CLIP_PAGINATED_OVERFLOW) &&
11725 PresContext()->IsPaginated() && IsBlockFrame();
11726 return clip ? PhysicalAxes::Both : PhysicalAxes::None;
11729 void nsIFrame::AddPaintedPresShell(mozilla::PresShell* aPresShell) {
11730 PaintedPresShellList()->AppendElement(do_GetWeakReference(aPresShell));
11733 void nsIFrame::UpdatePaintCountForPaintedPresShells() {
11734 for (nsWeakPtr& item : *PaintedPresShellList()) {
11735 if (RefPtr<mozilla::PresShell> presShell = do_QueryReferent(item)) {
11736 presShell->IncrementPaintCount();
11741 bool nsIFrame::DidPaintPresShell(mozilla::PresShell* aPresShell) {
11742 for (nsWeakPtr& item : *PaintedPresShellList()) {
11743 RefPtr<mozilla::PresShell> presShell = do_QueryReferent(item);
11744 if (presShell == aPresShell) {
11745 return true;
11748 return false;
11751 #ifdef DEBUG
11752 static void GetTagName(nsIFrame* aFrame, nsIContent* aContent, int aResultSize,
11753 char* aResult) {
11754 if (aContent) {
11755 snprintf(aResult, aResultSize, "%s@%p",
11756 nsAtomCString(aContent->NodeInfo()->NameAtom()).get(), aFrame);
11757 } else {
11758 snprintf(aResult, aResultSize, "@%p", aFrame);
11762 void nsIFrame::Trace(const char* aMethod, bool aEnter) {
11763 if (NS_FRAME_LOG_TEST(sFrameLogModule, NS_FRAME_TRACE_CALLS)) {
11764 char tagbuf[40];
11765 GetTagName(this, mContent, sizeof(tagbuf), tagbuf);
11766 printf_stderr("%s: %s %s", tagbuf, aEnter ? "enter" : "exit", aMethod);
11770 void nsIFrame::Trace(const char* aMethod, bool aEnter,
11771 const nsReflowStatus& aStatus) {
11772 if (NS_FRAME_LOG_TEST(sFrameLogModule, NS_FRAME_TRACE_CALLS)) {
11773 char tagbuf[40];
11774 GetTagName(this, mContent, sizeof(tagbuf), tagbuf);
11775 printf_stderr("%s: %s %s, status=%scomplete%s", tagbuf,
11776 aEnter ? "enter" : "exit", aMethod,
11777 aStatus.IsIncomplete() ? "not" : "",
11778 (aStatus.NextInFlowNeedsReflow()) ? "+reflow" : "");
11782 void nsIFrame::TraceMsg(const char* aFormatString, ...) {
11783 if (NS_FRAME_LOG_TEST(sFrameLogModule, NS_FRAME_TRACE_CALLS)) {
11784 // Format arguments into a buffer
11785 char argbuf[200];
11786 va_list ap;
11787 va_start(ap, aFormatString);
11788 VsprintfLiteral(argbuf, aFormatString, ap);
11789 va_end(ap);
11791 char tagbuf[40];
11792 GetTagName(this, mContent, sizeof(tagbuf), tagbuf);
11793 printf_stderr("%s: %s", tagbuf, argbuf);
11797 void nsIFrame::VerifyDirtyBitSet(const nsFrameList& aFrameList) {
11798 for (nsFrameList::Enumerator e(aFrameList); !e.AtEnd(); e.Next()) {
11799 NS_ASSERTION(e.get()->HasAnyStateBits(NS_FRAME_IS_DIRTY),
11800 "dirty bit not set");
11804 // Start Display Reflow
11805 DR_cookie::DR_cookie(nsPresContext* aPresContext, nsIFrame* aFrame,
11806 const ReflowInput& aReflowInput, ReflowOutput& aMetrics,
11807 nsReflowStatus& aStatus)
11808 : mPresContext(aPresContext),
11809 mFrame(aFrame),
11810 mReflowInput(aReflowInput),
11811 mMetrics(aMetrics),
11812 mStatus(aStatus) {
11813 MOZ_COUNT_CTOR(DR_cookie);
11814 mValue = nsIFrame::DisplayReflowEnter(aPresContext, mFrame, mReflowInput);
11817 DR_cookie::~DR_cookie() {
11818 MOZ_COUNT_DTOR(DR_cookie);
11819 nsIFrame::DisplayReflowExit(mPresContext, mFrame, mMetrics, mStatus, mValue);
11822 DR_layout_cookie::DR_layout_cookie(nsIFrame* aFrame) : mFrame(aFrame) {
11823 MOZ_COUNT_CTOR(DR_layout_cookie);
11824 mValue = nsIFrame::DisplayLayoutEnter(mFrame);
11827 DR_layout_cookie::~DR_layout_cookie() {
11828 MOZ_COUNT_DTOR(DR_layout_cookie);
11829 nsIFrame::DisplayLayoutExit(mFrame, mValue);
11832 DR_intrinsic_inline_size_cookie::DR_intrinsic_inline_size_cookie(
11833 nsIFrame* aFrame, const char* aType, nscoord& aResult)
11834 : mFrame(aFrame), mType(aType), mResult(aResult) {
11835 MOZ_COUNT_CTOR(DR_intrinsic_inline_size_cookie);
11836 mValue = nsIFrame::DisplayIntrinsicISizeEnter(mFrame, mType);
11839 DR_intrinsic_inline_size_cookie::~DR_intrinsic_inline_size_cookie() {
11840 MOZ_COUNT_DTOR(DR_intrinsic_inline_size_cookie);
11841 nsIFrame::DisplayIntrinsicISizeExit(mFrame, mType, mResult, mValue);
11844 DR_intrinsic_size_cookie::DR_intrinsic_size_cookie(nsIFrame* aFrame,
11845 const char* aType,
11846 nsSize& aResult)
11847 : mFrame(aFrame), mType(aType), mResult(aResult) {
11848 MOZ_COUNT_CTOR(DR_intrinsic_size_cookie);
11849 mValue = nsIFrame::DisplayIntrinsicSizeEnter(mFrame, mType);
11852 DR_intrinsic_size_cookie::~DR_intrinsic_size_cookie() {
11853 MOZ_COUNT_DTOR(DR_intrinsic_size_cookie);
11854 nsIFrame::DisplayIntrinsicSizeExit(mFrame, mType, mResult, mValue);
11857 DR_init_constraints_cookie::DR_init_constraints_cookie(
11858 nsIFrame* aFrame, ReflowInput* aState, nscoord aCBWidth, nscoord aCBHeight,
11859 const mozilla::Maybe<mozilla::LogicalMargin> aBorder,
11860 const mozilla::Maybe<mozilla::LogicalMargin> aPadding)
11861 : mFrame(aFrame), mState(aState) {
11862 MOZ_COUNT_CTOR(DR_init_constraints_cookie);
11863 nsMargin border;
11864 if (aBorder) {
11865 border = aBorder->GetPhysicalMargin(aFrame->GetWritingMode());
11867 nsMargin padding;
11868 if (aPadding) {
11869 padding = aPadding->GetPhysicalMargin(aFrame->GetWritingMode());
11871 mValue = ReflowInput::DisplayInitConstraintsEnter(
11872 mFrame, mState, aCBWidth, aCBHeight, aBorder ? &border : nullptr,
11873 aPadding ? &padding : nullptr);
11876 DR_init_constraints_cookie::~DR_init_constraints_cookie() {
11877 MOZ_COUNT_DTOR(DR_init_constraints_cookie);
11878 ReflowInput::DisplayInitConstraintsExit(mFrame, mState, mValue);
11881 DR_init_offsets_cookie::DR_init_offsets_cookie(
11882 nsIFrame* aFrame, SizeComputationInput* aState, nscoord aPercentBasis,
11883 WritingMode aCBWritingMode,
11884 const mozilla::Maybe<mozilla::LogicalMargin> aBorder,
11885 const mozilla::Maybe<mozilla::LogicalMargin> aPadding)
11886 : mFrame(aFrame), mState(aState) {
11887 MOZ_COUNT_CTOR(DR_init_offsets_cookie);
11888 nsMargin border;
11889 if (aBorder) {
11890 border = aBorder->GetPhysicalMargin(aFrame->GetWritingMode());
11892 nsMargin padding;
11893 if (aPadding) {
11894 padding = aPadding->GetPhysicalMargin(aFrame->GetWritingMode());
11896 mValue = SizeComputationInput::DisplayInitOffsetsEnter(
11897 mFrame, mState, aPercentBasis, aCBWritingMode,
11898 aBorder ? &border : nullptr, aPadding ? &padding : nullptr);
11901 DR_init_offsets_cookie::~DR_init_offsets_cookie() {
11902 MOZ_COUNT_DTOR(DR_init_offsets_cookie);
11903 SizeComputationInput::DisplayInitOffsetsExit(mFrame, mState, mValue);
11906 struct DR_Rule;
11908 struct DR_FrameTypeInfo {
11909 DR_FrameTypeInfo(LayoutFrameType aFrameType, const char* aFrameNameAbbrev,
11910 const char* aFrameName);
11911 ~DR_FrameTypeInfo();
11913 LayoutFrameType mType;
11914 char mNameAbbrev[16];
11915 char mName[32];
11916 nsTArray<DR_Rule*> mRules;
11918 private:
11919 DR_FrameTypeInfo& operator=(const DR_FrameTypeInfo&) = delete;
11922 struct DR_FrameTreeNode;
11923 struct DR_Rule;
11925 struct DR_State {
11926 DR_State();
11927 ~DR_State();
11928 void Init();
11929 void AddFrameTypeInfo(LayoutFrameType aFrameType,
11930 const char* aFrameNameAbbrev, const char* aFrameName);
11931 DR_FrameTypeInfo* GetFrameTypeInfo(LayoutFrameType aFrameType);
11932 DR_FrameTypeInfo* GetFrameTypeInfo(char* aFrameName);
11933 void InitFrameTypeTable();
11934 DR_FrameTreeNode* CreateTreeNode(nsIFrame* aFrame,
11935 const ReflowInput* aReflowInput);
11936 void FindMatchingRule(DR_FrameTreeNode& aNode);
11937 bool RuleMatches(DR_Rule& aRule, DR_FrameTreeNode& aNode);
11938 bool GetToken(FILE* aFile, char* aBuf, size_t aBufSize);
11939 DR_Rule* ParseRule(FILE* aFile);
11940 void ParseRulesFile();
11941 void AddRule(nsTArray<DR_Rule*>& aRules, DR_Rule& aRule);
11942 bool IsWhiteSpace(int c);
11943 bool GetNumber(char* aBuf, int32_t& aNumber);
11944 void PrettyUC(nscoord aSize, char* aBuf, int aBufSize);
11945 void PrintMargin(const char* tag, const nsMargin* aMargin);
11946 void DisplayFrameTypeInfo(nsIFrame* aFrame, int32_t aIndent);
11947 void DeleteTreeNode(DR_FrameTreeNode& aNode);
11949 bool mInited;
11950 bool mActive;
11951 int32_t mCount;
11952 int32_t mAssert;
11953 int32_t mIndent;
11954 bool mIndentUndisplayedFrames;
11955 bool mDisplayPixelErrors;
11956 nsTArray<DR_Rule*> mWildRules;
11957 nsTArray<DR_FrameTypeInfo> mFrameTypeTable;
11958 // reflow specific state
11959 nsTArray<DR_FrameTreeNode*> mFrameTreeLeaves;
11962 static DR_State* DR_state; // the one and only DR_State
11964 struct DR_RulePart {
11965 explicit DR_RulePart(LayoutFrameType aFrameType)
11966 : mFrameType(aFrameType), mNext(0) {}
11968 void Destroy();
11970 LayoutFrameType mFrameType;
11971 DR_RulePart* mNext;
11974 void DR_RulePart::Destroy() {
11975 if (mNext) {
11976 mNext->Destroy();
11978 delete this;
11981 struct DR_Rule {
11982 DR_Rule() : mLength(0), mTarget(nullptr), mDisplay(false) {
11983 MOZ_COUNT_CTOR(DR_Rule);
11985 ~DR_Rule() {
11986 if (mTarget) mTarget->Destroy();
11987 MOZ_COUNT_DTOR(DR_Rule);
11989 void AddPart(LayoutFrameType aFrameType);
11991 uint32_t mLength;
11992 DR_RulePart* mTarget;
11993 bool mDisplay;
11996 void DR_Rule::AddPart(LayoutFrameType aFrameType) {
11997 DR_RulePart* newPart = new DR_RulePart(aFrameType);
11998 newPart->mNext = mTarget;
11999 mTarget = newPart;
12000 mLength++;
12003 DR_FrameTypeInfo::~DR_FrameTypeInfo() {
12004 int32_t numElements;
12005 numElements = mRules.Length();
12006 for (int32_t i = numElements - 1; i >= 0; i--) {
12007 delete mRules.ElementAt(i);
12011 DR_FrameTypeInfo::DR_FrameTypeInfo(LayoutFrameType aFrameType,
12012 const char* aFrameNameAbbrev,
12013 const char* aFrameName) {
12014 mType = aFrameType;
12015 PL_strncpyz(mNameAbbrev, aFrameNameAbbrev, sizeof(mNameAbbrev));
12016 PL_strncpyz(mName, aFrameName, sizeof(mName));
12019 struct DR_FrameTreeNode {
12020 DR_FrameTreeNode(nsIFrame* aFrame, DR_FrameTreeNode* aParent)
12021 : mFrame(aFrame), mParent(aParent), mDisplay(0), mIndent(0) {
12022 MOZ_COUNT_CTOR(DR_FrameTreeNode);
12025 MOZ_COUNTED_DTOR(DR_FrameTreeNode)
12027 nsIFrame* mFrame;
12028 DR_FrameTreeNode* mParent;
12029 bool mDisplay;
12030 uint32_t mIndent;
12033 // DR_State implementation
12035 DR_State::DR_State()
12036 : mInited(false),
12037 mActive(false),
12038 mCount(0),
12039 mAssert(-1),
12040 mIndent(0),
12041 mIndentUndisplayedFrames(false),
12042 mDisplayPixelErrors(false) {
12043 MOZ_COUNT_CTOR(DR_State);
12046 void DR_State::Init() {
12047 char* env = PR_GetEnv("GECKO_DISPLAY_REFLOW_ASSERT");
12048 int32_t num;
12049 if (env) {
12050 if (GetNumber(env, num))
12051 mAssert = num;
12052 else
12053 printf("GECKO_DISPLAY_REFLOW_ASSERT - invalid value = %s", env);
12056 env = PR_GetEnv("GECKO_DISPLAY_REFLOW_INDENT_START");
12057 if (env) {
12058 if (GetNumber(env, num))
12059 mIndent = num;
12060 else
12061 printf("GECKO_DISPLAY_REFLOW_INDENT_START - invalid value = %s", env);
12064 env = PR_GetEnv("GECKO_DISPLAY_REFLOW_INDENT_UNDISPLAYED_FRAMES");
12065 if (env) {
12066 if (GetNumber(env, num))
12067 mIndentUndisplayedFrames = num;
12068 else
12069 printf(
12070 "GECKO_DISPLAY_REFLOW_INDENT_UNDISPLAYED_FRAMES - invalid value = %s",
12071 env);
12074 env = PR_GetEnv("GECKO_DISPLAY_REFLOW_FLAG_PIXEL_ERRORS");
12075 if (env) {
12076 if (GetNumber(env, num))
12077 mDisplayPixelErrors = num;
12078 else
12079 printf("GECKO_DISPLAY_REFLOW_FLAG_PIXEL_ERRORS - invalid value = %s",
12080 env);
12083 InitFrameTypeTable();
12084 ParseRulesFile();
12085 mInited = true;
12088 DR_State::~DR_State() {
12089 MOZ_COUNT_DTOR(DR_State);
12090 int32_t numElements, i;
12091 numElements = mWildRules.Length();
12092 for (i = numElements - 1; i >= 0; i--) {
12093 delete mWildRules.ElementAt(i);
12095 numElements = mFrameTreeLeaves.Length();
12096 for (i = numElements - 1; i >= 0; i--) {
12097 delete mFrameTreeLeaves.ElementAt(i);
12101 bool DR_State::GetNumber(char* aBuf, int32_t& aNumber) {
12102 if (sscanf(aBuf, "%d", &aNumber) > 0)
12103 return true;
12104 else
12105 return false;
12108 bool DR_State::IsWhiteSpace(int c) {
12109 return (c == ' ') || (c == '\t') || (c == '\n') || (c == '\r');
12112 bool DR_State::GetToken(FILE* aFile, char* aBuf, size_t aBufSize) {
12113 bool haveToken = false;
12114 aBuf[0] = 0;
12115 // get the 1st non whitespace char
12116 int c = -1;
12117 for (c = getc(aFile); (c > 0) && IsWhiteSpace(c); c = getc(aFile)) {
12120 if (c > 0) {
12121 haveToken = true;
12122 aBuf[0] = c;
12123 // get everything up to the next whitespace char
12124 size_t cX;
12125 for (cX = 1; cX + 1 < aBufSize; cX++) {
12126 c = getc(aFile);
12127 if (c < 0) { // EOF
12128 ungetc(' ', aFile);
12129 break;
12130 } else {
12131 if (IsWhiteSpace(c)) {
12132 break;
12133 } else {
12134 aBuf[cX] = c;
12138 aBuf[cX] = 0;
12140 return haveToken;
12143 DR_Rule* DR_State::ParseRule(FILE* aFile) {
12144 char buf[128];
12145 int32_t doDisplay;
12146 DR_Rule* rule = nullptr;
12147 while (GetToken(aFile, buf, sizeof(buf))) {
12148 if (GetNumber(buf, doDisplay)) {
12149 if (rule) {
12150 rule->mDisplay = !!doDisplay;
12151 break;
12152 } else {
12153 printf("unexpected token - %s \n", buf);
12155 } else {
12156 if (!rule) {
12157 rule = new DR_Rule;
12159 if (strcmp(buf, "*") == 0) {
12160 rule->AddPart(LayoutFrameType::None);
12161 } else {
12162 DR_FrameTypeInfo* info = GetFrameTypeInfo(buf);
12163 if (info) {
12164 rule->AddPart(info->mType);
12165 } else {
12166 printf("invalid frame type - %s \n", buf);
12171 return rule;
12174 void DR_State::AddRule(nsTArray<DR_Rule*>& aRules, DR_Rule& aRule) {
12175 int32_t numRules = aRules.Length();
12176 for (int32_t ruleX = 0; ruleX < numRules; ruleX++) {
12177 DR_Rule* rule = aRules.ElementAt(ruleX);
12178 NS_ASSERTION(rule, "program error");
12179 if (aRule.mLength > rule->mLength) {
12180 aRules.InsertElementAt(ruleX, &aRule);
12181 return;
12184 aRules.AppendElement(&aRule);
12187 static Maybe<bool> ShouldLogReflow(const char* processes) {
12188 switch (processes[0]) {
12189 case 'A':
12190 case 'a':
12191 return Some(true);
12192 case 'P':
12193 case 'p':
12194 return Some(XRE_IsParentProcess());
12195 case 'C':
12196 case 'c':
12197 return Some(XRE_IsContentProcess());
12198 default:
12199 return Nothing{};
12203 void DR_State::ParseRulesFile() {
12204 char* processes = PR_GetEnv("GECKO_DISPLAY_REFLOW_PROCESSES");
12205 if (processes) {
12206 Maybe<bool> enableLog = ShouldLogReflow(processes);
12207 if (enableLog.isNothing()) {
12208 MOZ_CRASH("GECKO_DISPLAY_REFLOW_PROCESSES: [a]ll [p]arent [c]ontent");
12209 } else if (enableLog.value()) {
12210 DR_Rule* rule = new DR_Rule;
12211 rule->AddPart(LayoutFrameType::None);
12212 rule->mDisplay = true;
12213 AddRule(mWildRules, *rule);
12214 mActive = true;
12216 return;
12219 char* path = PR_GetEnv("GECKO_DISPLAY_REFLOW_RULES_FILE");
12220 if (path) {
12221 FILE* inFile = fopen(path, "r");
12222 if (!inFile) {
12223 MOZ_CRASH(
12224 "Failed to open the specified rules file; Try `--setpref "
12225 "security.sandbox.content.level=2` if the sandbox is at cause");
12227 for (DR_Rule* rule = ParseRule(inFile); rule; rule = ParseRule(inFile)) {
12228 if (rule->mTarget) {
12229 LayoutFrameType fType = rule->mTarget->mFrameType;
12230 if (fType != LayoutFrameType::None) {
12231 DR_FrameTypeInfo* info = GetFrameTypeInfo(fType);
12232 AddRule(info->mRules, *rule);
12233 } else {
12234 AddRule(mWildRules, *rule);
12236 mActive = true;
12240 fclose(inFile);
12244 void DR_State::AddFrameTypeInfo(LayoutFrameType aFrameType,
12245 const char* aFrameNameAbbrev,
12246 const char* aFrameName) {
12247 mFrameTypeTable.EmplaceBack(aFrameType, aFrameNameAbbrev, aFrameName);
12250 DR_FrameTypeInfo* DR_State::GetFrameTypeInfo(LayoutFrameType aFrameType) {
12251 int32_t numEntries = mFrameTypeTable.Length();
12252 NS_ASSERTION(numEntries != 0, "empty FrameTypeTable");
12253 for (int32_t i = 0; i < numEntries; i++) {
12254 DR_FrameTypeInfo& info = mFrameTypeTable.ElementAt(i);
12255 if (info.mType == aFrameType) {
12256 return &info;
12259 return &mFrameTypeTable.ElementAt(numEntries -
12260 1); // return unknown frame type
12263 DR_FrameTypeInfo* DR_State::GetFrameTypeInfo(char* aFrameName) {
12264 int32_t numEntries = mFrameTypeTable.Length();
12265 NS_ASSERTION(numEntries != 0, "empty FrameTypeTable");
12266 for (int32_t i = 0; i < numEntries; i++) {
12267 DR_FrameTypeInfo& info = mFrameTypeTable.ElementAt(i);
12268 if ((strcmp(aFrameName, info.mName) == 0) ||
12269 (strcmp(aFrameName, info.mNameAbbrev) == 0)) {
12270 return &info;
12273 return &mFrameTypeTable.ElementAt(numEntries -
12274 1); // return unknown frame type
12277 void DR_State::InitFrameTypeTable() {
12278 AddFrameTypeInfo(LayoutFrameType::Block, "block", "block");
12279 AddFrameTypeInfo(LayoutFrameType::Br, "br", "br");
12280 AddFrameTypeInfo(LayoutFrameType::ColorControl, "color", "colorControl");
12281 AddFrameTypeInfo(LayoutFrameType::GfxButtonControl, "button",
12282 "gfxButtonControl");
12283 AddFrameTypeInfo(LayoutFrameType::HTMLButtonControl, "HTMLbutton",
12284 "HTMLButtonControl");
12285 AddFrameTypeInfo(LayoutFrameType::HTMLCanvas, "HTMLCanvas", "HTMLCanvas");
12286 AddFrameTypeInfo(LayoutFrameType::SubDocument, "subdoc", "subDocument");
12287 AddFrameTypeInfo(LayoutFrameType::Image, "img", "image");
12288 AddFrameTypeInfo(LayoutFrameType::Inline, "inline", "inline");
12289 AddFrameTypeInfo(LayoutFrameType::Letter, "letter", "letter");
12290 AddFrameTypeInfo(LayoutFrameType::Line, "line", "line");
12291 AddFrameTypeInfo(LayoutFrameType::ListControl, "select", "select");
12292 AddFrameTypeInfo(LayoutFrameType::Page, "page", "page");
12293 AddFrameTypeInfo(LayoutFrameType::Placeholder, "place", "placeholder");
12294 AddFrameTypeInfo(LayoutFrameType::Canvas, "canvas", "canvas");
12295 AddFrameTypeInfo(LayoutFrameType::XULRoot, "xulroot", "xulroot");
12296 AddFrameTypeInfo(LayoutFrameType::Scroll, "scroll", "scroll");
12297 AddFrameTypeInfo(LayoutFrameType::TableCell, "cell", "tableCell");
12298 AddFrameTypeInfo(LayoutFrameType::TableCol, "col", "tableCol");
12299 AddFrameTypeInfo(LayoutFrameType::TableColGroup, "colG", "tableColGroup");
12300 AddFrameTypeInfo(LayoutFrameType::Table, "tbl", "table");
12301 AddFrameTypeInfo(LayoutFrameType::TableWrapper, "tblW", "tableWrapper");
12302 AddFrameTypeInfo(LayoutFrameType::TableRowGroup, "rowG", "tableRowGroup");
12303 AddFrameTypeInfo(LayoutFrameType::TableRow, "row", "tableRow");
12304 AddFrameTypeInfo(LayoutFrameType::TextInput, "textCtl", "textInput");
12305 AddFrameTypeInfo(LayoutFrameType::Text, "text", "text");
12306 AddFrameTypeInfo(LayoutFrameType::Viewport, "VP", "viewport");
12307 AddFrameTypeInfo(LayoutFrameType::Box, "Box", "Box");
12308 AddFrameTypeInfo(LayoutFrameType::Slider, "Slider", "Slider");
12309 AddFrameTypeInfo(LayoutFrameType::PopupSet, "PopupSet", "PopupSet");
12310 AddFrameTypeInfo(LayoutFrameType::None, "unknown", "unknown");
12313 void DR_State::DisplayFrameTypeInfo(nsIFrame* aFrame, int32_t aIndent) {
12314 DR_FrameTypeInfo* frameTypeInfo = GetFrameTypeInfo(aFrame->Type());
12315 if (frameTypeInfo) {
12316 for (int32_t i = 0; i < aIndent; i++) {
12317 printf(" ");
12319 if (!strcmp(frameTypeInfo->mNameAbbrev, "unknown")) {
12320 if (aFrame) {
12321 nsAutoString name;
12322 aFrame->GetFrameName(name);
12323 printf("%s %p ", NS_LossyConvertUTF16toASCII(name).get(),
12324 (void*)aFrame);
12325 } else {
12326 printf("%s %p ", frameTypeInfo->mNameAbbrev, (void*)aFrame);
12328 } else {
12329 printf("%s %p ", frameTypeInfo->mNameAbbrev, (void*)aFrame);
12334 bool DR_State::RuleMatches(DR_Rule& aRule, DR_FrameTreeNode& aNode) {
12335 NS_ASSERTION(aRule.mTarget, "program error");
12337 DR_RulePart* rulePart;
12338 DR_FrameTreeNode* parentNode;
12339 for (rulePart = aRule.mTarget->mNext, parentNode = aNode.mParent;
12340 rulePart && parentNode;
12341 rulePart = rulePart->mNext, parentNode = parentNode->mParent) {
12342 if (rulePart->mFrameType != LayoutFrameType::None) {
12343 if (parentNode->mFrame) {
12344 if (rulePart->mFrameType != parentNode->mFrame->Type()) {
12345 return false;
12347 } else
12348 NS_ASSERTION(false, "program error");
12350 // else wild card match
12352 return true;
12355 void DR_State::FindMatchingRule(DR_FrameTreeNode& aNode) {
12356 if (!aNode.mFrame) {
12357 NS_ASSERTION(false, "invalid DR_FrameTreeNode \n");
12358 return;
12361 bool matchingRule = false;
12363 DR_FrameTypeInfo* info = GetFrameTypeInfo(aNode.mFrame->Type());
12364 NS_ASSERTION(info, "program error");
12365 int32_t numRules = info->mRules.Length();
12366 for (int32_t ruleX = 0; ruleX < numRules; ruleX++) {
12367 DR_Rule* rule = info->mRules.ElementAt(ruleX);
12368 if (rule && RuleMatches(*rule, aNode)) {
12369 aNode.mDisplay = rule->mDisplay;
12370 matchingRule = true;
12371 break;
12374 if (!matchingRule) {
12375 int32_t numWildRules = mWildRules.Length();
12376 for (int32_t ruleX = 0; ruleX < numWildRules; ruleX++) {
12377 DR_Rule* rule = mWildRules.ElementAt(ruleX);
12378 if (rule && RuleMatches(*rule, aNode)) {
12379 aNode.mDisplay = rule->mDisplay;
12380 break;
12386 DR_FrameTreeNode* DR_State::CreateTreeNode(nsIFrame* aFrame,
12387 const ReflowInput* aReflowInput) {
12388 // find the frame of the parent reflow input (usually just the parent of
12389 // aFrame)
12390 nsIFrame* parentFrame;
12391 if (aReflowInput) {
12392 const ReflowInput* parentRI = aReflowInput->mParentReflowInput;
12393 parentFrame = (parentRI) ? parentRI->mFrame : nullptr;
12394 } else {
12395 parentFrame = aFrame->GetParent();
12398 // find the parent tree node leaf
12399 DR_FrameTreeNode* parentNode = nullptr;
12401 DR_FrameTreeNode* lastLeaf = nullptr;
12402 if (mFrameTreeLeaves.Length())
12403 lastLeaf = mFrameTreeLeaves.ElementAt(mFrameTreeLeaves.Length() - 1);
12404 if (lastLeaf) {
12405 for (parentNode = lastLeaf;
12406 parentNode && (parentNode->mFrame != parentFrame);
12407 parentNode = parentNode->mParent) {
12410 DR_FrameTreeNode* newNode = new DR_FrameTreeNode(aFrame, parentNode);
12411 FindMatchingRule(*newNode);
12413 newNode->mIndent = mIndent;
12414 if (newNode->mDisplay || mIndentUndisplayedFrames) {
12415 ++mIndent;
12418 if (lastLeaf && (lastLeaf == parentNode)) {
12419 mFrameTreeLeaves.RemoveLastElement();
12421 mFrameTreeLeaves.AppendElement(newNode);
12422 mCount++;
12424 return newNode;
12427 void DR_State::PrettyUC(nscoord aSize, char* aBuf, int aBufSize) {
12428 if (NS_UNCONSTRAINEDSIZE == aSize) {
12429 strcpy(aBuf, "UC");
12430 } else {
12431 if ((nscoord)0xdeadbeefU == aSize) {
12432 strcpy(aBuf, "deadbeef");
12433 } else {
12434 snprintf(aBuf, aBufSize, "%d", aSize);
12439 void DR_State::PrintMargin(const char* tag, const nsMargin* aMargin) {
12440 if (aMargin) {
12441 char t[16], r[16], b[16], l[16];
12442 PrettyUC(aMargin->top, t, 16);
12443 PrettyUC(aMargin->right, r, 16);
12444 PrettyUC(aMargin->bottom, b, 16);
12445 PrettyUC(aMargin->left, l, 16);
12446 printf(" %s=%s,%s,%s,%s", tag, t, r, b, l);
12447 } else {
12448 // use %p here for consistency with other null-pointer printouts
12449 printf(" %s=%p", tag, (void*)aMargin);
12453 void DR_State::DeleteTreeNode(DR_FrameTreeNode& aNode) {
12454 mFrameTreeLeaves.RemoveElement(&aNode);
12455 int32_t numLeaves = mFrameTreeLeaves.Length();
12456 if ((0 == numLeaves) ||
12457 (aNode.mParent != mFrameTreeLeaves.ElementAt(numLeaves - 1))) {
12458 mFrameTreeLeaves.AppendElement(aNode.mParent);
12461 if (aNode.mDisplay || mIndentUndisplayedFrames) {
12462 --mIndent;
12464 // delete the tree node
12465 delete &aNode;
12468 static void CheckPixelError(nscoord aSize, int32_t aPixelToTwips) {
12469 if (NS_UNCONSTRAINEDSIZE != aSize) {
12470 if ((aSize % aPixelToTwips) > 0) {
12471 printf("VALUE %d is not a whole pixel \n", aSize);
12476 static void DisplayReflowEnterPrint(nsPresContext* aPresContext,
12477 nsIFrame* aFrame,
12478 const ReflowInput& aReflowInput,
12479 DR_FrameTreeNode& aTreeNode,
12480 bool aChanged) {
12481 if (aTreeNode.mDisplay) {
12482 DR_state->DisplayFrameTypeInfo(aFrame, aTreeNode.mIndent);
12484 char width[16];
12485 char height[16];
12487 DR_state->PrettyUC(aReflowInput.AvailableWidth(), width, 16);
12488 DR_state->PrettyUC(aReflowInput.AvailableHeight(), height, 16);
12489 printf("Reflow a=%s,%s ", width, height);
12491 DR_state->PrettyUC(aReflowInput.ComputedWidth(), width, 16);
12492 DR_state->PrettyUC(aReflowInput.ComputedHeight(), height, 16);
12493 printf("c=%s,%s ", width, height);
12495 if (aFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY)) printf("dirty ");
12497 if (aFrame->HasAnyStateBits(NS_FRAME_HAS_DIRTY_CHILDREN))
12498 printf("dirty-children ");
12500 if (aReflowInput.mFlags.mSpecialBSizeReflow) printf("special-bsize ");
12502 if (aReflowInput.IsHResize()) printf("h-resize ");
12504 if (aReflowInput.IsVResize()) printf("v-resize ");
12506 nsIFrame* inFlow = aFrame->GetPrevInFlow();
12507 if (inFlow) {
12508 printf("pif=%p ", (void*)inFlow);
12510 inFlow = aFrame->GetNextInFlow();
12511 if (inFlow) {
12512 printf("nif=%p ", (void*)inFlow);
12514 if (aChanged)
12515 printf("CHANGED \n");
12516 else
12517 printf("cnt=%d \n", DR_state->mCount);
12518 if (DR_state->mDisplayPixelErrors) {
12519 int32_t d2a = aPresContext->AppUnitsPerDevPixel();
12520 CheckPixelError(aReflowInput.AvailableWidth(), d2a);
12521 CheckPixelError(aReflowInput.AvailableHeight(), d2a);
12522 CheckPixelError(aReflowInput.ComputedWidth(), d2a);
12523 CheckPixelError(aReflowInput.ComputedHeight(), d2a);
12528 void* nsIFrame::DisplayReflowEnter(nsPresContext* aPresContext,
12529 nsIFrame* aFrame,
12530 const ReflowInput& aReflowInput) {
12531 if (!DR_state->mInited) DR_state->Init();
12532 if (!DR_state->mActive) return nullptr;
12534 NS_ASSERTION(aFrame, "invalid call");
12536 DR_FrameTreeNode* treeNode = DR_state->CreateTreeNode(aFrame, &aReflowInput);
12537 if (treeNode) {
12538 DisplayReflowEnterPrint(aPresContext, aFrame, aReflowInput, *treeNode,
12539 false);
12541 return treeNode;
12544 void* nsIFrame::DisplayLayoutEnter(nsIFrame* aFrame) {
12545 if (!DR_state->mInited) DR_state->Init();
12546 if (!DR_state->mActive) return nullptr;
12548 NS_ASSERTION(aFrame, "invalid call");
12550 DR_FrameTreeNode* treeNode = DR_state->CreateTreeNode(aFrame, nullptr);
12551 if (treeNode && treeNode->mDisplay) {
12552 DR_state->DisplayFrameTypeInfo(aFrame, treeNode->mIndent);
12553 printf("XULLayout\n");
12555 return treeNode;
12558 void* nsIFrame::DisplayIntrinsicISizeEnter(nsIFrame* aFrame,
12559 const char* aType) {
12560 if (!DR_state->mInited) DR_state->Init();
12561 if (!DR_state->mActive) return nullptr;
12563 NS_ASSERTION(aFrame, "invalid call");
12565 DR_FrameTreeNode* treeNode = DR_state->CreateTreeNode(aFrame, nullptr);
12566 if (treeNode && treeNode->mDisplay) {
12567 DR_state->DisplayFrameTypeInfo(aFrame, treeNode->mIndent);
12568 printf("Get%sISize\n", aType);
12570 return treeNode;
12573 void* nsIFrame::DisplayIntrinsicSizeEnter(nsIFrame* aFrame, const char* aType) {
12574 if (!DR_state->mInited) DR_state->Init();
12575 if (!DR_state->mActive) return nullptr;
12577 NS_ASSERTION(aFrame, "invalid call");
12579 DR_FrameTreeNode* treeNode = DR_state->CreateTreeNode(aFrame, nullptr);
12580 if (treeNode && treeNode->mDisplay) {
12581 DR_state->DisplayFrameTypeInfo(aFrame, treeNode->mIndent);
12582 printf("Get%sSize\n", aType);
12584 return treeNode;
12587 void nsIFrame::DisplayReflowExit(nsPresContext* aPresContext, nsIFrame* aFrame,
12588 ReflowOutput& aMetrics,
12589 const nsReflowStatus& aStatus,
12590 void* aFrameTreeNode) {
12591 if (!DR_state->mActive) return;
12593 NS_ASSERTION(aFrame, "DisplayReflowExit - invalid call");
12594 if (!aFrameTreeNode) return;
12596 DR_FrameTreeNode* treeNode = (DR_FrameTreeNode*)aFrameTreeNode;
12597 if (treeNode->mDisplay) {
12598 DR_state->DisplayFrameTypeInfo(aFrame, treeNode->mIndent);
12600 char width[16];
12601 char height[16];
12602 char x[16];
12603 char y[16];
12604 DR_state->PrettyUC(aMetrics.Width(), width, 16);
12605 DR_state->PrettyUC(aMetrics.Height(), height, 16);
12606 printf("Reflow d=%s,%s", width, height);
12608 if (!aStatus.IsEmpty()) {
12609 printf(" status=%s", ToString(aStatus).c_str());
12611 if (aFrame->HasOverflowAreas()) {
12612 DR_state->PrettyUC(aMetrics.InkOverflow().x, x, 16);
12613 DR_state->PrettyUC(aMetrics.InkOverflow().y, y, 16);
12614 DR_state->PrettyUC(aMetrics.InkOverflow().width, width, 16);
12615 DR_state->PrettyUC(aMetrics.InkOverflow().height, height, 16);
12616 printf(" vis-o=(%s,%s) %s x %s", x, y, width, height);
12618 nsRect storedOverflow = aFrame->InkOverflowRect();
12619 DR_state->PrettyUC(storedOverflow.x, x, 16);
12620 DR_state->PrettyUC(storedOverflow.y, y, 16);
12621 DR_state->PrettyUC(storedOverflow.width, width, 16);
12622 DR_state->PrettyUC(storedOverflow.height, height, 16);
12623 printf(" vis-sto=(%s,%s) %s x %s", x, y, width, height);
12625 DR_state->PrettyUC(aMetrics.ScrollableOverflow().x, x, 16);
12626 DR_state->PrettyUC(aMetrics.ScrollableOverflow().y, y, 16);
12627 DR_state->PrettyUC(aMetrics.ScrollableOverflow().width, width, 16);
12628 DR_state->PrettyUC(aMetrics.ScrollableOverflow().height, height, 16);
12629 printf(" scr-o=(%s,%s) %s x %s", x, y, width, height);
12631 storedOverflow = aFrame->ScrollableOverflowRect();
12632 DR_state->PrettyUC(storedOverflow.x, x, 16);
12633 DR_state->PrettyUC(storedOverflow.y, y, 16);
12634 DR_state->PrettyUC(storedOverflow.width, width, 16);
12635 DR_state->PrettyUC(storedOverflow.height, height, 16);
12636 printf(" scr-sto=(%s,%s) %s x %s", x, y, width, height);
12638 printf("\n");
12639 if (DR_state->mDisplayPixelErrors) {
12640 int32_t d2a = aPresContext->AppUnitsPerDevPixel();
12641 CheckPixelError(aMetrics.Width(), d2a);
12642 CheckPixelError(aMetrics.Height(), d2a);
12645 DR_state->DeleteTreeNode(*treeNode);
12648 void nsIFrame::DisplayLayoutExit(nsIFrame* aFrame, void* aFrameTreeNode) {
12649 if (!DR_state->mActive) return;
12651 NS_ASSERTION(aFrame, "non-null frame required");
12652 if (!aFrameTreeNode) return;
12654 DR_FrameTreeNode* treeNode = (DR_FrameTreeNode*)aFrameTreeNode;
12655 if (treeNode->mDisplay) {
12656 DR_state->DisplayFrameTypeInfo(aFrame, treeNode->mIndent);
12657 nsRect rect = aFrame->GetRect();
12658 printf("XULLayout=%d,%d,%d,%d\n", rect.x, rect.y, rect.width, rect.height);
12660 DR_state->DeleteTreeNode(*treeNode);
12663 void nsIFrame::DisplayIntrinsicISizeExit(nsIFrame* aFrame, const char* aType,
12664 nscoord aResult,
12665 void* aFrameTreeNode) {
12666 if (!DR_state->mActive) return;
12668 NS_ASSERTION(aFrame, "non-null frame required");
12669 if (!aFrameTreeNode) return;
12671 DR_FrameTreeNode* treeNode = (DR_FrameTreeNode*)aFrameTreeNode;
12672 if (treeNode->mDisplay) {
12673 DR_state->DisplayFrameTypeInfo(aFrame, treeNode->mIndent);
12674 char iSize[16];
12675 DR_state->PrettyUC(aResult, iSize, 16);
12676 printf("Get%sISize=%s\n", aType, iSize);
12678 DR_state->DeleteTreeNode(*treeNode);
12681 void nsIFrame::DisplayIntrinsicSizeExit(nsIFrame* aFrame, const char* aType,
12682 nsSize aResult, void* aFrameTreeNode) {
12683 if (!DR_state->mActive) return;
12685 NS_ASSERTION(aFrame, "non-null frame required");
12686 if (!aFrameTreeNode) return;
12688 DR_FrameTreeNode* treeNode = (DR_FrameTreeNode*)aFrameTreeNode;
12689 if (treeNode->mDisplay) {
12690 DR_state->DisplayFrameTypeInfo(aFrame, treeNode->mIndent);
12692 char width[16];
12693 char height[16];
12694 DR_state->PrettyUC(aResult.width, width, 16);
12695 DR_state->PrettyUC(aResult.height, height, 16);
12696 printf("Get%sSize=%s,%s\n", aType, width, height);
12698 DR_state->DeleteTreeNode(*treeNode);
12701 /* static */
12702 void nsIFrame::DisplayReflowStartup() { DR_state = new DR_State(); }
12704 /* static */
12705 void nsIFrame::DisplayReflowShutdown() {
12706 delete DR_state;
12707 DR_state = nullptr;
12710 void DR_cookie::Change() const {
12711 DR_FrameTreeNode* treeNode = (DR_FrameTreeNode*)mValue;
12712 if (treeNode && treeNode->mDisplay) {
12713 DisplayReflowEnterPrint(mPresContext, mFrame, mReflowInput, *treeNode,
12714 true);
12718 /* static */
12719 void* ReflowInput::DisplayInitConstraintsEnter(nsIFrame* aFrame,
12720 ReflowInput* aState,
12721 nscoord aContainingBlockWidth,
12722 nscoord aContainingBlockHeight,
12723 const nsMargin* aBorder,
12724 const nsMargin* aPadding) {
12725 MOZ_ASSERT(aFrame, "non-null frame required");
12726 MOZ_ASSERT(aState, "non-null state required");
12728 if (!DR_state->mInited) DR_state->Init();
12729 if (!DR_state->mActive) return nullptr;
12731 DR_FrameTreeNode* treeNode = DR_state->CreateTreeNode(aFrame, aState);
12732 if (treeNode && treeNode->mDisplay) {
12733 DR_state->DisplayFrameTypeInfo(aFrame, treeNode->mIndent);
12735 printf("InitConstraints parent=%p", (void*)aState->mParentReflowInput);
12737 char width[16];
12738 char height[16];
12740 DR_state->PrettyUC(aContainingBlockWidth, width, 16);
12741 DR_state->PrettyUC(aContainingBlockHeight, height, 16);
12742 printf(" cb=%s,%s", width, height);
12744 DR_state->PrettyUC(aState->AvailableWidth(), width, 16);
12745 DR_state->PrettyUC(aState->AvailableHeight(), height, 16);
12746 printf(" as=%s,%s", width, height);
12748 DR_state->PrintMargin("b", aBorder);
12749 DR_state->PrintMargin("p", aPadding);
12750 putchar('\n');
12752 return treeNode;
12755 /* static */
12756 void ReflowInput::DisplayInitConstraintsExit(nsIFrame* aFrame,
12757 ReflowInput* aState,
12758 void* aValue) {
12759 MOZ_ASSERT(aFrame, "non-null frame required");
12760 MOZ_ASSERT(aState, "non-null state required");
12762 if (!DR_state->mActive) return;
12763 if (!aValue) return;
12765 DR_FrameTreeNode* treeNode = (DR_FrameTreeNode*)aValue;
12766 if (treeNode->mDisplay) {
12767 DR_state->DisplayFrameTypeInfo(aFrame, treeNode->mIndent);
12768 char cmiw[16], cw[16], cmxw[16], cmih[16], ch[16], cmxh[16];
12769 DR_state->PrettyUC(aState->ComputedMinWidth(), cmiw, 16);
12770 DR_state->PrettyUC(aState->ComputedWidth(), cw, 16);
12771 DR_state->PrettyUC(aState->ComputedMaxWidth(), cmxw, 16);
12772 DR_state->PrettyUC(aState->ComputedMinHeight(), cmih, 16);
12773 DR_state->PrettyUC(aState->ComputedHeight(), ch, 16);
12774 DR_state->PrettyUC(aState->ComputedMaxHeight(), cmxh, 16);
12775 printf("InitConstraints= cw=(%s <= %s <= %s) ch=(%s <= %s <= %s)", cmiw, cw,
12776 cmxw, cmih, ch, cmxh);
12777 const nsMargin m = aState->ComputedPhysicalOffsets();
12778 DR_state->PrintMargin("co", &m);
12779 putchar('\n');
12781 DR_state->DeleteTreeNode(*treeNode);
12784 /* static */
12785 void* SizeComputationInput::DisplayInitOffsetsEnter(
12786 nsIFrame* aFrame, SizeComputationInput* aState, nscoord aPercentBasis,
12787 WritingMode aCBWritingMode, const nsMargin* aBorder,
12788 const nsMargin* aPadding) {
12789 MOZ_ASSERT(aFrame, "non-null frame required");
12790 MOZ_ASSERT(aState, "non-null state required");
12792 if (!DR_state->mInited) DR_state->Init();
12793 if (!DR_state->mActive) return nullptr;
12795 // aState is not necessarily a ReflowInput
12796 DR_FrameTreeNode* treeNode = DR_state->CreateTreeNode(aFrame, nullptr);
12797 if (treeNode && treeNode->mDisplay) {
12798 DR_state->DisplayFrameTypeInfo(aFrame, treeNode->mIndent);
12800 char pctBasisStr[16];
12801 DR_state->PrettyUC(aPercentBasis, pctBasisStr, 16);
12802 printf("InitOffsets pct_basis=%s", pctBasisStr);
12804 DR_state->PrintMargin("b", aBorder);
12805 DR_state->PrintMargin("p", aPadding);
12806 putchar('\n');
12808 return treeNode;
12811 /* static */
12812 void SizeComputationInput::DisplayInitOffsetsExit(nsIFrame* aFrame,
12813 SizeComputationInput* aState,
12814 void* aValue) {
12815 MOZ_ASSERT(aFrame, "non-null frame required");
12816 MOZ_ASSERT(aState, "non-null state required");
12818 if (!DR_state->mActive) return;
12819 if (!aValue) return;
12821 DR_FrameTreeNode* treeNode = (DR_FrameTreeNode*)aValue;
12822 if (treeNode->mDisplay) {
12823 DR_state->DisplayFrameTypeInfo(aFrame, treeNode->mIndent);
12824 printf("InitOffsets=");
12825 const auto m = aState->ComputedPhysicalMargin();
12826 DR_state->PrintMargin("m", &m);
12827 const auto p = aState->ComputedPhysicalPadding();
12828 DR_state->PrintMargin("p", &p);
12829 const auto bp = aState->ComputedPhysicalBorderPadding();
12830 DR_state->PrintMargin("b+p", &bp);
12831 putchar('\n');
12833 DR_state->DeleteTreeNode(*treeNode);
12836 // End Display Reflow
12838 // Validation of SideIsVertical.
12839 # define CASE(side, result) \
12840 static_assert(SideIsVertical(side) == result, "SideIsVertical is wrong")
12841 CASE(eSideTop, false);
12842 CASE(eSideRight, true);
12843 CASE(eSideBottom, false);
12844 CASE(eSideLeft, true);
12845 # undef CASE
12847 // Validation of HalfCornerIsX.
12848 # define CASE(corner, result) \
12849 static_assert(HalfCornerIsX(corner) == result, "HalfCornerIsX is wrong")
12850 CASE(eCornerTopLeftX, true);
12851 CASE(eCornerTopLeftY, false);
12852 CASE(eCornerTopRightX, true);
12853 CASE(eCornerTopRightY, false);
12854 CASE(eCornerBottomRightX, true);
12855 CASE(eCornerBottomRightY, false);
12856 CASE(eCornerBottomLeftX, true);
12857 CASE(eCornerBottomLeftY, false);
12858 # undef CASE
12860 // Validation of HalfToFullCorner.
12861 # define CASE(corner, result) \
12862 static_assert(HalfToFullCorner(corner) == result, \
12863 "HalfToFullCorner is " \
12864 "wrong")
12865 CASE(eCornerTopLeftX, eCornerTopLeft);
12866 CASE(eCornerTopLeftY, eCornerTopLeft);
12867 CASE(eCornerTopRightX, eCornerTopRight);
12868 CASE(eCornerTopRightY, eCornerTopRight);
12869 CASE(eCornerBottomRightX, eCornerBottomRight);
12870 CASE(eCornerBottomRightY, eCornerBottomRight);
12871 CASE(eCornerBottomLeftX, eCornerBottomLeft);
12872 CASE(eCornerBottomLeftY, eCornerBottomLeft);
12873 # undef CASE
12875 // Validation of FullToHalfCorner.
12876 # define CASE(corner, vert, result) \
12877 static_assert(FullToHalfCorner(corner, vert) == result, \
12878 "FullToHalfCorner is wrong")
12879 CASE(eCornerTopLeft, false, eCornerTopLeftX);
12880 CASE(eCornerTopLeft, true, eCornerTopLeftY);
12881 CASE(eCornerTopRight, false, eCornerTopRightX);
12882 CASE(eCornerTopRight, true, eCornerTopRightY);
12883 CASE(eCornerBottomRight, false, eCornerBottomRightX);
12884 CASE(eCornerBottomRight, true, eCornerBottomRightY);
12885 CASE(eCornerBottomLeft, false, eCornerBottomLeftX);
12886 CASE(eCornerBottomLeft, true, eCornerBottomLeftY);
12887 # undef CASE
12889 // Validation of SideToFullCorner.
12890 # define CASE(side, second, result) \
12891 static_assert(SideToFullCorner(side, second) == result, \
12892 "SideToFullCorner is wrong")
12893 CASE(eSideTop, false, eCornerTopLeft);
12894 CASE(eSideTop, true, eCornerTopRight);
12896 CASE(eSideRight, false, eCornerTopRight);
12897 CASE(eSideRight, true, eCornerBottomRight);
12899 CASE(eSideBottom, false, eCornerBottomRight);
12900 CASE(eSideBottom, true, eCornerBottomLeft);
12902 CASE(eSideLeft, false, eCornerBottomLeft);
12903 CASE(eSideLeft, true, eCornerTopLeft);
12904 # undef CASE
12906 // Validation of SideToHalfCorner.
12907 # define CASE(side, second, parallel, result) \
12908 static_assert(SideToHalfCorner(side, second, parallel) == result, \
12909 "SideToHalfCorner is wrong")
12910 CASE(eSideTop, false, true, eCornerTopLeftX);
12911 CASE(eSideTop, false, false, eCornerTopLeftY);
12912 CASE(eSideTop, true, true, eCornerTopRightX);
12913 CASE(eSideTop, true, false, eCornerTopRightY);
12915 CASE(eSideRight, false, false, eCornerTopRightX);
12916 CASE(eSideRight, false, true, eCornerTopRightY);
12917 CASE(eSideRight, true, false, eCornerBottomRightX);
12918 CASE(eSideRight, true, true, eCornerBottomRightY);
12920 CASE(eSideBottom, false, true, eCornerBottomRightX);
12921 CASE(eSideBottom, false, false, eCornerBottomRightY);
12922 CASE(eSideBottom, true, true, eCornerBottomLeftX);
12923 CASE(eSideBottom, true, false, eCornerBottomLeftY);
12925 CASE(eSideLeft, false, false, eCornerBottomLeftX);
12926 CASE(eSideLeft, false, true, eCornerBottomLeftY);
12927 CASE(eSideLeft, true, false, eCornerTopLeftX);
12928 CASE(eSideLeft, true, true, eCornerTopLeftY);
12929 # undef CASE
12931 #endif