Bug 1674658: part 2) Don't peform the default action for `mousemove` events when...
[gecko.git] / layout / generic / nsIFrame.cpp
blob11cf4c3a95b03108bd5ad8cfcc7b44f0287e89bb
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/ElementInlines.h"
21 #include "mozilla/dom/ImageTracker.h"
22 #include "mozilla/dom/Selection.h"
23 #include "mozilla/gfx/2D.h"
24 #include "mozilla/gfx/gfxVars.h"
25 #include "mozilla/gfx/PathHelpers.h"
26 #include "mozilla/PresShell.h"
27 #include "mozilla/PresShellInlines.h"
28 #include "mozilla/ResultExtensions.h"
29 #include "mozilla/Sprintf.h"
30 #include "mozilla/StaticAnalysisFunctions.h"
31 #include "mozilla/StaticPrefs_layout.h"
32 #include "mozilla/SVGMaskFrame.h"
33 #include "mozilla/SVGObserverUtils.h"
34 #include "mozilla/SVGTextFrame.h"
35 #include "mozilla/SVGIntegrationUtils.h"
36 #include "mozilla/SVGUtils.h"
37 #include "mozilla/ToString.h"
38 #include "mozilla/ViewportUtils.h"
40 #include "nsCOMPtr.h"
41 #include "nsFieldSetFrame.h"
42 #include "nsFlexContainerFrame.h"
43 #include "nsFrameList.h"
44 #include "nsPlaceholderFrame.h"
45 #include "nsIBaseWindow.h"
46 #include "nsIContent.h"
47 #include "nsIContentInlines.h"
48 #include "nsContentUtils.h"
49 #include "nsCSSFrameConstructor.h"
50 #include "nsCSSProps.h"
51 #include "nsCSSPseudoElements.h"
52 #include "nsCSSRendering.h"
53 #include "nsAtom.h"
54 #include "nsString.h"
55 #include "nsReadableUtils.h"
56 #include "nsTableWrapperFrame.h"
57 #include "nsView.h"
58 #include "nsViewManager.h"
59 #include "nsIScrollableFrame.h"
60 #include "nsPresContext.h"
61 #include "nsPresContextInlines.h"
62 #include "nsStyleConsts.h"
63 #include "mozilla/Logging.h"
64 #include "nsLayoutUtils.h"
65 #include "LayoutLogging.h"
66 #include "mozilla/RestyleManager.h"
67 #include "nsImageFrame.h"
68 #include "nsInlineFrame.h"
69 #include "nsFrameSelection.h"
70 #include "nsGkAtoms.h"
71 #include "nsCSSAnonBoxes.h"
72 #include "nsCanvasFrame.h"
74 #include "nsFieldSetFrame.h"
75 #include "nsFrameTraversal.h"
76 #include "nsRange.h"
77 #include "nsITextControlFrame.h"
78 #include "nsNameSpaceManager.h"
79 #include "nsIPercentBSizeObserver.h"
80 #include "nsStyleStructInlines.h"
81 #include "FrameLayerBuilder.h"
82 #include "ImageLayers.h"
84 #include "nsBidiPresUtils.h"
85 #include "RubyUtils.h"
86 #include "TextOverflow.h"
87 #include "nsAnimationManager.h"
89 // For triple-click pref
90 #include "imgIRequest.h"
91 #include "nsError.h"
92 #include "nsContainerFrame.h"
93 #include "nsBoxLayoutState.h"
94 #include "nsBlockFrame.h"
95 #include "nsDisplayList.h"
96 #include "nsChangeHint.h"
97 #include "nsDeckFrame.h"
98 #include "nsSubDocumentFrame.h"
99 #include "RetainedDisplayListBuilder.h"
101 #include "gfxContext.h"
102 #include "nsAbsoluteContainingBlock.h"
103 #include "StickyScrollContainer.h"
104 #include "nsFontInflationData.h"
105 #include "nsRegion.h"
106 #include "nsIFrameInlines.h"
107 #include "nsStyleChangeList.h"
108 #include "nsWindowSizes.h"
110 #include "mozilla/AsyncEventDispatcher.h"
111 #include "mozilla/CSSClipPathInstance.h"
112 #include "mozilla/EffectCompositor.h"
113 #include "mozilla/EffectSet.h"
114 #include "mozilla/EventListenerManager.h"
115 #include "mozilla/EventStateManager.h"
116 #include "mozilla/EventStates.h"
117 #include "mozilla/Preferences.h"
118 #include "mozilla/LookAndFeel.h"
119 #include "mozilla/MouseEvents.h"
120 #include "mozilla/ServoStyleSet.h"
121 #include "mozilla/ServoStyleSetInlines.h"
122 #include "mozilla/css/ImageLoader.h"
123 #include "mozilla/dom/HTMLBodyElement.h"
124 #include "mozilla/dom/SVGPathData.h"
125 #include "mozilla/dom/TouchEvent.h"
126 #include "mozilla/gfx/Tools.h"
127 #include "mozilla/layers/WebRenderUserData.h"
128 #include "mozilla/layout/ScrollAnchorContainer.h"
129 #include "nsPrintfCString.h"
130 #include "ActiveLayerTracker.h"
132 #include "nsITheme.h"
134 using namespace mozilla;
135 using namespace mozilla::css;
136 using namespace mozilla::dom;
137 using namespace mozilla::gfx;
138 using namespace mozilla::layers;
139 using namespace mozilla::layout;
140 typedef nsAbsoluteContainingBlock::AbsPosReflowFlags AbsPosReflowFlags;
141 using nsStyleTransformMatrix::TransformReferenceBox;
143 const mozilla::LayoutFrameType nsIFrame::sLayoutFrameTypes[
144 #define FRAME_ID(...) 1 +
145 #define ABSTRACT_FRAME_ID(...)
146 #include "mozilla/FrameIdList.h"
147 #undef FRAME_ID
148 #undef ABSTRACT_FRAME_ID
149 0] = {
150 #define FRAME_ID(class_, type_, ...) mozilla::LayoutFrameType::type_,
151 #define ABSTRACT_FRAME_ID(...)
152 #include "mozilla/FrameIdList.h"
153 #undef FRAME_ID
154 #undef ABSTRACT_FRAME_ID
157 const nsIFrame::FrameClassBits nsIFrame::sFrameClassBits[
158 #define FRAME_ID(...) 1 +
159 #define ABSTRACT_FRAME_ID(...)
160 #include "mozilla/FrameIdList.h"
161 #undef FRAME_ID
162 #undef ABSTRACT_FRAME_ID
163 0] = {
164 #define Leaf eFrameClassBitsLeaf
165 #define NotLeaf eFrameClassBitsNone
166 #define DynamicLeaf eFrameClassBitsDynamicLeaf
167 #define FRAME_ID(class_, type_, leaf_, ...) leaf_,
168 #define ABSTRACT_FRAME_ID(...)
169 #include "mozilla/FrameIdList.h"
170 #undef Leaf
171 #undef NotLeaf
172 #undef DynamicLeaf
173 #undef FRAME_ID
174 #undef ABSTRACT_FRAME_ID
177 // Struct containing cached metrics for box-wrapped frames.
178 struct nsBoxLayoutMetrics {
179 nsSize mPrefSize;
180 nsSize mMinSize;
181 nsSize mMaxSize;
183 nsSize mBlockMinSize;
184 nsSize mBlockPrefSize;
185 nscoord mBlockAscent;
187 nscoord mFlex;
188 nscoord mAscent;
190 nsSize mLastSize;
193 struct nsContentAndOffset {
194 nsIContent* mContent = nullptr;
195 int32_t mOffset = 0;
198 // Some Misc #defines
199 #define SELECTION_DEBUG 0
200 #define FORCE_SELECTION_UPDATE 1
201 #define CALC_DEBUG 0
203 #include "nsILineIterator.h"
204 #include "prenv.h"
206 NS_DECLARE_FRAME_PROPERTY_DELETABLE(BoxMetricsProperty, nsBoxLayoutMetrics)
208 static void InitBoxMetrics(nsIFrame* aFrame, bool aClear) {
209 if (aClear) {
210 aFrame->RemoveProperty(BoxMetricsProperty());
213 nsBoxLayoutMetrics* metrics = new nsBoxLayoutMetrics();
214 aFrame->SetProperty(BoxMetricsProperty(), metrics);
216 aFrame->nsIFrame::MarkIntrinsicISizesDirty();
217 metrics->mBlockAscent = 0;
218 metrics->mLastSize.SizeTo(0, 0);
221 // Utility function to set a nsRect-valued property table entry on aFrame,
222 // reusing the existing storage if the property happens to be already set.
223 template <typename T>
224 static void SetOrUpdateRectValuedProperty(
225 nsIFrame* aFrame, FrameProperties::Descriptor<T> aProperty,
226 const nsRect& aNewValue) {
227 bool found;
228 nsRect* rectStorage = aFrame->GetProperty(aProperty, &found);
229 if (!found) {
230 rectStorage = new nsRect(aNewValue);
231 aFrame->AddProperty(aProperty, rectStorage);
232 } else {
233 *rectStorage = aNewValue;
237 static bool IsXULBoxWrapped(const nsIFrame* aFrame) {
238 return aFrame->GetParent() && aFrame->GetParent()->IsXULBoxFrame() &&
239 !aFrame->IsXULBoxFrame();
242 void nsReflowStatus::UpdateTruncated(const ReflowInput& aReflowInput,
243 const ReflowOutput& aMetrics) {
244 const WritingMode containerWM = aMetrics.GetWritingMode();
245 if (aReflowInput.GetWritingMode().IsOrthogonalTo(containerWM)) {
246 // Orthogonal flows are always reflowed with an unconstrained dimension,
247 // so should never end up truncated (see ReflowInput::Init()).
248 mTruncated = false;
249 } else if (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
250 aReflowInput.AvailableBSize() < aMetrics.BSize(containerWM) &&
251 !aReflowInput.mFlags.mIsTopOfPage) {
252 mTruncated = true;
253 } else {
254 mTruncated = false;
258 /* static */
259 void nsIFrame::DestroyAnonymousContent(
260 nsPresContext* aPresContext, already_AddRefed<nsIContent>&& aContent) {
261 if (nsCOMPtr<nsIContent> content = aContent) {
262 aPresContext->EventStateManager()->NativeAnonymousContentRemoved(content);
263 aPresContext->PresShell()->NativeAnonymousContentRemoved(content);
264 content->UnbindFromTree();
268 // Formerly the nsIFrameDebug interface
270 std::ostream& operator<<(std::ostream& aStream, const nsReflowStatus& aStatus) {
271 char complete = 'Y';
272 if (aStatus.IsIncomplete()) {
273 complete = 'N';
274 } else if (aStatus.IsOverflowIncomplete()) {
275 complete = 'O';
278 char brk = 'N';
279 if (aStatus.IsInlineBreakBefore()) {
280 brk = 'B';
281 } else if (aStatus.IsInlineBreakAfter()) {
282 brk = 'A';
285 aStream << "["
286 << "Complete=" << complete << ","
287 << "NIF=" << (aStatus.NextInFlowNeedsReflow() ? 'Y' : 'N') << ","
288 << "Truncated=" << (aStatus.IsTruncated() ? 'Y' : 'N') << ","
289 << "Break=" << brk << ","
290 << "FirstLetter=" << (aStatus.FirstLetterComplete() ? 'Y' : 'N')
291 << "]";
292 return aStream;
295 #ifdef DEBUG
296 static bool gShowFrameBorders = false;
298 void nsIFrame::ShowFrameBorders(bool aEnable) { gShowFrameBorders = aEnable; }
300 bool nsIFrame::GetShowFrameBorders() { return gShowFrameBorders; }
302 static bool gShowEventTargetFrameBorder = false;
304 void nsIFrame::ShowEventTargetFrameBorder(bool aEnable) {
305 gShowEventTargetFrameBorder = aEnable;
308 bool nsIFrame::GetShowEventTargetFrameBorder() {
309 return gShowEventTargetFrameBorder;
313 * Note: the log module is created during library initialization which
314 * means that you cannot perform logging before then.
316 mozilla::LazyLogModule nsIFrame::sFrameLogModule("frame");
318 #endif
320 NS_DECLARE_FRAME_PROPERTY_DELETABLE(AbsoluteContainingBlockProperty,
321 nsAbsoluteContainingBlock)
323 bool nsIFrame::HasAbsolutelyPositionedChildren() const {
324 return IsAbsoluteContainer() &&
325 GetAbsoluteContainingBlock()->HasAbsoluteFrames();
328 nsAbsoluteContainingBlock* nsIFrame::GetAbsoluteContainingBlock() const {
329 NS_ASSERTION(IsAbsoluteContainer(),
330 "The frame is not marked as an abspos container correctly");
331 nsAbsoluteContainingBlock* absCB =
332 GetProperty(AbsoluteContainingBlockProperty());
333 NS_ASSERTION(absCB,
334 "The frame is marked as an abspos container but doesn't have "
335 "the property");
336 return absCB;
339 void nsIFrame::MarkAsAbsoluteContainingBlock() {
340 MOZ_ASSERT(HasAnyStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN));
341 NS_ASSERTION(!GetProperty(AbsoluteContainingBlockProperty()),
342 "Already has an abs-pos containing block property?");
343 NS_ASSERTION(!HasAnyStateBits(NS_FRAME_HAS_ABSPOS_CHILDREN),
344 "Already has NS_FRAME_HAS_ABSPOS_CHILDREN state bit?");
345 AddStateBits(NS_FRAME_HAS_ABSPOS_CHILDREN);
346 SetProperty(AbsoluteContainingBlockProperty(),
347 new nsAbsoluteContainingBlock(GetAbsoluteListID()));
350 void nsIFrame::MarkAsNotAbsoluteContainingBlock() {
351 NS_ASSERTION(!HasAbsolutelyPositionedChildren(), "Think of the children!");
352 NS_ASSERTION(GetProperty(AbsoluteContainingBlockProperty()),
353 "Should have an abs-pos containing block property");
354 NS_ASSERTION(HasAnyStateBits(NS_FRAME_HAS_ABSPOS_CHILDREN),
355 "Should have NS_FRAME_HAS_ABSPOS_CHILDREN state bit");
356 MOZ_ASSERT(HasAnyStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN));
357 RemoveStateBits(NS_FRAME_HAS_ABSPOS_CHILDREN);
358 RemoveProperty(AbsoluteContainingBlockProperty());
361 bool nsIFrame::CheckAndClearPaintedState() {
362 bool result = HasAnyStateBits(NS_FRAME_PAINTED_THEBES);
363 RemoveStateBits(NS_FRAME_PAINTED_THEBES);
365 for (const auto& childList : ChildLists()) {
366 for (nsIFrame* child : childList.mList) {
367 if (child->CheckAndClearPaintedState()) {
368 result = true;
372 return result;
375 bool nsIFrame::CheckAndClearDisplayListState() {
376 bool result = BuiltDisplayList();
377 SetBuiltDisplayList(false);
379 for (const auto& childList : ChildLists()) {
380 for (nsIFrame* child : childList.mList) {
381 if (child->CheckAndClearDisplayListState()) {
382 result = true;
386 return result;
389 bool nsIFrame::IsVisibleConsideringAncestors(uint32_t aFlags) const {
390 if (!StyleVisibility()->IsVisible()) {
391 return false;
394 if (PresShell()->IsUnderHiddenEmbedderElement()) {
395 return false;
398 const nsIFrame* frame = this;
399 while (frame) {
400 nsView* view = frame->GetView();
401 if (view && view->GetVisibility() == nsViewVisibility_kHide) return false;
403 nsIFrame* parent = frame->GetParent();
404 nsDeckFrame* deck = do_QueryFrame(parent);
405 if (deck) {
406 if (deck->GetSelectedBox() != frame) return false;
409 if (parent) {
410 frame = parent;
411 } else {
412 parent = nsLayoutUtils::GetCrossDocParentFrame(frame);
413 if (!parent) break;
415 if ((aFlags & nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY) == 0 &&
416 parent->PresContext()->IsChrome() &&
417 !frame->PresContext()->IsChrome()) {
418 break;
421 frame = parent;
425 return true;
428 void nsIFrame::FindCloserFrameForSelection(
429 const nsPoint& aPoint, FrameWithDistance* aCurrentBestFrame) {
430 if (nsLayoutUtils::PointIsCloserToRect(aPoint, mRect,
431 aCurrentBestFrame->mXDistance,
432 aCurrentBestFrame->mYDistance)) {
433 aCurrentBestFrame->mFrame = this;
437 void nsIFrame::ContentStatesChanged(mozilla::EventStates aStates) {}
439 void WeakFrame::Clear(mozilla::PresShell* aPresShell) {
440 if (aPresShell) {
441 aPresShell->RemoveWeakFrame(this);
443 mFrame = nullptr;
446 AutoWeakFrame::AutoWeakFrame(const WeakFrame& aOther)
447 : mPrev(nullptr), mFrame(nullptr) {
448 Init(aOther.GetFrame());
451 void AutoWeakFrame::Clear(mozilla::PresShell* aPresShell) {
452 if (aPresShell) {
453 aPresShell->RemoveAutoWeakFrame(this);
455 mFrame = nullptr;
456 mPrev = nullptr;
459 AutoWeakFrame::~AutoWeakFrame() {
460 Clear(mFrame ? mFrame->PresContext()->GetPresShell() : nullptr);
463 void AutoWeakFrame::Init(nsIFrame* aFrame) {
464 Clear(mFrame ? mFrame->PresContext()->GetPresShell() : nullptr);
465 mFrame = aFrame;
466 if (mFrame) {
467 mozilla::PresShell* presShell = mFrame->PresContext()->GetPresShell();
468 NS_WARNING_ASSERTION(presShell, "Null PresShell in AutoWeakFrame!");
469 if (presShell) {
470 presShell->AddAutoWeakFrame(this);
471 } else {
472 mFrame = nullptr;
477 void WeakFrame::Init(nsIFrame* aFrame) {
478 Clear(mFrame ? mFrame->PresContext()->GetPresShell() : nullptr);
479 mFrame = aFrame;
480 if (mFrame) {
481 mozilla::PresShell* presShell = mFrame->PresContext()->GetPresShell();
482 MOZ_ASSERT(presShell, "Null PresShell in WeakFrame!");
483 if (presShell) {
484 presShell->AddWeakFrame(this);
485 } else {
486 mFrame = nullptr;
491 nsIFrame* NS_NewEmptyFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
492 return new (aPresShell) nsIFrame(aStyle, aPresShell->GetPresContext());
495 nsIFrame::~nsIFrame() {
496 MOZ_COUNT_DTOR(nsIFrame);
498 MOZ_ASSERT(GetVisibility() != Visibility::ApproximatelyVisible,
499 "Visible nsFrame is being destroyed");
502 NS_IMPL_FRAMEARENA_HELPERS(nsIFrame)
504 // Dummy operator delete. Will never be called, but must be defined
505 // to satisfy some C++ ABIs.
506 void nsIFrame::operator delete(void*, size_t) {
507 MOZ_CRASH("nsIFrame::operator delete should never be called");
510 NS_QUERYFRAME_HEAD(nsIFrame)
511 NS_QUERYFRAME_ENTRY(nsIFrame)
512 NS_QUERYFRAME_TAIL_INHERITANCE_ROOT
514 /////////////////////////////////////////////////////////////////////////////
515 // nsIFrame
517 static bool IsFontSizeInflationContainer(nsIFrame* aFrame,
518 const nsStyleDisplay* aStyleDisplay) {
520 * Font size inflation is built around the idea that we're inflating
521 * the fonts for a pan-and-zoom UI so that when the user scales up a
522 * block or other container to fill the width of the device, the fonts
523 * will be readable. To do this, we need to pick what counts as a
524 * container.
526 * From a code perspective, the only hard requirement is that frames
527 * that are line participants
528 * (nsIFrame::IsFrameOfType(nsIFrame::eLineParticipant)) are never
529 * containers, since line layout assumes that the inflation is
530 * consistent within a line.
532 * This is not an imposition, since we obviously want a bunch of text
533 * (possibly with inline elements) flowing within a block to count the
534 * block (or higher) as its container.
536 * We also want form controls, including the text in the anonymous
537 * content inside of them, to match each other and the text next to
538 * them, so they and their anonymous content should also not be a
539 * container.
541 * However, because we can't reliably compute sizes across XUL during
542 * reflow, any XUL frame with a XUL parent is always a container.
544 * There are contexts where it would be nice if some blocks didn't
545 * count as a container, so that, for example, an indented quotation
546 * didn't end up with a smaller font size. However, it's hard to
547 * distinguish these situations where we really do want the indented
548 * thing to count as a container, so we don't try, and blocks are
549 * always containers.
552 // The root frame should always be an inflation container.
553 if (!aFrame->GetParent()) {
554 return true;
557 nsIContent* content = aFrame->GetContent();
558 if (content && content->IsInNativeAnonymousSubtree()) {
559 // Native anonymous content shouldn't be a font inflation root,
560 // except for the canvas custom content container.
561 nsCanvasFrame* canvas = aFrame->PresShell()->GetCanvasFrame();
562 return canvas && canvas->GetCustomContentContainer() == content;
565 LayoutFrameType frameType = aFrame->Type();
566 bool isInline =
567 (nsStyleDisplay::IsInlineFlow(aFrame->GetDisplay()) ||
568 RubyUtils::IsRubyBox(frameType) ||
569 (aFrame->IsFloating() && frameType == LayoutFrameType::Letter) ||
570 // Given multiple frames for the same node, only the
571 // outer one should be considered a container.
572 // (Important, e.g., for nsSelectsAreaFrame.)
573 (aFrame->GetParent()->GetContent() == content) ||
574 (content &&
575 // Form controls shouldn't become inflation containers.
576 (content->IsAnyOfHTMLElements(nsGkAtoms::option, nsGkAtoms::optgroup,
577 nsGkAtoms::select, nsGkAtoms::input,
578 nsGkAtoms::button)))) &&
579 !(aFrame->IsXULBoxFrame() && aFrame->GetParent()->IsXULBoxFrame());
580 NS_ASSERTION(!aFrame->IsFrameOfType(nsIFrame::eLineParticipant) || isInline ||
581 // br frames and mathml frames report being line
582 // participants even when their position or display is
583 // set
584 aFrame->IsBrFrame() ||
585 aFrame->IsFrameOfType(nsIFrame::eMathML),
586 "line participants must not be containers");
587 NS_ASSERTION(!aFrame->IsBulletFrame() || isInline,
588 "bullets should not be containers");
589 return !isInline;
592 static void MaybeScheduleReflowSVGNonDisplayText(nsIFrame* aFrame) {
593 if (!SVGUtils::IsInSVGTextSubtree(aFrame)) {
594 return;
597 // We need to ensure that any non-display SVGTextFrames get reflowed when a
598 // child text frame gets new style. Thus we need to schedule a reflow in
599 // |DidSetComputedStyle|. We also need to call it from |DestroyFrom|,
600 // because otherwise we won't get notified when style changes to
601 // "display:none".
602 SVGTextFrame* svgTextFrame = static_cast<SVGTextFrame*>(
603 nsLayoutUtils::GetClosestFrameOfType(aFrame, LayoutFrameType::SVGText));
604 nsIFrame* anonBlock = svgTextFrame->PrincipalChildList().FirstChild();
606 // Note that we must check NS_FRAME_FIRST_REFLOW on our SVGTextFrame's
607 // anonymous block frame rather than our aFrame, since NS_FRAME_FIRST_REFLOW
608 // may be set on us if we're a new frame that has been inserted after the
609 // document's first reflow. (In which case this DidSetComputedStyle call may
610 // be happening under frame construction under a Reflow() call.)
611 if (!anonBlock || anonBlock->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
612 return;
615 if (!svgTextFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY) ||
616 svgTextFrame->HasAnyStateBits(NS_STATE_SVG_TEXT_IN_REFLOW)) {
617 return;
620 svgTextFrame->ScheduleReflowSVGNonDisplayText(IntrinsicDirty::StyleChange);
623 bool nsIFrame::IsPrimaryFrameOfRootOrBodyElement() const {
624 if (!IsPrimaryFrame()) {
625 return false;
627 nsIContent* content = GetContent();
628 Document* document = content->OwnerDoc();
629 return content == document->GetRootElement() ||
630 content == document->GetBodyElement();
633 bool nsIFrame::IsRenderedLegend() const {
634 if (auto* parent = GetParent(); parent && parent->IsFieldSetFrame()) {
635 return static_cast<nsFieldSetFrame*>(parent)->GetLegend() == this;
637 return false;
640 void nsIFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
641 nsIFrame* aPrevInFlow) {
642 MOZ_ASSERT(nsQueryFrame::FrameIID(mClass) == GetFrameId());
643 MOZ_ASSERT(!mContent, "Double-initing a frame?");
644 NS_ASSERTION(IsFrameOfType(eDEBUGAllFrames) && !IsFrameOfType(eDEBUGNoFrames),
645 "IsFrameOfType implementation that doesn't call base class");
647 mContent = aContent;
648 mParent = aParent;
649 MOZ_DIAGNOSTIC_ASSERT(!mParent || PresShell() == mParent->PresShell());
651 if (aPrevInFlow) {
652 mWritingMode = aPrevInFlow->GetWritingMode();
654 // Copy some state bits from prev-in-flow (the bits that should apply
655 // throughout a continuation chain). The bits are sorted according to their
656 // order in nsFrameStateBits.h.
658 // clang-format off
659 AddStateBits(aPrevInFlow->GetStateBits() &
660 (NS_FRAME_GENERATED_CONTENT |
661 NS_FRAME_OUT_OF_FLOW |
662 NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN |
663 NS_FRAME_INDEPENDENT_SELECTION |
664 NS_FRAME_PART_OF_IBSPLIT |
665 NS_FRAME_MAY_BE_TRANSFORMED |
666 NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR));
667 // clang-format on
669 // Copy other bits in nsIFrame from prev-in-flow.
670 mHasColumnSpanSiblings = aPrevInFlow->HasColumnSpanSiblings();
671 } else {
672 PresContext()->ConstructedFrame();
675 if (GetParent()) {
676 if (MOZ_UNLIKELY(mContent == PresContext()->Document()->GetRootElement() &&
677 mContent == GetParent()->GetContent())) {
678 // Our content is the root element and we have the same content as our
679 // parent. That is, we are the internal anonymous frame of the root
680 // element. Copy the used mWritingMode from our parent because
681 // mDocElementContainingBlock gets its mWritingMode from <body>.
682 mWritingMode = GetParent()->GetWritingMode();
685 // Copy some state bits from our parent (the bits that should apply
686 // recursively throughout a subtree). The bits are sorted according to their
687 // order in nsFrameStateBits.h.
689 // clang-format off
690 AddStateBits(GetParent()->GetStateBits() &
691 (NS_FRAME_GENERATED_CONTENT |
692 NS_FRAME_INDEPENDENT_SELECTION |
693 NS_FRAME_IS_SVG_TEXT |
694 NS_FRAME_IN_POPUP |
695 NS_FRAME_IS_NONDISPLAY));
696 // clang-format on
698 if (HasAnyStateBits(NS_FRAME_IN_POPUP) && TrackingVisibility()) {
699 // Assume all frames in popups are visible.
700 IncApproximateVisibleCount();
703 if (aPrevInFlow) {
704 mMayHaveOpacityAnimation = aPrevInFlow->MayHaveOpacityAnimation();
705 mMayHaveTransformAnimation = aPrevInFlow->MayHaveTransformAnimation();
706 } else if (mContent) {
707 // It's fine to fetch the EffectSet for the style frame here because in the
708 // following code we take care of the case where animations may target
709 // a different frame.
710 EffectSet* effectSet = EffectSet::GetEffectSetForStyleFrame(this);
711 if (effectSet) {
712 mMayHaveOpacityAnimation = effectSet->MayHaveOpacityAnimation();
714 if (effectSet->MayHaveTransformAnimation()) {
715 // If we are the inner table frame for display:table content, then
716 // transform animations should go on our parent frame (the table wrapper
717 // frame).
719 // We do this when initializing the child frame (table inner frame),
720 // because when initializng the table wrapper frame, we don't yet have
721 // access to its children so we can't tell if we have transform
722 // animations or not.
723 if (IsFrameOfType(eSupportsCSSTransforms)) {
724 mMayHaveTransformAnimation = true;
725 AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED);
726 } else if (aParent && nsLayoutUtils::GetStyleFrame(aParent) == this) {
727 MOZ_ASSERT(
728 aParent->IsFrameOfType(eSupportsCSSTransforms),
729 "Style frames that don't support transforms should have parents"
730 " that do");
731 aParent->mMayHaveTransformAnimation = true;
732 aParent->AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED);
738 const nsStyleDisplay* disp = StyleDisplay();
739 if (disp->HasTransform(this)) {
740 // If 'transform' dynamically changes, RestyleManager takes care of
741 // updating this bit.
742 AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED);
745 if (disp->IsContainLayout() && disp->IsContainSize() &&
746 // All frames that support contain:layout also support contain:size.
747 IsFrameOfType(eSupportsContainLayoutAndPaint) && !IsTableWrapperFrame()) {
748 // In general, frames that have contain:layout+size can be reflow roots.
749 // (One exception: table-wrapper frames don't work well as reflow roots,
750 // because their inner-table ReflowInput init path tries to reuse & deref
751 // the wrapper's containing block reflow input, which may be null if we
752 // initiate reflow from the table-wrapper itself.)
754 // Changes to `contain` force frame reconstructions, so this bit can be set
755 // for the whole lifetime of this frame.
756 AddStateBits(NS_FRAME_REFLOW_ROOT);
759 if (nsLayoutUtils::FontSizeInflationEnabled(PresContext()) ||
760 !GetParent()
761 #ifdef DEBUG
762 // We have assertions that check inflation invariants even when
763 // font size inflation is not enabled.
764 || true
765 #endif
767 if (IsFontSizeInflationContainer(this, disp)) {
768 AddStateBits(NS_FRAME_FONT_INFLATION_CONTAINER);
769 if (!GetParent() ||
770 // I'd use NS_FRAME_OUT_OF_FLOW, but it's not set yet.
771 disp->IsFloating(this) || disp->IsAbsolutelyPositioned(this) ||
772 GetParent()->IsFlexContainerFrame() ||
773 GetParent()->IsGridContainerFrame()) {
774 AddStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT);
777 NS_ASSERTION(
778 GetParent() || HasAnyStateBits(NS_FRAME_FONT_INFLATION_CONTAINER),
779 "root frame should always be a container");
782 if (PresShell()->AssumeAllFramesVisible() && TrackingVisibility()) {
783 IncApproximateVisibleCount();
786 DidSetComputedStyle(nullptr);
788 if (::IsXULBoxWrapped(this)) ::InitBoxMetrics(this, false);
790 // For a newly created frame, we need to update this frame's visibility state.
791 // Usually we update the state when the frame is restyled and has a
792 // VisibilityChange change hint but we don't generate any change hints for
793 // newly created frames.
794 // Note: We don't need to do this for placeholders since placeholders have
795 // different styles so that the styles don't have visibility:hidden even if
796 // the parent has visibility:hidden style. We also don't need to update the
797 // state when creating continuations because its visibility is the same as its
798 // prev-in-flow, and the animation code cares only primary frames.
799 if (!IsPlaceholderFrame() && !aPrevInFlow) {
800 UpdateVisibleDescendantsState();
804 void nsIFrame::DestroyFrom(nsIFrame* aDestructRoot,
805 PostDestroyData& aPostDestroyData) {
806 NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
807 "destroy called on frame while scripts not blocked");
808 NS_ASSERTION(!GetNextSibling() && !GetPrevSibling(),
809 "Frames should be removed before destruction.");
810 NS_ASSERTION(aDestructRoot, "Must specify destruct root");
811 MOZ_ASSERT(!HasAbsolutelyPositionedChildren());
812 MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT),
813 "NS_FRAME_PART_OF_IBSPLIT set on non-nsContainerFrame?");
815 MaybeScheduleReflowSVGNonDisplayText(this);
817 SVGObserverUtils::InvalidateDirectRenderingObservers(this);
819 if (StyleDisplay()->mPosition == StylePositionProperty::Sticky) {
820 StickyScrollContainer* ssc =
821 StickyScrollContainer::GetStickyScrollContainerForFrame(this);
822 if (ssc) {
823 ssc->RemoveFrame(this);
827 nsPresContext* presContext = PresContext();
828 mozilla::PresShell* presShell = presContext->GetPresShell();
829 if (mState & NS_FRAME_OUT_OF_FLOW) {
830 nsPlaceholderFrame* placeholder = GetPlaceholderFrame();
831 NS_ASSERTION(
832 !placeholder || (aDestructRoot != this),
833 "Don't call Destroy() on OOFs, call Destroy() on the placeholder.");
834 NS_ASSERTION(!placeholder || nsLayoutUtils::IsProperAncestorFrame(
835 aDestructRoot, placeholder),
836 "Placeholder relationship should have been torn down already; "
837 "this might mean we have a stray placeholder in the tree.");
838 if (placeholder) {
839 placeholder->SetOutOfFlowFrame(nullptr);
843 if (IsPrimaryFrame()) {
844 // This needs to happen before we clear our Properties() table.
845 ActiveLayerTracker::TransferActivityToContent(this, mContent);
848 ScrollAnchorContainer* anchor = nullptr;
849 if (IsScrollAnchor(&anchor)) {
850 anchor->InvalidateAnchor();
853 if (HasCSSAnimations() || HasCSSTransitions() ||
854 // It's fine to look up the style frame here since if we're destroying the
855 // frames for display:table content we should be destroying both wrapper
856 // and inner frame.
857 EffectSet::GetEffectSetForStyleFrame(this)) {
858 // If no new frame for this element is created by the end of the
859 // restyling process, stop animations and transitions for this frame
860 RestyleManager::AnimationsWithDestroyedFrame* adf =
861 presContext->RestyleManager()->GetAnimationsWithDestroyedFrame();
862 // AnimationsWithDestroyedFrame only lives during the restyling process.
863 if (adf) {
864 adf->Put(mContent, mComputedStyle);
868 // Disable visibility tracking. Note that we have to do this before we clear
869 // frame properties and lose track of whether we were previously visible.
870 // XXX(seth): It'd be ideal to assert that we're already marked nonvisible
871 // here, but it's unfortunately tricky to guarantee in the face of things like
872 // frame reconstruction induced by style changes.
873 DisableVisibilityTracking();
875 // Ensure that we're not in the approximately visible list anymore.
876 PresContext()->GetPresShell()->RemoveFrameFromApproximatelyVisibleList(this);
878 presShell->NotifyDestroyingFrame(this);
880 if (mState & NS_FRAME_EXTERNAL_REFERENCE) {
881 presShell->ClearFrameRefs(this);
884 nsView* view = GetView();
885 if (view) {
886 view->SetFrame(nullptr);
887 view->Destroy();
890 // Make sure that our deleted frame can't be returned from GetPrimaryFrame()
891 if (IsPrimaryFrame()) {
892 mContent->SetPrimaryFrame(nullptr);
894 // Pass the root of a generated content subtree (e.g. ::after/::before) to
895 // aPostDestroyData to unbind it after frame destruction is done.
896 if (HasAnyStateBits(NS_FRAME_GENERATED_CONTENT) &&
897 mContent->IsRootOfNativeAnonymousSubtree()) {
898 aPostDestroyData.AddAnonymousContent(mContent.forget());
902 // Remove all properties attached to the frame, to ensure any property
903 // destructors that need the frame pointer are handled properly.
904 RemoveAllProperties();
906 // Must retrieve the object ID before calling destructors, so the
907 // vtable is still valid.
909 // Note to future tweakers: having the method that returns the
910 // object size call the destructor will not avoid an indirect call;
911 // the compiler cannot devirtualize the call to the destructor even
912 // if it's from a method defined in the same class.
914 nsQueryFrame::FrameIID id = GetFrameId();
915 this->~nsIFrame();
917 #ifdef DEBUG
919 nsIFrame* rootFrame = presShell->GetRootFrame();
920 MOZ_ASSERT(rootFrame);
921 if (this != rootFrame) {
922 const RetainedDisplayListData* data =
923 GetRetainedDisplayListData(rootFrame);
925 const bool inModifiedList =
926 data && (data->GetFlags(this) &
927 RetainedDisplayListData::FrameFlags::Modified);
929 MOZ_ASSERT(!inModifiedList,
930 "A dtor added this frame to modified frames list!");
933 #endif
935 // Now that we're totally cleaned out, we need to add ourselves to
936 // the presshell's recycler.
937 presShell->FreeFrame(id, this);
940 nsresult nsIFrame::GetOffsets(int32_t& aStart, int32_t& aEnd) const {
941 aStart = 0;
942 aEnd = 0;
943 return NS_OK;
946 static void CompareLayers(
947 const nsStyleImageLayers* aFirstLayers,
948 const nsStyleImageLayers* aSecondLayers,
949 const std::function<void(imgRequestProxy* aReq)>& aCallback) {
950 NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, (*aFirstLayers)) {
951 const auto& image = aFirstLayers->mLayers[i].mImage;
952 if (!image.IsImageRequestType() || !image.IsResolved()) {
953 continue;
956 // aCallback is called when the style image in aFirstLayers is thought to
957 // be different with the corresponded one in aSecondLayers
958 if (!aSecondLayers || i >= aSecondLayers->mImageCount ||
959 (!aSecondLayers->mLayers[i].mImage.IsResolved() ||
960 image.GetImageRequest() !=
961 aSecondLayers->mLayers[i].mImage.GetImageRequest())) {
962 if (imgRequestProxy* req = image.GetImageRequest()) {
963 aCallback(req);
969 static void AddAndRemoveImageAssociations(
970 ImageLoader& aImageLoader, nsIFrame* aFrame,
971 const nsStyleImageLayers* aOldLayers,
972 const nsStyleImageLayers* aNewLayers) {
973 // If the old context had a background-image image, or mask-image image,
974 // and new context does not have the same image, clear the image load
975 // notifier (which keeps the image loading, if it still is) for the frame.
976 // We want to do this conservatively because some frames paint their
977 // backgrounds from some other frame's style data, and we don't want
978 // to clear those notifiers unless we have to. (They'll be reset
979 // when we paint, although we could miss a notification in that
980 // interval.)
981 if (aOldLayers && aFrame->HasImageRequest()) {
982 CompareLayers(aOldLayers, aNewLayers, [&](imgRequestProxy* aReq) {
983 aImageLoader.DisassociateRequestFromFrame(aReq, aFrame);
987 CompareLayers(aNewLayers, aOldLayers, [&](imgRequestProxy* aReq) {
988 aImageLoader.AssociateRequestToFrame(aReq, aFrame);
992 void nsIFrame::AddDisplayItem(nsDisplayItemBase* aItem) {
993 DisplayItemArray* items = GetProperty(DisplayItems());
994 if (!items) {
995 items = new DisplayItemArray();
996 AddProperty(DisplayItems(), items);
998 MOZ_DIAGNOSTIC_ASSERT(!items->Contains(aItem));
999 items->AppendElement(aItem);
1002 bool nsIFrame::RemoveDisplayItem(nsDisplayItemBase* aItem) {
1003 DisplayItemArray* items = GetProperty(DisplayItems());
1004 if (!items) {
1005 return false;
1007 bool result = items->RemoveElement(aItem);
1008 if (items->IsEmpty()) {
1009 RemoveProperty(DisplayItems());
1011 return result;
1014 bool nsIFrame::HasDisplayItems() {
1015 DisplayItemArray* items = GetProperty(DisplayItems());
1016 return items != nullptr;
1019 bool nsIFrame::HasDisplayItem(nsDisplayItemBase* aItem) {
1020 DisplayItemArray* items = GetProperty(DisplayItems());
1021 if (!items) {
1022 return false;
1024 return items->Contains(aItem);
1027 bool nsIFrame::HasDisplayItem(uint32_t aKey) {
1028 DisplayItemArray* items = GetProperty(DisplayItems());
1029 if (!items) {
1030 return false;
1033 for (nsDisplayItemBase* i : *items) {
1034 if (i->GetPerFrameKey() == aKey) {
1035 return true;
1038 return false;
1041 template <typename Condition>
1042 static void DiscardDisplayItems(nsIFrame* aFrame, Condition aCondition) {
1043 auto* items = aFrame->GetProperty(nsIFrame::DisplayItems());
1044 if (!items) {
1045 return;
1048 for (nsDisplayItemBase* i : *items) {
1049 // Only discard items that are invalidated by this frame, as we're only
1050 // guaranteed to rebuild those items. Table background items are created by
1051 // the relevant table part, but have the cell frame as the primary frame,
1052 // and we don't want to remove them if this is the cell.
1053 if (aCondition(i) && i->FrameForInvalidation() == aFrame) {
1054 i->SetCantBeReused();
1059 static void DiscardOldItems(nsIFrame* aFrame) {
1060 DiscardDisplayItems(
1061 aFrame, [](nsDisplayItemBase* aItem) { return aItem->IsOldItem(); });
1064 void nsIFrame::RemoveDisplayItemDataForDeletion() {
1065 // Destroying a WebRenderUserDataTable can cause destruction of other objects
1066 // which can remove frame properties in their destructor. If we delete a frame
1067 // property it runs the destructor of the stored object in the middle of
1068 // updating the frame property table, so if the destruction of that object
1069 // causes another update to the frame property table it would leave the frame
1070 // property table in an inconsistent state. So we remove it from the table and
1071 // then destroy it. (bug 1530657)
1072 WebRenderUserDataTable* userDataTable =
1073 TakeProperty(WebRenderUserDataProperty::Key());
1074 if (userDataTable) {
1075 for (auto iter = userDataTable->Iter(); !iter.Done(); iter.Next()) {
1076 iter.UserData()->RemoveFromTable();
1078 delete userDataTable;
1081 FrameLayerBuilder::RemoveFrameFromLayerManager(this, DisplayItemData());
1082 DisplayItemData().Clear();
1084 DisplayItemArray* items = TakeProperty(DisplayItems());
1085 if (items) {
1086 for (nsDisplayItemBase* i : *items) {
1087 if (i->GetDependentFrame() == this && !i->HasDeletedFrame()) {
1088 i->Frame()->MarkNeedsDisplayItemRebuild();
1090 i->RemoveFrame(this);
1092 delete items;
1095 if (!nsLayoutUtils::AreRetainedDisplayListsEnabled()) {
1096 // Retained display lists are disabled, no need to update
1097 // RetainedDisplayListData.
1098 return;
1101 const bool updateData = IsFrameModified() || HasOverrideDirtyRegion() ||
1102 MayHaveWillChangeBudget();
1104 if (!updateData) {
1105 // No RetainedDisplayListData to update.
1106 return;
1109 nsIFrame* rootFrame = PresShell()->GetRootFrame();
1110 MOZ_ASSERT(rootFrame);
1112 RetainedDisplayListData* data = GetOrSetRetainedDisplayListData(rootFrame);
1114 if (MayHaveWillChangeBudget()) {
1115 // Keep the frame in list, so it can be removed from the will-change budget.
1116 data->Flags(this) = RetainedDisplayListData::FrameFlags::HadWillChange;
1117 return;
1120 if (IsFrameModified() || HasOverrideDirtyRegion()) {
1121 // Remove deleted frames from RetainedDisplayListData.
1122 DebugOnly<bool> removed = data->Remove(this);
1123 MOZ_ASSERT(removed,
1124 "Frame had flags set, but it was not found in DisplayListData!");
1128 void nsIFrame::MarkNeedsDisplayItemRebuild() {
1129 if (!nsLayoutUtils::AreRetainedDisplayListsEnabled() || IsFrameModified() ||
1130 HasAnyStateBits(NS_FRAME_IN_POPUP)) {
1131 // Skip frames that are already marked modified.
1132 return;
1135 if (Type() == LayoutFrameType::Placeholder) {
1136 nsIFrame* oof = static_cast<nsPlaceholderFrame*>(this)->GetOutOfFlowFrame();
1137 if (oof) {
1138 oof->MarkNeedsDisplayItemRebuild();
1140 // Do not mark placeholder frames modified.
1141 return;
1144 if (!nsLayoutUtils::DisplayRootHasRetainedDisplayListBuilder(this)) {
1145 return;
1148 nsIFrame* rootFrame = PresShell()->GetRootFrame();
1149 MOZ_ASSERT(rootFrame);
1151 if (rootFrame->IsFrameModified()) {
1152 return;
1155 RetainedDisplayListData* data = GetOrSetRetainedDisplayListData(rootFrame);
1157 if (data->ModifiedFramesCount() >
1158 StaticPrefs::layout_display_list_rebuild_frame_limit()) {
1159 // If the modified frames count is above the rebuild limit, mark the root
1160 // frame modified, and stop marking additional frames modified.
1161 data->AddModifiedFrame(rootFrame);
1162 rootFrame->SetFrameIsModified(true);
1163 return;
1166 data->AddModifiedFrame(this);
1167 SetFrameIsModified(true);
1169 MOZ_ASSERT(
1170 PresContext()->LayoutPhaseCount(nsLayoutPhase::DisplayListBuilding) == 0);
1172 // Hopefully this is cheap, but we could use a frame state bit to note
1173 // the presence of dependencies to speed it up.
1174 DisplayItemArray* items = GetProperty(DisplayItems());
1175 if (items) {
1176 for (nsDisplayItemBase* i : *items) {
1177 if (i->HasDeletedFrame() || i->Frame() == this) {
1178 // Ignore the items with deleted frames, and the items with |this| as
1179 // the primary frame.
1180 continue;
1183 if (i->GetDependentFrame() == this) {
1184 // For items with |this| as a dependent frame, mark the primary frame
1185 // for rebuild.
1186 i->Frame()->MarkNeedsDisplayItemRebuild();
1192 // Subclass hook for style post processing
1193 /* virtual */
1194 void nsIFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) {
1195 MaybeScheduleReflowSVGNonDisplayText(this);
1197 Document* doc = PresContext()->Document();
1198 ImageLoader* loader = doc->StyleImageLoader();
1199 // Continuing text frame doesn't initialize its continuation pointer before
1200 // reaching here for the first time, so we have to exclude text frames. This
1201 // doesn't affect correctness because text can't match selectors.
1203 // FIXME(emilio): We should consider fixing that.
1205 // TODO(emilio): Can we avoid doing some / all of the image stuff when
1206 // isNonTextFirstContinuation is false? We should consider doing this just for
1207 // primary frames and pseudos, but the first-line reparenting code makes it
1208 // all bad, should get around to bug 1465474 eventually :(
1209 const bool isNonText = !IsTextFrame();
1210 if (isNonText) {
1211 mComputedStyle->StartImageLoads(*doc, aOldComputedStyle);
1214 const nsStyleImageLayers* oldLayers =
1215 aOldComputedStyle ? &aOldComputedStyle->StyleBackground()->mImage
1216 : nullptr;
1217 const nsStyleImageLayers* newLayers = &StyleBackground()->mImage;
1218 AddAndRemoveImageAssociations(*loader, this, oldLayers, newLayers);
1220 oldLayers =
1221 aOldComputedStyle ? &aOldComputedStyle->StyleSVGReset()->mMask : nullptr;
1222 newLayers = &StyleSVGReset()->mMask;
1223 AddAndRemoveImageAssociations(*loader, this, oldLayers, newLayers);
1225 const nsStyleDisplay* disp = StyleDisplay();
1226 bool handleStickyChange = false;
1227 if (aOldComputedStyle) {
1228 // Detect style changes that should trigger a scroll anchor adjustment
1229 // suppression.
1230 // https://drafts.csswg.org/css-scroll-anchoring/#suppression-triggers
1231 bool needAnchorSuppression = false;
1233 // If we detect a change on margin, padding or border, we store the old
1234 // values on the frame itself between now and reflow, so if someone
1235 // calls GetUsed(Margin|Border|Padding)() before the next reflow, we
1236 // can give an accurate answer.
1237 // We don't want to set the property if one already exists.
1238 nsMargin oldValue(0, 0, 0, 0);
1239 nsMargin newValue(0, 0, 0, 0);
1240 const nsStyleMargin* oldMargin = aOldComputedStyle->StyleMargin();
1241 if (oldMargin->GetMargin(oldValue)) {
1242 if (!StyleMargin()->GetMargin(newValue) || oldValue != newValue) {
1243 if (!HasProperty(UsedMarginProperty())) {
1244 AddProperty(UsedMarginProperty(), new nsMargin(oldValue));
1246 needAnchorSuppression = true;
1250 const nsStylePadding* oldPadding = aOldComputedStyle->StylePadding();
1251 if (oldPadding->GetPadding(oldValue)) {
1252 if (!StylePadding()->GetPadding(newValue) || oldValue != newValue) {
1253 if (!HasProperty(UsedPaddingProperty())) {
1254 AddProperty(UsedPaddingProperty(), new nsMargin(oldValue));
1256 needAnchorSuppression = true;
1260 const nsStyleBorder* oldBorder = aOldComputedStyle->StyleBorder();
1261 oldValue = oldBorder->GetComputedBorder();
1262 newValue = StyleBorder()->GetComputedBorder();
1263 if (oldValue != newValue && !HasProperty(UsedBorderProperty())) {
1264 AddProperty(UsedBorderProperty(), new nsMargin(oldValue));
1267 const nsStyleDisplay* oldDisp = aOldComputedStyle->StyleDisplay();
1268 if (oldDisp->mOverflowAnchor != disp->mOverflowAnchor) {
1269 if (auto* container = ScrollAnchorContainer::FindFor(this)) {
1270 container->InvalidateAnchor();
1272 if (nsIScrollableFrame* scrollableFrame = do_QueryFrame(this)) {
1273 scrollableFrame->Anchor()->InvalidateAnchor();
1277 if (mInScrollAnchorChain) {
1278 const nsStylePosition* pos = StylePosition();
1279 const nsStylePosition* oldPos = aOldComputedStyle->StylePosition();
1280 if (!needAnchorSuppression &&
1281 (oldPos->mOffset != pos->mOffset || oldPos->mWidth != pos->mWidth ||
1282 oldPos->mMinWidth != pos->mMinWidth ||
1283 oldPos->mMaxWidth != pos->mMaxWidth ||
1284 oldPos->mHeight != pos->mHeight ||
1285 oldPos->mMinHeight != pos->mMinHeight ||
1286 oldPos->mMaxHeight != pos->mMaxHeight ||
1287 oldDisp->mPosition != disp->mPosition ||
1288 oldDisp->mTransform != disp->mTransform)) {
1289 needAnchorSuppression = true;
1292 if (needAnchorSuppression &&
1293 StaticPrefs::layout_css_scroll_anchoring_suppressions_enabled()) {
1294 ScrollAnchorContainer::FindFor(this)->SuppressAdjustments();
1298 if (disp->mPosition != oldDisp->mPosition) {
1299 if (!disp->IsRelativelyPositionedStyle() &&
1300 oldDisp->IsRelativelyPositionedStyle()) {
1301 RemoveProperty(NormalPositionProperty());
1304 handleStickyChange = disp->mPosition == StylePositionProperty::Sticky ||
1305 oldDisp->mPosition == StylePositionProperty::Sticky;
1307 } else { // !aOldComputedStyle
1308 handleStickyChange = disp->mPosition == StylePositionProperty::Sticky;
1311 if (handleStickyChange && !HasAnyStateBits(NS_FRAME_IS_NONDISPLAY) &&
1312 !GetPrevInFlow()) {
1313 // Note that we only add first continuations, but we really only
1314 // want to add first continuation-or-ib-split-siblings. But since we don't
1315 // yet know if we're a later part of a block-in-inline split, we'll just
1316 // add later members of a block-in-inline split here, and then
1317 // StickyScrollContainer will remove them later.
1318 if (auto* ssc =
1319 StickyScrollContainer::GetStickyScrollContainerForFrame(this)) {
1320 if (disp->mPosition == StylePositionProperty::Sticky) {
1321 ssc->AddFrame(this);
1322 } else {
1323 ssc->RemoveFrame(this);
1328 imgIRequest* oldBorderImage =
1329 aOldComputedStyle
1330 ? aOldComputedStyle->StyleBorder()->GetBorderImageRequest()
1331 : nullptr;
1332 imgIRequest* newBorderImage = StyleBorder()->GetBorderImageRequest();
1333 // FIXME (Bug 759996): The following is no longer true.
1334 // For border-images, we can't be as conservative (we need to set the
1335 // new loaders if there has been any change) since the CalcDifference
1336 // call depended on the result of GetComputedBorder() and that result
1337 // depends on whether the image has loaded, start the image load now
1338 // so that we'll get notified when it completes loading and can do a
1339 // restyle. Otherwise, the image might finish loading from the
1340 // network before we start listening to its notifications, and then
1341 // we'll never know that it's finished loading. Likewise, we want to
1342 // do this for freshly-created frames to prevent a similar race if the
1343 // image loads between reflow (which can depend on whether the image
1344 // is loaded) and paint. We also don't really care about any callers who try
1345 // to paint borders with a different style, because they won't have the
1346 // correct size for the border either.
1347 if (oldBorderImage != newBorderImage) {
1348 // stop and restart the image loading/notification
1349 if (oldBorderImage && HasImageRequest()) {
1350 RemoveProperty(CachedBorderImageDataProperty());
1351 loader->DisassociateRequestFromFrame(oldBorderImage, this);
1353 if (newBorderImage) {
1354 loader->AssociateRequestToFrame(newBorderImage, this);
1358 auto GetShapeImageRequest = [](const ComputedStyle* aStyle) -> imgIRequest* {
1359 if (!aStyle) {
1360 return nullptr;
1362 auto& shape = aStyle->StyleDisplay()->mShapeOutside;
1363 if (!shape.IsImage()) {
1364 return nullptr;
1366 return shape.AsImage().GetImageRequest();
1369 imgIRequest* oldShapeImage = GetShapeImageRequest(aOldComputedStyle);
1370 imgIRequest* newShapeImage = GetShapeImageRequest(Style());
1371 if (oldShapeImage != newShapeImage) {
1372 if (oldShapeImage && HasImageRequest()) {
1373 loader->DisassociateRequestFromFrame(oldShapeImage, this);
1375 if (newShapeImage) {
1376 loader->AssociateRequestToFrame(
1377 newShapeImage, this,
1378 ImageLoader::Flags::
1379 RequiresReflowOnFirstFrameCompleteAndLoadEventBlocking);
1383 // SVGObserverUtils::GetEffectProperties() asserts that we only invoke it with
1384 // the first continuation so we need to check that in advance.
1385 const bool isNonTextFirstContinuation = isNonText && !GetPrevContinuation();
1386 if (isNonTextFirstContinuation) {
1387 // Kick off loading of external SVG resources referenced from properties if
1388 // any. This currently includes filter, clip-path, and mask.
1389 SVGObserverUtils::InitiateResourceDocLoads(this);
1392 // If the page contains markup that overrides text direction, and
1393 // does not contain any characters that would activate the Unicode
1394 // bidi algorithm, we need to call |SetBidiEnabled| on the pres
1395 // context before reflow starts. See bug 115921.
1396 if (StyleVisibility()->mDirection == StyleDirection::Rtl) {
1397 PresContext()->SetBidiEnabled();
1400 // The following part is for caching offset-path:path(). We cache the
1401 // flatten gfx path, so we don't have to rebuild and re-flattern it at
1402 // each cycle if we have animations on offset-* with a fixed offset-path.
1403 const StyleOffsetPath* oldPath =
1404 aOldComputedStyle ? &aOldComputedStyle->StyleDisplay()->mOffsetPath
1405 : nullptr;
1406 const StyleOffsetPath& newPath = StyleDisplay()->mOffsetPath;
1407 if (!oldPath || *oldPath != newPath) {
1408 if (newPath.IsPath()) {
1409 // Here we only need to build a valid path for motion path, so
1410 // using the default values of stroke-width, stoke-linecap, and fill-rule
1411 // is fine for now because what we want is to get the point and its normal
1412 // vector along the path, instead of rendering it.
1413 RefPtr<gfx::PathBuilder> builder =
1414 gfxPlatform::GetPlatform()
1415 ->ScreenReferenceDrawTarget()
1416 ->CreatePathBuilder(gfx::FillRule::FILL_WINDING);
1417 RefPtr<gfx::Path> path =
1418 MotionPathUtils::BuildPath(newPath.AsPath(), builder);
1419 if (path) {
1420 // The newPath could be path('') (i.e. empty path), so its gfx path
1421 // could be nullptr, and so we only set property for a non-empty path.
1422 SetProperty(nsIFrame::OffsetPathCache(), path.forget().take());
1423 } else {
1424 // May have an old cached path, so we have to delete it.
1425 RemoveProperty(nsIFrame::OffsetPathCache());
1427 } else if (oldPath) {
1428 RemoveProperty(nsIFrame::OffsetPathCache());
1432 RemoveStateBits(NS_FRAME_SIMPLE_EVENT_REGIONS | NS_FRAME_SIMPLE_DISPLAYLIST);
1434 mMayHaveRoundedCorners = true;
1437 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
1438 void nsIFrame::AssertNewStyleIsSane(ComputedStyle& aNewStyle) {
1439 MOZ_DIAGNOSTIC_ASSERT(
1440 aNewStyle.GetPseudoType() == mComputedStyle->GetPseudoType() ||
1441 // ::first-line continuations are weird, this should probably be fixed via
1442 // bug 1465474.
1443 (mComputedStyle->GetPseudoType() == PseudoStyleType::firstLine &&
1444 aNewStyle.GetPseudoType() == PseudoStyleType::mozLineFrame) ||
1445 // ::first-letter continuations are broken, in particular floating ones,
1446 // see bug 1490281. The construction code tries to fix this up after the
1447 // fact, then restyling undoes it...
1448 (mComputedStyle->GetPseudoType() == PseudoStyleType::mozText &&
1449 aNewStyle.GetPseudoType() == PseudoStyleType::firstLetterContinuation) ||
1450 (mComputedStyle->GetPseudoType() ==
1451 PseudoStyleType::firstLetterContinuation &&
1452 aNewStyle.GetPseudoType() == PseudoStyleType::mozText));
1454 #endif
1456 void nsIFrame::ReparentFrameViewTo(nsViewManager* aViewManager,
1457 nsView* aNewParentView,
1458 nsView* aOldParentView) {
1459 if (HasView()) {
1460 #ifdef MOZ_XUL
1461 if (IsMenuPopupFrame()) {
1462 // This view must be parented by the root view, don't reparent it.
1463 return;
1465 #endif
1466 nsView* view = GetView();
1467 // Verify that the current parent view is what we think it is
1468 // nsView* parentView;
1469 // NS_ASSERTION(parentView == aOldParentView, "unexpected parent view");
1471 aViewManager->RemoveChild(view);
1473 // The view will remember the Z-order and other attributes that have been
1474 // set on it.
1475 nsView* insertBefore =
1476 nsLayoutUtils::FindSiblingViewFor(aNewParentView, this);
1477 aViewManager->InsertChild(aNewParentView, view, insertBefore,
1478 insertBefore != nullptr);
1479 } else if (HasAnyStateBits(NS_FRAME_HAS_CHILD_WITH_VIEW)) {
1480 for (const auto& childList : ChildLists()) {
1481 // Iterate the child frames, and check each child frame to see if it has
1482 // a view
1483 for (nsIFrame* child : childList.mList) {
1484 child->ReparentFrameViewTo(aViewManager, aNewParentView,
1485 aOldParentView);
1491 void nsIFrame::SyncFrameViewProperties(nsView* aView) {
1492 if (!aView) {
1493 aView = GetView();
1494 if (!aView) {
1495 return;
1499 nsViewManager* vm = aView->GetViewManager();
1501 // Make sure visibility is correct. This only affects nsSubDocumentFrame.
1502 if (!SupportsVisibilityHidden()) {
1503 // See if the view should be hidden or visible
1504 ComputedStyle* sc = Style();
1505 vm->SetViewVisibility(aView, sc->StyleVisibility()->IsVisible()
1506 ? nsViewVisibility_kShow
1507 : nsViewVisibility_kHide);
1510 const auto zIndex = ZIndex();
1511 const bool autoZIndex = !zIndex;
1512 vm->SetViewZIndex(aView, autoZIndex, zIndex.valueOr(0));
1515 void nsIFrame::CreateView() {
1516 MOZ_ASSERT(!HasView());
1518 nsView* parentView = GetParent()->GetClosestView();
1519 MOZ_ASSERT(parentView, "no parent with view");
1521 nsViewManager* viewManager = parentView->GetViewManager();
1522 MOZ_ASSERT(viewManager, "null view manager");
1524 nsView* view = viewManager->CreateView(GetRect(), parentView);
1525 SyncFrameViewProperties(view);
1527 nsView* insertBefore = nsLayoutUtils::FindSiblingViewFor(parentView, this);
1528 // we insert this view 'above' the insertBefore view, unless insertBefore is
1529 // null, in which case we want to call with aAbove == false to insert at the
1530 // beginning in document order
1531 viewManager->InsertChild(parentView, view, insertBefore,
1532 insertBefore != nullptr);
1534 // REVIEW: Don't create a widget for fixed-pos elements anymore.
1535 // ComputeRepaintRegionForCopy will calculate the right area to repaint
1536 // when we scroll.
1537 // Reparent views on any child frames (or their descendants) to this
1538 // view. We can just call ReparentFrameViewTo on this frame because
1539 // we know this frame has no view, so it will crawl the children. Also,
1540 // we know that any descendants with views must have 'parentView' as their
1541 // parent view.
1542 ReparentFrameViewTo(viewManager, view, parentView);
1544 // Remember our view
1545 SetView(view);
1547 NS_FRAME_LOG(NS_FRAME_TRACE_CALLS,
1548 ("nsIFrame::CreateView: frame=%p view=%p", this, view));
1551 // MSVC fails with link error "one or more multiply defined symbols found",
1552 // gcc fails with "hidden symbol `nsIFrame::kPrincipalList' isn't defined"
1553 // etc if they are not defined.
1554 #ifndef _MSC_VER
1555 // static nsIFrame constants; initialized in the header file.
1556 const nsIFrame::ChildListID nsIFrame::kPrincipalList;
1557 const nsIFrame::ChildListID nsIFrame::kAbsoluteList;
1558 const nsIFrame::ChildListID nsIFrame::kBulletList;
1559 const nsIFrame::ChildListID nsIFrame::kCaptionList;
1560 const nsIFrame::ChildListID nsIFrame::kColGroupList;
1561 const nsIFrame::ChildListID nsIFrame::kExcessOverflowContainersList;
1562 const nsIFrame::ChildListID nsIFrame::kFixedList;
1563 const nsIFrame::ChildListID nsIFrame::kFloatList;
1564 const nsIFrame::ChildListID nsIFrame::kOverflowContainersList;
1565 const nsIFrame::ChildListID nsIFrame::kOverflowList;
1566 const nsIFrame::ChildListID nsIFrame::kOverflowOutOfFlowList;
1567 const nsIFrame::ChildListID nsIFrame::kPopupList;
1568 const nsIFrame::ChildListID nsIFrame::kPushedFloatsList;
1569 const nsIFrame::ChildListID nsIFrame::kSelectPopupList;
1570 const nsIFrame::ChildListID nsIFrame::kNoReflowPrincipalList;
1571 #endif
1573 /* virtual */
1574 nsMargin nsIFrame::GetUsedMargin() const {
1575 nsMargin margin(0, 0, 0, 0);
1576 if (((mState & NS_FRAME_FIRST_REFLOW) && !(mState & NS_FRAME_IN_REFLOW)) ||
1577 SVGUtils::IsInSVGTextSubtree(this))
1578 return margin;
1580 nsMargin* m = GetProperty(UsedMarginProperty());
1581 if (m) {
1582 margin = *m;
1583 } else {
1584 if (!StyleMargin()->GetMargin(margin)) {
1585 // If we get here, our caller probably shouldn't be calling us...
1586 NS_ERROR(
1587 "Returning bogus 0-sized margin, because this margin "
1588 "depends on layout & isn't cached!");
1591 return margin;
1594 /* virtual */
1595 nsMargin nsIFrame::GetUsedBorder() const {
1596 nsMargin border(0, 0, 0, 0);
1597 if (((mState & NS_FRAME_FIRST_REFLOW) && !(mState & NS_FRAME_IN_REFLOW)) ||
1598 SVGUtils::IsInSVGTextSubtree(this))
1599 return border;
1601 // Theme methods don't use const-ness.
1602 nsIFrame* mutable_this = const_cast<nsIFrame*>(this);
1604 const nsStyleDisplay* disp = StyleDisplay();
1605 if (mutable_this->IsThemed(disp)) {
1606 nsPresContext* pc = PresContext();
1607 LayoutDeviceIntMargin widgetBorder = pc->Theme()->GetWidgetBorder(
1608 pc->DeviceContext(), mutable_this, disp->EffectiveAppearance());
1609 border =
1610 LayoutDevicePixel::ToAppUnits(widgetBorder, pc->AppUnitsPerDevPixel());
1611 return border;
1614 nsMargin* b = GetProperty(UsedBorderProperty());
1615 if (b) {
1616 border = *b;
1617 } else {
1618 border = StyleBorder()->GetComputedBorder();
1620 return border;
1623 /* virtual */
1624 nsMargin nsIFrame::GetUsedPadding() const {
1625 nsMargin padding(0, 0, 0, 0);
1626 if (((mState & NS_FRAME_FIRST_REFLOW) && !(mState & NS_FRAME_IN_REFLOW)) ||
1627 SVGUtils::IsInSVGTextSubtree(this))
1628 return padding;
1630 // Theme methods don't use const-ness.
1631 nsIFrame* mutable_this = const_cast<nsIFrame*>(this);
1633 const nsStyleDisplay* disp = StyleDisplay();
1634 if (mutable_this->IsThemed(disp)) {
1635 nsPresContext* pc = PresContext();
1636 LayoutDeviceIntMargin widgetPadding;
1637 if (pc->Theme()->GetWidgetPadding(pc->DeviceContext(), mutable_this,
1638 disp->EffectiveAppearance(),
1639 &widgetPadding)) {
1640 return LayoutDevicePixel::ToAppUnits(widgetPadding,
1641 pc->AppUnitsPerDevPixel());
1645 nsMargin* p = GetProperty(UsedPaddingProperty());
1646 if (p) {
1647 padding = *p;
1648 } else {
1649 if (!StylePadding()->GetPadding(padding)) {
1650 // If we get here, our caller probably shouldn't be calling us...
1651 NS_ERROR(
1652 "Returning bogus 0-sized padding, because this padding "
1653 "depends on layout & isn't cached!");
1656 return padding;
1659 nsIFrame::Sides nsIFrame::GetSkipSides() const {
1660 if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak ==
1661 StyleBoxDecorationBreak::Clone) &&
1662 !HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
1663 return Sides();
1666 // Convert the logical skip sides to physical sides using the frame's
1667 // writing mode
1668 WritingMode writingMode = GetWritingMode();
1669 LogicalSides logicalSkip = GetLogicalSkipSides();
1670 Sides skip;
1672 if (logicalSkip.BStart()) {
1673 if (writingMode.IsVertical()) {
1674 skip |= writingMode.IsVerticalLR() ? SideBits::eLeft : SideBits::eRight;
1675 } else {
1676 skip |= SideBits::eTop;
1680 if (logicalSkip.BEnd()) {
1681 if (writingMode.IsVertical()) {
1682 skip |= writingMode.IsVerticalLR() ? SideBits::eRight : SideBits::eLeft;
1683 } else {
1684 skip |= SideBits::eBottom;
1688 if (logicalSkip.IStart()) {
1689 if (writingMode.IsVertical()) {
1690 skip |= SideBits::eTop;
1691 } else {
1692 skip |= writingMode.IsBidiLTR() ? SideBits::eLeft : SideBits::eRight;
1696 if (logicalSkip.IEnd()) {
1697 if (writingMode.IsVertical()) {
1698 skip |= SideBits::eBottom;
1699 } else {
1700 skip |= writingMode.IsBidiLTR() ? SideBits::eRight : SideBits::eLeft;
1703 return skip;
1706 nsRect nsIFrame::GetPaddingRectRelativeToSelf() const {
1707 nsMargin border = GetUsedBorder().ApplySkipSides(GetSkipSides());
1708 nsRect r(0, 0, mRect.width, mRect.height);
1709 r.Deflate(border);
1710 return r;
1713 nsRect nsIFrame::GetPaddingRect() const {
1714 return GetPaddingRectRelativeToSelf() + GetPosition();
1717 WritingMode nsIFrame::WritingModeForLine(WritingMode aSelfWM,
1718 nsIFrame* aSubFrame) const {
1719 MOZ_ASSERT(aSelfWM == GetWritingMode());
1720 WritingMode writingMode = aSelfWM;
1722 if (StyleTextReset()->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_PLAINTEXT) {
1723 nsBidiLevel frameLevel = nsBidiPresUtils::GetFrameBaseLevel(aSubFrame);
1724 writingMode.SetDirectionFromBidiLevel(frameLevel);
1727 return writingMode;
1730 nsRect nsIFrame::GetMarginRectRelativeToSelf() const {
1731 nsMargin m = GetUsedMargin().ApplySkipSides(GetSkipSides());
1732 nsRect r(0, 0, mRect.width, mRect.height);
1733 r.Inflate(m);
1734 return r;
1737 bool nsIFrame::IsTransformed(const nsStyleDisplay* aStyleDisplay) const {
1738 return IsCSSTransformed(aStyleDisplay) || IsSVGTransformed();
1741 bool nsIFrame::IsCSSTransformed(const nsStyleDisplay* aStyleDisplay) const {
1742 MOZ_ASSERT(aStyleDisplay == StyleDisplay());
1743 return ((mState & NS_FRAME_MAY_BE_TRANSFORMED) &&
1744 (aStyleDisplay->HasTransform(this) || HasAnimationOfTransform()));
1747 bool nsIFrame::HasAnimationOfTransform() const {
1748 return IsPrimaryFrame() &&
1749 nsLayoutUtils::HasAnimationOfTransformAndMotionPath(this) &&
1750 IsFrameOfType(eSupportsCSSTransforms);
1753 bool nsIFrame::ChildrenHavePerspective(
1754 const nsStyleDisplay* aStyleDisplay) const {
1755 MOZ_ASSERT(aStyleDisplay == StyleDisplay());
1756 return aStyleDisplay->HasPerspective(this);
1759 bool nsIFrame::HasAnimationOfOpacity(EffectSet* aEffectSet) const {
1760 return ((nsLayoutUtils::IsPrimaryStyleFrame(this) ||
1761 nsLayoutUtils::FirstContinuationOrIBSplitSibling(this)
1762 ->IsPrimaryFrame()) &&
1763 nsLayoutUtils::HasAnimationOfPropertySet(
1764 this, nsCSSPropertyIDSet::OpacityProperties(), aEffectSet));
1767 bool nsIFrame::HasOpacityInternal(float aThreshold,
1768 const nsStyleDisplay* aStyleDisplay,
1769 const nsStyleEffects* aStyleEffects,
1770 EffectSet* aEffectSet) const {
1771 MOZ_ASSERT(0.0 <= aThreshold && aThreshold <= 1.0, "Invalid argument");
1772 if (aStyleEffects->mOpacity < aThreshold ||
1773 (aStyleDisplay->mWillChange.bits & StyleWillChangeBits::OPACITY)) {
1774 return true;
1777 if (!mMayHaveOpacityAnimation) {
1778 return false;
1781 return HasAnimationOfOpacity(aEffectSet);
1784 bool nsIFrame::IsSVGTransformed(gfx::Matrix* aOwnTransforms,
1785 gfx::Matrix* aFromParentTransforms) const {
1786 return false;
1789 bool nsIFrame::Extend3DContext(const nsStyleDisplay* aStyleDisplay,
1790 const nsStyleEffects* aStyleEffects,
1791 mozilla::EffectSet* aEffectSetForOpacity) const {
1792 if (!(mState & NS_FRAME_MAY_BE_TRANSFORMED)) {
1793 return false;
1795 const nsStyleDisplay* disp = StyleDisplayWithOptionalParam(aStyleDisplay);
1796 if (disp->mTransformStyle != StyleTransformStyle::Preserve3d ||
1797 !IsFrameOfType(nsIFrame::eSupportsCSSTransforms)) {
1798 return false;
1801 // If we're all scroll frame, then all descendants will be clipped, so we
1802 // can't preserve 3d.
1803 if (IsScrollFrame()) {
1804 return false;
1807 const nsStyleEffects* effects = StyleEffectsWithOptionalParam(aStyleEffects);
1808 if (HasOpacity(disp, effects, aEffectSetForOpacity)) {
1809 return false;
1812 return ShouldApplyOverflowClipping(disp) == PhysicalAxes::None &&
1813 !GetClipPropClipRect(disp, effects, GetSize()) &&
1814 !SVGIntegrationUtils::UsingEffectsForFrame(this) &&
1815 !effects->HasMixBlendMode() &&
1816 disp->mIsolation != StyleIsolation::Isolate;
1819 bool nsIFrame::Combines3DTransformWithAncestors(
1820 const nsStyleDisplay* aStyleDisplay) const {
1821 MOZ_ASSERT(aStyleDisplay == StyleDisplay());
1822 nsIFrame* parent = GetClosestFlattenedTreeAncestorPrimaryFrame();
1823 if (!parent || !parent->Extend3DContext()) {
1824 return false;
1826 return IsCSSTransformed(aStyleDisplay) || BackfaceIsHidden(aStyleDisplay);
1829 bool nsIFrame::In3DContextAndBackfaceIsHidden() const {
1830 // While both tests fail most of the time, test BackfaceIsHidden()
1831 // first since it's likely to fail faster.
1832 const nsStyleDisplay* disp = StyleDisplay();
1833 return BackfaceIsHidden(disp) && Combines3DTransformWithAncestors(disp);
1836 bool nsIFrame::HasPerspective(const nsStyleDisplay* aStyleDisplay) const {
1837 MOZ_ASSERT(aStyleDisplay == StyleDisplay());
1838 if (!IsTransformed(aStyleDisplay)) {
1839 return false;
1841 nsIFrame* containingBlock =
1842 GetContainingBlock(SKIP_SCROLLED_FRAME, aStyleDisplay);
1843 if (!containingBlock) {
1844 return false;
1846 return containingBlock->ChildrenHavePerspective();
1849 nsRect nsIFrame::GetContentRectRelativeToSelf() const {
1850 nsMargin bp = GetUsedBorderAndPadding().ApplySkipSides(GetSkipSides());
1851 nsRect r(0, 0, mRect.width, mRect.height);
1852 r.Deflate(bp);
1853 return r;
1856 nsRect nsIFrame::GetContentRect() const {
1857 return GetContentRectRelativeToSelf() + GetPosition();
1860 bool nsIFrame::ComputeBorderRadii(const BorderRadius& aBorderRadius,
1861 const nsSize& aFrameSize,
1862 const nsSize& aBorderArea, Sides aSkipSides,
1863 nscoord aRadii[8]) {
1864 // Percentages are relative to whichever side they're on.
1865 for (const auto i : mozilla::AllPhysicalHalfCorners()) {
1866 const LengthPercentage& c = aBorderRadius.Get(i);
1867 nscoord axis = HalfCornerIsX(i) ? aFrameSize.width : aFrameSize.height;
1868 aRadii[i] = std::max(0, c.Resolve(axis));
1871 if (aSkipSides.Top()) {
1872 aRadii[eCornerTopLeftX] = 0;
1873 aRadii[eCornerTopLeftY] = 0;
1874 aRadii[eCornerTopRightX] = 0;
1875 aRadii[eCornerTopRightY] = 0;
1878 if (aSkipSides.Right()) {
1879 aRadii[eCornerTopRightX] = 0;
1880 aRadii[eCornerTopRightY] = 0;
1881 aRadii[eCornerBottomRightX] = 0;
1882 aRadii[eCornerBottomRightY] = 0;
1885 if (aSkipSides.Bottom()) {
1886 aRadii[eCornerBottomRightX] = 0;
1887 aRadii[eCornerBottomRightY] = 0;
1888 aRadii[eCornerBottomLeftX] = 0;
1889 aRadii[eCornerBottomLeftY] = 0;
1892 if (aSkipSides.Left()) {
1893 aRadii[eCornerBottomLeftX] = 0;
1894 aRadii[eCornerBottomLeftY] = 0;
1895 aRadii[eCornerTopLeftX] = 0;
1896 aRadii[eCornerTopLeftY] = 0;
1899 // css3-background specifies this algorithm for reducing
1900 // corner radii when they are too big.
1901 bool haveRadius = false;
1902 double ratio = 1.0f;
1903 for (const auto side : mozilla::AllPhysicalSides()) {
1904 uint32_t hc1 = SideToHalfCorner(side, false, true);
1905 uint32_t hc2 = SideToHalfCorner(side, true, true);
1906 nscoord length =
1907 SideIsVertical(side) ? aBorderArea.height : aBorderArea.width;
1908 nscoord sum = aRadii[hc1] + aRadii[hc2];
1909 if (sum) {
1910 haveRadius = true;
1911 // avoid floating point division in the normal case
1912 if (length < sum) {
1913 ratio = std::min(ratio, double(length) / sum);
1917 if (ratio < 1.0) {
1918 for (const auto corner : mozilla::AllPhysicalHalfCorners()) {
1919 aRadii[corner] *= ratio;
1923 return haveRadius;
1926 /* static */
1927 void nsIFrame::InsetBorderRadii(nscoord aRadii[8], const nsMargin& aOffsets) {
1928 for (const auto side : mozilla::AllPhysicalSides()) {
1929 nscoord offset = aOffsets.Side(side);
1930 uint32_t hc1 = SideToHalfCorner(side, false, false);
1931 uint32_t hc2 = SideToHalfCorner(side, true, false);
1932 aRadii[hc1] = std::max(0, aRadii[hc1] - offset);
1933 aRadii[hc2] = std::max(0, aRadii[hc2] - offset);
1937 /* static */
1938 void nsIFrame::OutsetBorderRadii(nscoord aRadii[8], const nsMargin& aOffsets) {
1939 auto AdjustOffset = [](const uint32_t aRadius, const nscoord aOffset) {
1940 // Implement the cubic formula to adjust offset when aOffset > 0 and
1941 // aRadius / aOffset < 1.
1942 // https://drafts.csswg.org/css-shapes/#valdef-shape-box-margin-box
1943 if (aOffset > 0) {
1944 const double ratio = aRadius / double(aOffset);
1945 if (ratio < 1.0) {
1946 return nscoord(aOffset * (1.0 + std::pow(ratio - 1, 3)));
1949 return aOffset;
1952 for (const auto side : mozilla::AllPhysicalSides()) {
1953 const nscoord offset = aOffsets.Side(side);
1954 const uint32_t hc1 = SideToHalfCorner(side, false, false);
1955 const uint32_t hc2 = SideToHalfCorner(side, true, false);
1956 if (aRadii[hc1] > 0) {
1957 const nscoord offset1 = AdjustOffset(aRadii[hc1], offset);
1958 aRadii[hc1] = std::max(0, aRadii[hc1] + offset1);
1960 if (aRadii[hc2] > 0) {
1961 const nscoord offset2 = AdjustOffset(aRadii[hc2], offset);
1962 aRadii[hc2] = std::max(0, aRadii[hc2] + offset2);
1967 static inline bool RadiiAreDefinitelyZero(const BorderRadius& aBorderRadius) {
1968 for (const auto corner : mozilla::AllPhysicalHalfCorners()) {
1969 if (!aBorderRadius.Get(corner).IsDefinitelyZero()) {
1970 return false;
1973 return true;
1976 /* virtual */
1977 bool nsIFrame::GetBorderRadii(const nsSize& aFrameSize,
1978 const nsSize& aBorderArea, Sides aSkipSides,
1979 nscoord aRadii[8]) const {
1980 if (!mMayHaveRoundedCorners) {
1981 memset(aRadii, 0, sizeof(nscoord) * 8);
1982 return false;
1985 if (IsThemed()) {
1986 // When we're themed, the native theme code draws the border and
1987 // background, and therefore it doesn't make sense to tell other
1988 // code that's interested in border-radius that we have any radii.
1990 // In an ideal world, we might have a way for the them to tell us an
1991 // border radius, but since we don't, we're better off assuming
1992 // zero.
1993 for (const auto corner : mozilla::AllPhysicalHalfCorners()) {
1994 aRadii[corner] = 0;
1996 return false;
1999 const auto& radii = StyleBorder()->mBorderRadius;
2000 const bool hasRadii =
2001 ComputeBorderRadii(radii, aFrameSize, aBorderArea, aSkipSides, aRadii);
2002 if (!hasRadii) {
2003 // TODO(emilio): Maybe we can just remove this bit and do the
2004 // IsDefinitelyZero check unconditionally. That should still avoid most of
2005 // the work, though maybe not the cache miss of going through the style and
2006 // the border struct.
2007 const_cast<nsIFrame*>(this)->mMayHaveRoundedCorners =
2008 !RadiiAreDefinitelyZero(radii);
2010 return hasRadii;
2013 bool nsIFrame::GetBorderRadii(nscoord aRadii[8]) const {
2014 nsSize sz = GetSize();
2015 return GetBorderRadii(sz, sz, GetSkipSides(), aRadii);
2018 bool nsIFrame::GetMarginBoxBorderRadii(nscoord aRadii[8]) const {
2019 return GetBoxBorderRadii(aRadii, GetUsedMargin(), true);
2022 bool nsIFrame::GetPaddingBoxBorderRadii(nscoord aRadii[8]) const {
2023 return GetBoxBorderRadii(aRadii, GetUsedBorder(), false);
2026 bool nsIFrame::GetContentBoxBorderRadii(nscoord aRadii[8]) const {
2027 return GetBoxBorderRadii(aRadii, GetUsedBorderAndPadding(), false);
2030 bool nsIFrame::GetBoxBorderRadii(nscoord aRadii[8], nsMargin aOffset,
2031 bool aIsOutset) const {
2032 if (!GetBorderRadii(aRadii)) return false;
2033 if (aIsOutset) {
2034 OutsetBorderRadii(aRadii, aOffset);
2035 } else {
2036 InsetBorderRadii(aRadii, aOffset);
2038 for (const auto corner : mozilla::AllPhysicalHalfCorners()) {
2039 if (aRadii[corner]) return true;
2041 return false;
2044 bool nsIFrame::GetShapeBoxBorderRadii(nscoord aRadii[8]) const {
2045 using Tag = StyleShapeOutside::Tag;
2046 auto& shapeOutside = StyleDisplay()->mShapeOutside;
2047 auto box = StyleShapeBox::MarginBox;
2048 switch (shapeOutside.tag) {
2049 case Tag::Image:
2050 case Tag::None:
2051 return false;
2052 case Tag::Box:
2053 box = shapeOutside.AsBox();
2054 break;
2055 case Tag::Shape:
2056 box = shapeOutside.AsShape()._1;
2057 break;
2060 switch (box) {
2061 case StyleShapeBox::ContentBox:
2062 return GetContentBoxBorderRadii(aRadii);
2063 case StyleShapeBox::PaddingBox:
2064 return GetPaddingBoxBorderRadii(aRadii);
2065 case StyleShapeBox::BorderBox:
2066 return GetBorderRadii(aRadii);
2067 case StyleShapeBox::MarginBox:
2068 return GetMarginBoxBorderRadii(aRadii);
2069 default:
2070 MOZ_ASSERT_UNREACHABLE("Unexpected box value");
2071 return false;
2075 ComputedStyle* nsIFrame::GetAdditionalComputedStyle(int32_t aIndex) const {
2076 MOZ_ASSERT(aIndex >= 0, "invalid index number");
2077 return nullptr;
2080 void nsIFrame::SetAdditionalComputedStyle(int32_t aIndex,
2081 ComputedStyle* aComputedStyle) {
2082 MOZ_ASSERT(aIndex >= 0, "invalid index number");
2085 nscoord nsIFrame::GetLogicalBaseline(WritingMode aWritingMode) const {
2086 NS_ASSERTION(!IsSubtreeDirty(), "frame must not be dirty");
2087 // Baseline for inverted line content is the top (block-start) margin edge,
2088 // as the frame is in effect "flipped" for alignment purposes.
2089 if (aWritingMode.IsLineInverted()) {
2090 return -GetLogicalUsedMargin(aWritingMode).BStart(aWritingMode);
2092 // Otherwise, the bottom margin edge, per CSS2.1's definition of the
2093 // 'baseline' value of 'vertical-align'.
2094 return BSize(aWritingMode) +
2095 GetLogicalUsedMargin(aWritingMode).BEnd(aWritingMode);
2098 const nsFrameList& nsIFrame::GetChildList(ChildListID aListID) const {
2099 if (IsAbsoluteContainer() && aListID == GetAbsoluteListID()) {
2100 return GetAbsoluteContainingBlock()->GetChildList();
2101 } else {
2102 return nsFrameList::EmptyList();
2106 void nsIFrame::GetChildLists(nsTArray<ChildList>* aLists) const {
2107 if (IsAbsoluteContainer()) {
2108 nsFrameList absoluteList = GetAbsoluteContainingBlock()->GetChildList();
2109 absoluteList.AppendIfNonempty(aLists, GetAbsoluteListID());
2113 AutoTArray<nsIFrame::ChildList, 4> nsIFrame::CrossDocChildLists() {
2114 AutoTArray<ChildList, 4> childLists;
2115 nsSubDocumentFrame* subdocumentFrame = do_QueryFrame(this);
2116 if (subdocumentFrame) {
2117 // Descend into the subdocument
2118 nsIFrame* root = subdocumentFrame->GetSubdocumentRootFrame();
2119 if (root) {
2120 childLists.EmplaceBack(
2121 nsFrameList(root, nsLayoutUtils::GetLastSibling(root)),
2122 nsIFrame::kPrincipalList);
2126 GetChildLists(&childLists);
2127 return childLists;
2130 Visibility nsIFrame::GetVisibility() const {
2131 if (!HasAnyStateBits(NS_FRAME_VISIBILITY_IS_TRACKED)) {
2132 return Visibility::Untracked;
2135 bool isSet = false;
2136 uint32_t visibleCount = GetProperty(VisibilityStateProperty(), &isSet);
2138 MOZ_ASSERT(isSet,
2139 "Should have a VisibilityStateProperty value "
2140 "if NS_FRAME_VISIBILITY_IS_TRACKED is set");
2142 return visibleCount > 0 ? Visibility::ApproximatelyVisible
2143 : Visibility::ApproximatelyNonVisible;
2146 void nsIFrame::UpdateVisibilitySynchronously() {
2147 mozilla::PresShell* presShell = PresShell();
2148 if (!presShell) {
2149 return;
2152 if (presShell->AssumeAllFramesVisible()) {
2153 presShell->EnsureFrameInApproximatelyVisibleList(this);
2154 return;
2157 bool visible = StyleVisibility()->IsVisible();
2158 nsIFrame* f = GetParent();
2159 nsRect rect = GetRectRelativeToSelf();
2160 nsIFrame* rectFrame = this;
2161 while (f && visible) {
2162 nsIScrollableFrame* sf = do_QueryFrame(f);
2163 if (sf) {
2164 nsRect transformedRect =
2165 nsLayoutUtils::TransformFrameRectToAncestor(rectFrame, rect, f);
2166 if (!sf->IsRectNearlyVisible(transformedRect)) {
2167 visible = false;
2168 break;
2171 // In this code we're trying to synchronously update *approximate*
2172 // visibility. (In the future we may update precise visibility here as
2173 // well, which is why the method name does not contain 'approximate'.) The
2174 // IsRectNearlyVisible() check above tells us that the rect we're checking
2175 // is approximately visible within the scrollframe, but we still need to
2176 // ensure that, even if it was scrolled into view, it'd be visible when we
2177 // consider the rest of the document. To do that, we move transformedRect
2178 // to be contained in the scrollport as best we can (it might not fit) to
2179 // pretend that it was scrolled into view.
2180 rect = transformedRect.MoveInsideAndClamp(sf->GetScrollPortRect());
2181 rectFrame = f;
2183 nsIFrame* parent = f->GetParent();
2184 if (!parent) {
2185 parent = nsLayoutUtils::GetCrossDocParentFrame(f);
2186 if (parent && parent->PresContext()->IsChrome()) {
2187 break;
2190 f = parent;
2193 if (visible) {
2194 presShell->EnsureFrameInApproximatelyVisibleList(this);
2195 } else {
2196 presShell->RemoveFrameFromApproximatelyVisibleList(this);
2200 void nsIFrame::EnableVisibilityTracking() {
2201 if (HasAnyStateBits(NS_FRAME_VISIBILITY_IS_TRACKED)) {
2202 return; // Nothing to do.
2205 MOZ_ASSERT(!HasProperty(VisibilityStateProperty()),
2206 "Shouldn't have a VisibilityStateProperty value "
2207 "if NS_FRAME_VISIBILITY_IS_TRACKED is not set");
2209 // Add the state bit so we know to track visibility for this frame, and
2210 // initialize the frame property.
2211 AddStateBits(NS_FRAME_VISIBILITY_IS_TRACKED);
2212 SetProperty(VisibilityStateProperty(), 0);
2214 mozilla::PresShell* presShell = PresShell();
2215 if (!presShell) {
2216 return;
2219 // Schedule a visibility update. This method will virtually always be called
2220 // when layout has changed anyway, so it's very unlikely that any additional
2221 // visibility updates will be triggered by this, but this way we guarantee
2222 // that if this frame is currently visible we'll eventually find out.
2223 presShell->ScheduleApproximateFrameVisibilityUpdateSoon();
2226 void nsIFrame::DisableVisibilityTracking() {
2227 if (!HasAnyStateBits(NS_FRAME_VISIBILITY_IS_TRACKED)) {
2228 return; // Nothing to do.
2231 bool isSet = false;
2232 uint32_t visibleCount = TakeProperty(VisibilityStateProperty(), &isSet);
2234 MOZ_ASSERT(isSet,
2235 "Should have a VisibilityStateProperty value "
2236 "if NS_FRAME_VISIBILITY_IS_TRACKED is set");
2238 RemoveStateBits(NS_FRAME_VISIBILITY_IS_TRACKED);
2240 if (visibleCount == 0) {
2241 return; // We were nonvisible.
2244 // We were visible, so send an OnVisibilityChange() notification.
2245 OnVisibilityChange(Visibility::ApproximatelyNonVisible);
2248 void nsIFrame::DecApproximateVisibleCount(
2249 const Maybe<OnNonvisible>& aNonvisibleAction
2250 /* = Nothing() */) {
2251 MOZ_ASSERT(HasAnyStateBits(NS_FRAME_VISIBILITY_IS_TRACKED));
2253 bool isSet = false;
2254 uint32_t visibleCount = GetProperty(VisibilityStateProperty(), &isSet);
2256 MOZ_ASSERT(isSet,
2257 "Should have a VisibilityStateProperty value "
2258 "if NS_FRAME_VISIBILITY_IS_TRACKED is set");
2259 MOZ_ASSERT(visibleCount > 0,
2260 "Frame is already nonvisible and we're "
2261 "decrementing its visible count?");
2263 visibleCount--;
2264 SetProperty(VisibilityStateProperty(), visibleCount);
2265 if (visibleCount > 0) {
2266 return;
2269 // We just became nonvisible, so send an OnVisibilityChange() notification.
2270 OnVisibilityChange(Visibility::ApproximatelyNonVisible, aNonvisibleAction);
2273 void nsIFrame::IncApproximateVisibleCount() {
2274 MOZ_ASSERT(HasAnyStateBits(NS_FRAME_VISIBILITY_IS_TRACKED));
2276 bool isSet = false;
2277 uint32_t visibleCount = GetProperty(VisibilityStateProperty(), &isSet);
2279 MOZ_ASSERT(isSet,
2280 "Should have a VisibilityStateProperty value "
2281 "if NS_FRAME_VISIBILITY_IS_TRACKED is set");
2283 visibleCount++;
2284 SetProperty(VisibilityStateProperty(), visibleCount);
2285 if (visibleCount > 1) {
2286 return;
2289 // We just became visible, so send an OnVisibilityChange() notification.
2290 OnVisibilityChange(Visibility::ApproximatelyVisible);
2293 void nsIFrame::OnVisibilityChange(Visibility aNewVisibility,
2294 const Maybe<OnNonvisible>& aNonvisibleAction
2295 /* = Nothing() */) {
2296 // XXX(seth): In bug 1218990 we'll implement visibility tracking for CSS
2297 // images here.
2300 static nsIFrame* GetActiveSelectionFrame(nsPresContext* aPresContext,
2301 nsIFrame* aFrame) {
2302 nsIContent* capturingContent = PresShell::GetCapturingContent();
2303 if (capturingContent) {
2304 nsIFrame* activeFrame = aPresContext->GetPrimaryFrameFor(capturingContent);
2305 return activeFrame ? activeFrame : aFrame;
2308 return aFrame;
2311 int16_t nsIFrame::DetermineDisplaySelection() {
2312 int16_t selType = nsISelectionController::SELECTION_OFF;
2314 nsCOMPtr<nsISelectionController> selCon;
2315 nsresult result =
2316 GetSelectionController(PresContext(), getter_AddRefs(selCon));
2317 if (NS_SUCCEEDED(result) && selCon) {
2318 result = selCon->GetDisplaySelection(&selType);
2319 if (NS_SUCCEEDED(result) &&
2320 (selType != nsISelectionController::SELECTION_OFF)) {
2321 // Check whether style allows selection.
2322 if (!IsSelectable(nullptr)) {
2323 selType = nsISelectionController::SELECTION_OFF;
2327 return selType;
2330 static Element* FindElementAncestorForMozSelection(nsIContent* aContent) {
2331 NS_ENSURE_TRUE(aContent, nullptr);
2332 while (aContent && aContent->IsInNativeAnonymousSubtree()) {
2333 aContent = aContent->GetClosestNativeAnonymousSubtreeRootParent();
2335 NS_ASSERTION(aContent, "aContent isn't in non-anonymous tree?");
2336 return aContent ? aContent->GetAsElementOrParentElement() : nullptr;
2339 already_AddRefed<ComputedStyle> nsIFrame::ComputeSelectionStyle(
2340 int16_t aSelectionStatus) const {
2341 // Just bail out if not a selection-status that ::selection applies to.
2342 if (aSelectionStatus != nsISelectionController::SELECTION_ON &&
2343 aSelectionStatus != nsISelectionController::SELECTION_DISABLED) {
2344 return nullptr;
2346 // When in high-contrast mode, the style system ends up ignoring the color
2347 // declarations, which means that the ::selection style becomes the inherited
2348 // color, and default background. That's no good.
2349 if (!PresContext()->PrefSheetPrefs().mUseDocumentColors) {
2350 return nullptr;
2352 Element* element = FindElementAncestorForMozSelection(GetContent());
2353 if (!element) {
2354 return nullptr;
2356 return PresContext()->StyleSet()->ProbePseudoElementStyle(
2357 *element, PseudoStyleType::selection, Style());
2360 template <typename SizeOrMaxSize>
2361 static inline bool IsIntrinsicKeyword(const SizeOrMaxSize& aSize) {
2362 if (!aSize.IsExtremumLength()) {
2363 return false;
2366 // All of the keywords except for '-moz-available' depend on intrinsic sizes.
2367 return aSize.AsExtremumLength() != StyleExtremumLength::MozAvailable;
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 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()->mColorAdjust == StyleColorAdjust::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 result = nsDisplayBackgroundImage::AppendBackgroundItemsToTop(
2596 aBuilder, this,
2597 GetRectRelativeToSelf() + aBuilder->ToReferenceFrame(this),
2598 aLists.BorderBackground());
2601 if (result == AppendedBackgroundType::None) {
2602 aBuilder->BuildCompositorHitTestInfoIfNeeded(this,
2603 aLists.BorderBackground());
2606 return result == AppendedBackgroundType::ThemedBackground;
2609 void nsIFrame::DisplayBorderBackgroundOutline(nsDisplayListBuilder* aBuilder,
2610 const nsDisplayListSet& aLists,
2611 bool aForceBackground) {
2612 // The visibility check belongs here since child elements have the
2613 // opportunity to override the visibility property and display even if
2614 // their parent is hidden.
2615 if (!IsVisibleForPainting()) {
2616 return;
2619 DisplayOutsetBoxShadowUnconditional(aBuilder, aLists.BorderBackground());
2621 bool bgIsThemed =
2622 DisplayBackgroundUnconditional(aBuilder, aLists, aForceBackground);
2624 DisplayInsetBoxShadowUnconditional(aBuilder, aLists.BorderBackground());
2626 // If there's a themed background, we should not create a border item.
2627 // It won't be rendered.
2628 // Don't paint borders for tables here, since they paint them in a different
2629 // order.
2630 if (!bgIsThemed && StyleBorder()->HasBorder() && !IsTableFrame()) {
2631 aLists.BorderBackground()->AppendNewToTop<nsDisplayBorder>(aBuilder, this);
2634 DisplayOutlineUnconditional(aBuilder, aLists);
2637 inline static bool IsSVGContentWithCSSClip(const nsIFrame* aFrame) {
2638 // The CSS spec says that the 'clip' property only applies to absolutely
2639 // positioned elements, whereas the SVG spec says that it applies to SVG
2640 // elements regardless of the value of the 'position' property. Here we obey
2641 // the CSS spec for outer-<svg> (since that's what we generally do), but
2642 // obey the SVG spec for other SVG elements to which 'clip' applies.
2643 return aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT) &&
2644 aFrame->GetContent()->IsAnyOfSVGElements(nsGkAtoms::svg,
2645 nsGkAtoms::foreignObject);
2648 bool nsIFrame::FormsBackdropRoot(const nsStyleDisplay* aStyleDisplay,
2649 const nsStyleEffects* aStyleEffects,
2650 const nsStyleSVGReset* aStyleSVGReset) {
2651 // Check if this is a root frame.
2652 if (!GetParent()) {
2653 return true;
2656 // Check for filter effects.
2657 if (aStyleEffects->HasFilters() || aStyleEffects->HasBackdropFilters() ||
2658 aStyleEffects->HasMixBlendMode()) {
2659 return true;
2662 // Check for opacity.
2663 if (HasOpacity(aStyleDisplay, aStyleEffects)) {
2664 return true;
2667 // Check for mask or clip path.
2668 if (aStyleSVGReset->HasMask() || aStyleSVGReset->HasClipPath()) {
2669 return true;
2672 // TODO(cbrewster): Check will-change attributes
2674 return false;
2677 Maybe<nsRect> nsIFrame::GetClipPropClipRect(const nsStyleDisplay* aDisp,
2678 const nsStyleEffects* aEffects,
2679 const nsSize& aSize) const {
2680 if (aEffects->mClip.IsAuto() ||
2681 !(aDisp->IsAbsolutelyPositioned(this) || IsSVGContentWithCSSClip(this))) {
2682 return Nothing();
2685 auto& clipRect = aEffects->mClip.AsRect();
2686 nsRect rect = clipRect.ToLayoutRect();
2687 if (MOZ_LIKELY(StyleBorder()->mBoxDecorationBreak ==
2688 StyleBoxDecorationBreak::Slice)) {
2689 // The clip applies to the joined boxes so it's relative the first
2690 // continuation.
2691 nscoord y = 0;
2692 for (nsIFrame* f = GetPrevContinuation(); f; f = f->GetPrevContinuation()) {
2693 y += f->GetRect().height;
2695 rect.MoveBy(nsPoint(0, -y));
2698 if (clipRect.right.IsAuto()) {
2699 rect.width = aSize.width - rect.x;
2701 if (clipRect.bottom.IsAuto()) {
2702 rect.height = aSize.height - rect.y;
2704 return Some(rect);
2708 * If the CSS 'overflow' property applies to this frame, and is not
2709 * handled by constructing a dedicated nsHTML/XULScrollFrame, set up clipping
2710 * for that overflow in aBuilder->ClipState() to clip all containing-block
2711 * descendants.
2713 static void ApplyOverflowClipping(
2714 nsDisplayListBuilder* aBuilder, const nsIFrame* aFrame,
2715 nsIFrame::PhysicalAxes aClipAxes,
2716 DisplayListClipState::AutoClipMultiple& aClipState) {
2717 // Only 'clip' is handled here (and 'hidden' for table frames, and any
2718 // non-'visible' value for blocks in a paginated context).
2719 // We allow 'clip' to apply to any kind of frame. This is required by
2720 // comboboxes which make their display text (an inline frame) have clipping.
2721 MOZ_ASSERT(aClipAxes != nsIFrame::PhysicalAxes::None);
2722 MOZ_ASSERT(aFrame->ShouldApplyOverflowClipping(aFrame->StyleDisplay()) ==
2723 aClipAxes);
2725 nsRect clipRect;
2726 bool haveRadii = false;
2727 nscoord radii[8];
2728 auto* disp = aFrame->StyleDisplay();
2729 // Only deflate the padding if we clip to the content-box in that axis.
2730 auto wm = aFrame->GetWritingMode();
2731 bool cbH = (wm.IsVertical() ? disp->mOverflowClipBoxBlock
2732 : disp->mOverflowClipBoxInline) ==
2733 StyleOverflowClipBox::ContentBox;
2734 bool cbV = (wm.IsVertical() ? disp->mOverflowClipBoxInline
2735 : disp->mOverflowClipBoxBlock) ==
2736 StyleOverflowClipBox::ContentBox;
2737 nsMargin bp = aFrame->GetUsedPadding();
2738 if (!cbH) {
2739 bp.left = bp.right = nscoord(0);
2741 if (!cbV) {
2742 bp.top = bp.bottom = nscoord(0);
2745 bp += aFrame->GetUsedBorder();
2746 bp.ApplySkipSides(aFrame->GetSkipSides());
2747 nsRect rect(nsPoint(0, 0), aFrame->GetSize());
2748 rect.Deflate(bp);
2749 if (MOZ_UNLIKELY(!(aClipAxes & nsIFrame::PhysicalAxes::Horizontal))) {
2750 // NOTE(mats) We shouldn't be clipping at all in this dimension really,
2751 // but clipping in just one axis isn't supported by our GFX APIs so we
2752 // clip to our visual overflow rect instead.
2753 nsRect o = aFrame->InkOverflowRect();
2754 rect.x = o.x;
2755 rect.width = o.width;
2757 if (MOZ_UNLIKELY(!(aClipAxes & nsIFrame::PhysicalAxes::Vertical))) {
2758 // See the note above.
2759 nsRect o = aFrame->InkOverflowRect();
2760 rect.y = o.y;
2761 rect.height = o.height;
2763 clipRect = rect + aBuilder->ToReferenceFrame(aFrame);
2764 haveRadii = aFrame->GetBoxBorderRadii(radii, bp, false);
2765 aClipState.ClipContainingBlockDescendantsExtra(clipRect,
2766 haveRadii ? radii : nullptr);
2769 #ifdef DEBUG
2770 static void PaintDebugBorder(nsIFrame* aFrame, DrawTarget* aDrawTarget,
2771 const nsRect& aDirtyRect, nsPoint aPt) {
2772 nsRect r(aPt, aFrame->GetSize());
2773 int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
2774 sRGBColor blueOrRed(aFrame->HasView() ? sRGBColor(0.f, 0.f, 1.f, 1.f)
2775 : sRGBColor(1.f, 0.f, 0.f, 1.f));
2776 aDrawTarget->StrokeRect(NSRectToRect(r, appUnitsPerDevPixel),
2777 ColorPattern(ToDeviceColor(blueOrRed)));
2780 static void PaintEventTargetBorder(nsIFrame* aFrame, DrawTarget* aDrawTarget,
2781 const nsRect& aDirtyRect, nsPoint aPt) {
2782 nsRect r(aPt, aFrame->GetSize());
2783 int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
2784 ColorPattern purple(ToDeviceColor(sRGBColor(.5f, 0.f, .5f, 1.f)));
2785 aDrawTarget->StrokeRect(NSRectToRect(r, appUnitsPerDevPixel), purple);
2788 static void DisplayDebugBorders(nsDisplayListBuilder* aBuilder,
2789 nsIFrame* aFrame,
2790 const nsDisplayListSet& aLists) {
2791 // Draw a border around the child
2792 // REVIEW: From nsContainerFrame::PaintChild
2793 if (nsIFrame::GetShowFrameBorders() && !aFrame->GetRect().IsEmpty()) {
2794 aLists.Outlines()->AppendNewToTop<nsDisplayGeneric>(
2795 aBuilder, aFrame, PaintDebugBorder, "DebugBorder",
2796 DisplayItemType::TYPE_DEBUG_BORDER);
2798 // Draw a border around the current event target
2799 if (nsIFrame::GetShowEventTargetFrameBorder() &&
2800 aFrame->PresShell()->GetDrawEventTargetFrame() == aFrame) {
2801 aLists.Outlines()->AppendNewToTop<nsDisplayGeneric>(
2802 aBuilder, aFrame, PaintEventTargetBorder, "EventTargetBorder",
2803 DisplayItemType::TYPE_EVENT_TARGET_BORDER);
2806 #endif
2808 static bool IsScrollFrameActive(nsDisplayListBuilder* aBuilder,
2809 nsIScrollableFrame* aScrollableFrame) {
2810 return aScrollableFrame && aScrollableFrame->IsScrollingActive(aBuilder);
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()->Count() == 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 gfxMatrix cssToDevMatrix = SVGUtils::GetCSSPxToDevPxMatrix(aMaskedFrame);
2982 nsPoint toReferenceFrame;
2983 aBuilder->FindReferenceFrameFor(aMaskedFrame, &toReferenceFrame);
2985 Maybe<gfxRect> combinedClip;
2986 if (maskUsage.shouldApplyBasicShapeOrPath) {
2987 Maybe<Rect> result =
2988 CSSClipPathInstance::GetBoundingRectForBasicShapeOrPathClip(
2989 aMaskedFrame, svgReset->mClipPath);
2990 if (result) {
2991 combinedClip = Some(ThebesRect(*result));
2993 } else if (maskUsage.shouldApplyClipPath) {
2994 gfxRect result = SVGUtils::GetBBox(
2995 aMaskedFrame,
2996 SVGUtils::eBBoxIncludeClipped | SVGUtils::eBBoxIncludeFill |
2997 SVGUtils::eBBoxIncludeMarkers | SVGUtils::eBBoxIncludeStroke |
2998 SVGUtils::eDoNotClipToBBoxOfContentInsideClipPath);
2999 combinedClip = Some(cssToDevMatrix.TransformBounds(result));
3000 } else {
3001 // The code for this case is adapted from ComputeMaskGeometry().
3003 nsRect borderArea(toReferenceFrame, aMaskedFrame->GetSize());
3004 borderArea -= offsetToUserSpace;
3006 // Use an infinite dirty rect to pass into nsCSSRendering::
3007 // GetImageLayerClip() because we don't have an actual dirty rect to
3008 // pass in. This is fine because the only time GetImageLayerClip() will
3009 // not intersect the incoming dirty rect with something is in the "NoClip"
3010 // case, and we handle that specially.
3011 nsRect dirtyRect(nscoord_MIN / 2, nscoord_MIN / 2, nscoord_MAX,
3012 nscoord_MAX);
3014 nsIFrame* firstFrame =
3015 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aMaskedFrame);
3016 nsTArray<SVGMaskFrame*> maskFrames;
3017 // XXX check return value?
3018 SVGObserverUtils::GetAndObserveMasks(firstFrame, &maskFrames);
3020 for (uint32_t i = 0; i < maskFrames.Length(); ++i) {
3021 gfxRect clipArea;
3022 if (maskFrames[i]) {
3023 clipArea = maskFrames[i]->GetMaskArea(aMaskedFrame);
3024 clipArea = cssToDevMatrix.TransformBounds(clipArea);
3025 } else {
3026 const auto& layer = svgReset->mMask.mLayers[i];
3027 if (layer.mClip == StyleGeometryBox::NoClip) {
3028 return Nothing();
3031 nsCSSRendering::ImageLayerClipState clipState;
3032 nsCSSRendering::GetImageLayerClip(
3033 layer, aMaskedFrame, *aMaskedFrame->StyleBorder(), borderArea,
3034 dirtyRect, false /* aWillPaintBorder */, devPixelRatio, &clipState);
3035 clipArea = clipState.mDirtyRectInDevPx;
3037 combinedClip = UnionMaybeRects(combinedClip, Some(clipArea));
3040 if (combinedClip) {
3041 if (combinedClip->IsEmpty()) {
3042 // *clipForMask might be empty if all mask references are not resolvable
3043 // or the size of them are empty. We still need to create a transparent
3044 // mask before bug 1276834 fixed, so don't clip ctx by an empty rectangle
3045 // for for now.
3046 return Nothing();
3049 // Convert to user space.
3050 *combinedClip += devPixelOffsetToUserSpace;
3052 // Round the clip out. In FrameLayerBuilder we round clips to nearest
3053 // pixels, and if we have a really thin clip here, that can cause the
3054 // clip to become empty if we didn't round out here.
3055 // The rounding happens in coordinates that are relative to the reference
3056 // frame, which matches what FrameLayerBuilder does.
3057 combinedClip->RoundOut();
3059 // Convert to app units.
3060 nsRect result =
3061 nsLayoutUtils::RoundGfxRectToAppRect(*combinedClip, devPixelRatio);
3063 // The resulting clip is relative to the reference frame, but the caller
3064 // expects it to be relative to the masked frame, so adjust it.
3065 result -= toReferenceFrame;
3066 return Some(result);
3068 return Nothing();
3071 struct AutoCheckBuilder {
3072 explicit AutoCheckBuilder(nsDisplayListBuilder* aBuilder)
3073 : mBuilder(aBuilder) {
3074 aBuilder->Check();
3077 ~AutoCheckBuilder() { mBuilder->Check(); }
3079 nsDisplayListBuilder* mBuilder;
3083 * Helper class to track container creation. Stores the first tracked container.
3084 * Used to find the innermost container for hit test information, and to notify
3085 * callers whether a container item was created or not.
3087 struct ContainerTracker {
3088 void TrackContainer(nsDisplayItem* aContainer) {
3089 if (!aContainer) {
3090 return;
3093 if (!mContainer) {
3094 mContainer = aContainer;
3097 mCreatedContainer = true;
3100 void ResetCreatedContainer() { mCreatedContainer = false; }
3102 nsDisplayItem* mContainer = nullptr;
3103 bool mCreatedContainer = false;
3106 void nsIFrame::BuildDisplayListForStackingContext(
3107 nsDisplayListBuilder* aBuilder, nsDisplayList* aList,
3108 bool* aCreatedContainerItem) {
3109 AutoCheckBuilder check(aBuilder);
3110 if (HasAnyStateBits(NS_FRAME_TOO_DEEP_IN_FRAME_TREE)) return;
3112 // Replaced elements have their visibility handled here, because
3113 // they're visually atomic
3114 if (IsFrameOfType(eReplaced) && !IsVisibleForPainting()) return;
3116 const nsStyleDisplay* disp = StyleDisplay();
3117 const nsStyleEffects* effects = StyleEffects();
3118 EffectSet* effectSetForOpacity = EffectSet::GetEffectSetForFrame(
3119 this, nsCSSPropertyIDSet::OpacityProperties());
3120 // We can stop right away if this is a zero-opacity stacking context and
3121 // we're painting, and we're not animating opacity.
3122 bool needHitTestInfo =
3123 aBuilder->BuildCompositorHitTestInfo() &&
3124 StyleUI()->GetEffectivePointerEvents(this) != StylePointerEvents::None;
3125 bool opacityItemForEventsOnly = false;
3126 if (effects->mOpacity == 0.0 && aBuilder->IsForPainting() &&
3127 !(disp->mWillChange.bits & StyleWillChangeBits::OPACITY) &&
3128 !nsLayoutUtils::HasAnimationOfPropertySet(
3129 this, nsCSSPropertyIDSet::OpacityProperties(), effectSetForOpacity)) {
3130 if (needHitTestInfo) {
3131 opacityItemForEventsOnly = true;
3132 } else {
3133 return;
3137 if (aBuilder->IsForPainting() && disp->mWillChange.bits) {
3138 aBuilder->AddToWillChangeBudget(this, GetSize());
3141 // For preserves3d, use the dirty rect already installed on the
3142 // builder, since aDirtyRect maybe distorted for transforms along
3143 // the chain.
3144 nsRect visibleRect = aBuilder->GetVisibleRect();
3145 nsRect dirtyRect = aBuilder->GetDirtyRect();
3147 // We build an opacity item if it's not going to be drawn by SVG content.
3148 // We could in principle skip creating an nsDisplayOpacity item if
3149 // nsDisplayOpacity::NeedsActiveLayer returns false and usingSVGEffects is
3150 // true (the nsDisplayFilter/nsDisplayMasksAndClipPaths could handle the
3151 // opacity). Since SVG has perf issues where we sometimes spend a lot of
3152 // time creating display list items that might be helpful. We'd need to
3153 // restore our mechanism to do that (changed in bug 1482403), and we'd
3154 // need to invalidate the frame if the value that would be return from
3155 // NeedsActiveLayer was to change, which we don't currently do.
3156 const bool useOpacity =
3157 HasVisualOpacity(disp, effects, effectSetForOpacity) &&
3158 !SVGUtils::CanOptimizeOpacity(this);
3160 const bool isTransformed = IsTransformed(disp);
3161 const bool hasPerspective = isTransformed && HasPerspective(disp);
3162 const bool extend3DContext =
3163 Extend3DContext(disp, effects, effectSetForOpacity);
3164 const bool combines3DTransformWithAncestors =
3165 (extend3DContext || isTransformed) &&
3166 Combines3DTransformWithAncestors(disp);
3168 Maybe<nsDisplayListBuilder::AutoPreserves3DContext> autoPreserves3DContext;
3169 if (extend3DContext && !combines3DTransformWithAncestors) {
3170 // Start a new preserves3d context to keep informations on
3171 // nsDisplayListBuilder.
3172 autoPreserves3DContext.emplace(aBuilder);
3173 // Save dirty rect on the builder to avoid being distorted for
3174 // multiple transforms along the chain.
3175 aBuilder->SavePreserves3DRect();
3177 // We rebuild everything within preserve-3d and don't try
3178 // to retain, so override the dirty rect now.
3179 if (aBuilder->IsRetainingDisplayList()) {
3180 dirtyRect = visibleRect;
3181 aBuilder->SetDisablePartialUpdates(true);
3185 const bool useBlendMode = effects->mMixBlendMode != StyleBlend::Normal;
3186 if (useBlendMode) {
3187 aBuilder->SetContainsBlendMode(true);
3190 // reset blend mode so we can keep track if this stacking context needs have
3191 // a nsDisplayBlendContainer. Set the blend mode back when the routine exits
3192 // so we keep track if the parent stacking context needs a container too.
3193 AutoSaveRestoreContainsBlendMode autoRestoreBlendMode(*aBuilder);
3194 aBuilder->SetContainsBlendMode(false);
3196 bool usingBackdropFilter =
3197 effects->HasBackdropFilters() &&
3198 nsDisplayBackdropFilters::CanCreateWebRenderCommands(aBuilder, this);
3200 if (usingBackdropFilter) {
3201 aBuilder->SetContainsBackdropFilter(true);
3204 AutoSaveRestoreContainsBackdropFilter autoRestoreBackdropFilter(*aBuilder);
3205 aBuilder->SetContainsBackdropFilter(false);
3207 nsRect visibleRectOutsideTransform = visibleRect;
3208 nsDisplayTransform::PrerenderInfo prerenderInfo;
3209 bool inTransform = aBuilder->IsInTransform();
3210 if (isTransformed) {
3211 prerenderInfo = nsDisplayTransform::ShouldPrerenderTransformedContent(
3212 aBuilder, this, &visibleRect);
3214 switch (prerenderInfo.mDecision) {
3215 case nsDisplayTransform::PrerenderDecision::Full:
3216 case nsDisplayTransform::PrerenderDecision::Partial:
3217 dirtyRect = visibleRect;
3218 break;
3219 case nsDisplayTransform::PrerenderDecision::No: {
3220 // If we didn't prerender an animated frame in a preserve-3d context,
3221 // then we want disable async animations for the rest of the preserve-3d
3222 // (especially ancestors).
3223 if ((extend3DContext || combines3DTransformWithAncestors) &&
3224 prerenderInfo.mHasAnimations) {
3225 aBuilder->SavePreserves3DAllowAsyncAnimation(false);
3228 const nsRect overflow = InkOverflowRectRelativeToSelf();
3229 if (overflow.IsEmpty() && !extend3DContext) {
3230 return;
3233 // If we're in preserve-3d then grab the dirty rect that was given to
3234 // the root and transform using the combined transform.
3235 if (combines3DTransformWithAncestors) {
3236 visibleRect = dirtyRect = aBuilder->GetPreserves3DRect();
3239 nsRect untransformedDirtyRect;
3240 if (nsDisplayTransform::UntransformRect(dirtyRect, overflow, this,
3241 &untransformedDirtyRect)) {
3242 dirtyRect = untransformedDirtyRect;
3243 nsDisplayTransform::UntransformRect(visibleRect, overflow, this,
3244 &visibleRect);
3245 } else {
3246 // This should only happen if the transform is singular, in which case
3247 // nothing is visible anyway
3248 dirtyRect.SetEmpty();
3249 visibleRect.SetEmpty();
3253 inTransform = true;
3254 } else if (IsFixedPosContainingBlock()) {
3255 // Restict the building area to the overflow rect for these frames, since
3256 // RetainedDisplayListBuilder uses it to know if the size of the stacking
3257 // context changed.
3258 visibleRect.IntersectRect(visibleRect, InkOverflowRect());
3259 dirtyRect.IntersectRect(dirtyRect, InkOverflowRect());
3262 bool hasOverrideDirtyRect = false;
3263 // If we're doing a partial build, we're not invalid and we're capable
3264 // of having an override building rect (stacking context and fixed pos
3265 // containing block), then we should assume we have one.
3266 // Either we have an explicit one, or nothing in our subtree changed and
3267 // we have an implicit empty rect.
3268 if (aBuilder->IsPartialUpdate() && !aBuilder->InInvalidSubtree() &&
3269 !IsFrameModified() && IsFixedPosContainingBlock()) {
3270 dirtyRect = nsRect();
3271 if (HasOverrideDirtyRegion()) {
3272 nsDisplayListBuilder::DisplayListBuildingData* data =
3273 GetProperty(nsDisplayListBuilder::DisplayListBuildingRect());
3274 if (data) {
3275 dirtyRect = data->mDirtyRect.Intersect(visibleRect);
3276 hasOverrideDirtyRect = true;
3281 bool usingFilter = effects->HasFilters();
3282 bool usingMask = SVGIntegrationUtils::UsingMaskOrClipPathForFrame(this);
3283 bool usingSVGEffects = usingFilter || usingMask;
3285 nsRect visibleRectOutsideSVGEffects = visibleRect;
3286 nsDisplayList hoistedScrollInfoItemsStorage;
3287 if (usingSVGEffects) {
3288 dirtyRect =
3289 SVGIntegrationUtils::GetRequiredSourceForInvalidArea(this, dirtyRect);
3290 visibleRect =
3291 SVGIntegrationUtils::GetRequiredSourceForInvalidArea(this, visibleRect);
3292 aBuilder->EnterSVGEffectsContents(this, &hoistedScrollInfoItemsStorage);
3295 bool useStickyPosition =
3296 disp->mPosition == StylePositionProperty::Sticky &&
3297 IsScrollFrameActive(
3298 aBuilder,
3299 nsLayoutUtils::GetNearestScrollableFrame(
3300 GetParent(), nsLayoutUtils::SCROLLABLE_SAME_DOC |
3301 nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN));
3302 bool useFixedPosition =
3303 disp->mPosition == StylePositionProperty::Fixed &&
3304 (DisplayPortUtils::IsFixedPosFrameInDisplayPort(this) ||
3305 BuilderHasScrolledClip(aBuilder));
3307 nsDisplayListBuilder::AutoBuildingDisplayList buildingDisplayList(
3308 aBuilder, this, visibleRect, dirtyRect, isTransformed);
3310 UpdateCurrentHitTestInfo(aBuilder, this);
3312 // Depending on the effects that are applied to this frame, we can create
3313 // multiple container display items and wrap them around our contents.
3314 // This enum lists all the potential container display items, in the order
3315 // outside to inside.
3316 enum class ContainerItemType : uint8_t {
3317 None = 0,
3318 OwnLayerIfNeeded,
3319 BlendMode,
3320 FixedPosition,
3321 OwnLayerForTransformWithRoundedClip,
3322 Perspective,
3323 Transform,
3324 SeparatorTransforms,
3325 Opacity,
3326 Filter,
3327 BlendContainer
3330 nsDisplayListBuilder::AutoContainerASRTracker contASRTracker(aBuilder);
3332 auto cssClip = GetClipPropClipRect(disp, effects, GetSize());
3333 auto ApplyClipProp = [&](DisplayListClipState::AutoSaveRestore& aClipState) {
3334 if (!cssClip) {
3335 return;
3337 nsPoint offset = aBuilder->GetCurrentFrameOffsetToReferenceFrame();
3338 aBuilder->IntersectDirtyRect(*cssClip);
3339 aBuilder->IntersectVisibleRect(*cssClip);
3340 aClipState.ClipContentDescendants(*cssClip + offset);
3343 // The CSS clip property is effectively inside the transform, but outside the
3344 // filters. So if we're not transformed we can apply it just here for
3345 // simplicity, instead of on each of the places that handle clipCapturedBy.
3346 DisplayListClipState::AutoSaveRestore untransformedCssClip(aBuilder);
3347 if (!isTransformed) {
3348 ApplyClipProp(untransformedCssClip);
3351 // If there is a current clip, then depending on the container items we
3352 // create, different things can happen to it. Some container items simply
3353 // propagate the clip to their children and aren't clipped themselves.
3354 // But other container items, especially those that establish a different
3355 // geometry for their contents (e.g. transforms), capture the clip on
3356 // themselves and unset the clip for their contents. If we create more than
3357 // one of those container items, the clip will be captured on the outermost
3358 // one and the inner container items will be unclipped.
3359 ContainerItemType clipCapturedBy = ContainerItemType::None;
3360 if (useFixedPosition) {
3361 clipCapturedBy = ContainerItemType::FixedPosition;
3362 } else if (isTransformed) {
3363 const DisplayItemClipChain* currentClip =
3364 aBuilder->ClipState().GetCurrentCombinedClipChain(aBuilder);
3365 if ((hasPerspective || extend3DContext) &&
3366 (currentClip && currentClip->HasRoundedCorners())) {
3367 // If we're creating an nsDisplayTransform item that is going to combine
3368 // its transform with its children (preserve-3d or perspective), then we
3369 // can't have an intermediate surface. Mask layers force an intermediate
3370 // surface, so if we're going to need both then create a separate
3371 // wrapping layer for the mask.
3372 clipCapturedBy = ContainerItemType::OwnLayerForTransformWithRoundedClip;
3373 } else if (hasPerspective) {
3374 clipCapturedBy = ContainerItemType::Perspective;
3375 } else {
3376 clipCapturedBy = ContainerItemType::Transform;
3378 } else if (usingFilter) {
3379 clipCapturedBy = ContainerItemType::Filter;
3382 DisplayListClipState::AutoSaveRestore clipState(aBuilder);
3383 if (clipCapturedBy != ContainerItemType::None) {
3384 clipState.Clear();
3387 DisplayListClipState::AutoSaveRestore transformedCssClip(aBuilder);
3388 if (isTransformed) {
3389 // FIXME(emilio, bug 1525159): In the case we have a both a transform _and_
3390 // filters, this clips the input to the filters as well, which is not
3391 // correct (clipping by the `clip` property is supposed to happen after
3392 // applying the filter effects, per [1].
3394 // This is not a regression though, since we used to do that anyway before
3395 // bug 1514384, and even without the transform we get it wrong.
3397 // [1]: https://drafts.fxtf.org/css-masking/#placement
3398 ApplyClipProp(transformedCssClip);
3401 nsDisplayListCollection set(aBuilder);
3402 Maybe<nsRect> clipForMask;
3403 bool insertBackdropRoot;
3405 DisplayListClipState::AutoSaveRestore nestedClipState(aBuilder);
3406 nsDisplayListBuilder::AutoInTransformSetter inTransformSetter(aBuilder,
3407 inTransform);
3408 nsDisplayListBuilder::AutoEnterFilter filterASRSetter(aBuilder,
3409 usingFilter);
3410 nsDisplayListBuilder::AutoInEventsOnly inEventsSetter(
3411 aBuilder, opacityItemForEventsOnly);
3413 // If we have a mask, compute a clip to bound the masked content.
3414 // This is necessary in case the content moves with an ancestor
3415 // ASR of the mask.
3416 // Don't do this if we also have a filter, because then the clip
3417 // would be applied before the filter, violating
3418 // https://www.w3.org/TR/filter-effects-1/#placement.
3419 // Filters are a containing block for fixed and absolute descendants,
3420 // so the masked content cannot move with an ancestor ASR.
3421 if (usingMask && !usingFilter) {
3422 clipForMask = ComputeClipForMaskItem(aBuilder, this);
3423 if (clipForMask) {
3424 aBuilder->IntersectDirtyRect(*clipForMask);
3425 aBuilder->IntersectVisibleRect(*clipForMask);
3426 nestedClipState.ClipContentDescendants(
3427 *clipForMask + aBuilder->GetCurrentFrameOffsetToReferenceFrame());
3431 // extend3DContext also guarantees that applyAbsPosClipping and
3432 // usingSVGEffects are false We only modify the preserve-3d rect if we are
3433 // the top of a preserve-3d heirarchy
3434 if (extend3DContext) {
3435 // Mark these first so MarkAbsoluteFramesForDisplayList knows if we are
3436 // going to be forced to descend into frames.
3437 aBuilder->MarkPreserve3DFramesForDisplayList(this);
3440 aBuilder->AdjustWindowDraggingRegion(this);
3442 MarkAbsoluteFramesForDisplayList(aBuilder);
3443 aBuilder->Check();
3444 BuildDisplayList(aBuilder, set);
3445 aBuilder->Check();
3446 aBuilder->DisplayCaret(this, set.Outlines());
3448 insertBackdropRoot = aBuilder->ContainsBackdropFilter() &&
3449 FormsBackdropRoot(disp, effects, StyleSVGReset());
3451 // Blend modes are a real pain for retained display lists. We build a blend
3452 // container item if the built list contains any blend mode items within
3453 // the current stacking context. This can change without an invalidation
3454 // to the stacking context frame, or the blend mode frame (e.g. by moving
3455 // an intermediate frame).
3456 // When we gain/remove a blend container item, we need to mark this frame
3457 // as invalid and have the full display list for merging to track
3458 // the change correctly.
3459 // It seems really hard to track this in advance, as the bookkeeping
3460 // required to note which stacking contexts have blend descendants
3461 // is complex and likely to be buggy.
3462 // Instead we're doing the sad thing, detecting it afterwards, and just
3463 // repeating display list building if it changed.
3464 // We have to repeat building for the entire display list (or at least
3465 // the outer stacking context), since we need to mark this frame as invalid
3466 // to remove any existing content that isn't wrapped in the blend container,
3467 // and then we need to build content infront/behind the blend container
3468 // to get correct positioning during merging.
3469 if ((insertBackdropRoot || aBuilder->ContainsBlendMode()) &&
3470 aBuilder->IsRetainingDisplayList()) {
3471 if (aBuilder->IsPartialUpdate()) {
3472 aBuilder->SetPartialBuildFailed(true);
3473 } else {
3474 aBuilder->SetDisablePartialUpdates(true);
3479 // If a child contains a backdrop filter, but this stacking context does not
3480 // form a backdrop root, we need to propogate up the tree until we find an
3481 // ancestor that does form a backdrop root.
3482 if (!insertBackdropRoot && aBuilder->ContainsBackdropFilter()) {
3483 autoRestoreBackdropFilter.DelegateUp(true);
3486 if (aBuilder->IsBackgroundOnly()) {
3487 set.BlockBorderBackgrounds()->DeleteAll(aBuilder);
3488 set.Floats()->DeleteAll(aBuilder);
3489 set.Content()->DeleteAll(aBuilder);
3490 set.PositionedDescendants()->DeleteAll(aBuilder);
3491 set.Outlines()->DeleteAll(aBuilder);
3494 if (hasOverrideDirtyRect &&
3495 StaticPrefs::layout_display_list_show_rebuild_area()) {
3496 nsDisplaySolidColor* color = MakeDisplayItem<nsDisplaySolidColor>(
3497 aBuilder, this,
3498 dirtyRect + aBuilder->GetCurrentFrameOffsetToReferenceFrame(),
3499 NS_RGBA(255, 0, 0, 64), false);
3500 if (color) {
3501 color->SetOverrideZIndex(INT32_MAX);
3502 set.PositionedDescendants()->AppendToTop(color);
3506 nsIContent* content = GetContent();
3507 if (!content) {
3508 content = PresContext()->Document()->GetRootElement();
3511 nsDisplayList resultList;
3512 set.SerializeWithCorrectZOrder(&resultList, content);
3514 #ifdef DEBUG
3515 DisplayDebugBorders(aBuilder, this, set);
3516 #endif
3518 // Get the ASR to use for the container items that we create here.
3519 const ActiveScrolledRoot* containerItemASR = contASRTracker.GetContainerASR();
3521 ContainerTracker ct;
3523 /* If adding both a nsDisplayBlendContainer and a nsDisplayBlendMode to the
3524 * same list, the nsDisplayBlendContainer should be added first. This only
3525 * happens when the element creating this stacking context has mix-blend-mode
3526 * and also contains a child which has mix-blend-mode.
3527 * The nsDisplayBlendContainer must be added to the list first, so it does not
3528 * isolate the containing element blending as well.
3530 if (aBuilder->ContainsBlendMode()) {
3531 DisplayListClipState::AutoSaveRestore blendContainerClipState(aBuilder);
3532 resultList.AppendToTop(nsDisplayBlendContainer::CreateForMixBlendMode(
3533 aBuilder, this, &resultList, containerItemASR));
3534 ct.TrackContainer(resultList.GetTop());
3537 if (insertBackdropRoot) {
3538 DisplayListClipState::AutoSaveRestore backdropRootContainerClipState(
3539 aBuilder);
3540 resultList.AppendNewToTop<nsDisplayBackdropRootContainer>(
3541 aBuilder, this, &resultList, containerItemASR);
3542 ct.TrackContainer(resultList.GetTop());
3545 if (usingBackdropFilter) {
3546 DisplayListClipState::AutoSaveRestore clipState(aBuilder);
3547 nsRect backdropRect =
3548 GetRectRelativeToSelf() + aBuilder->ToReferenceFrame(this);
3549 resultList.AppendNewToTop<nsDisplayBackdropFilters>(
3550 aBuilder, this, &resultList, backdropRect);
3551 ct.TrackContainer(resultList.GetTop());
3554 /* If there are any SVG effects, wrap the list up in an SVG effects item
3555 * (which also handles CSS group opacity). Note that we create an SVG effects
3556 * item even if resultList is empty, since a filter can produce graphical
3557 * output even if the element being filtered wouldn't otherwise do so.
3559 if (usingSVGEffects) {
3560 MOZ_ASSERT(usingFilter || usingMask,
3561 "Beside filter & mask/clip-path, what else effect do we have?");
3563 if (clipCapturedBy == ContainerItemType::Filter) {
3564 clipState.Restore();
3566 // Revert to the post-filter dirty rect.
3567 aBuilder->SetVisibleRect(visibleRectOutsideSVGEffects);
3569 // Skip all filter effects while generating glyph mask.
3570 if (usingFilter && !aBuilder->IsForGenerateGlyphMask()) {
3571 /* List now emptied, so add the new list to the top. */
3572 resultList.AppendNewToTop<nsDisplayFilters>(aBuilder, this, &resultList);
3573 ct.TrackContainer(resultList.GetTop());
3576 if (usingMask) {
3577 DisplayListClipState::AutoSaveRestore maskClipState(aBuilder);
3578 // The mask should move with aBuilder->CurrentActiveScrolledRoot(), so
3579 // that's the ASR we prefer to use for the mask item. However, we can
3580 // only do this if the mask if clipped with respect to that ASR, because
3581 // an item always needs to have finite bounds with respect to its ASR.
3582 // If we weren't able to compute a clip for the mask, we fall back to
3583 // using containerItemASR, which is the lowest common ancestor clip of
3584 // the mask's contents. That's not entirely correct, but it satisfies
3585 // the base requirement of the ASR system (that items have finite bounds
3586 // wrt. their ASR).
3587 const ActiveScrolledRoot* maskASR =
3588 clipForMask.isSome() ? aBuilder->CurrentActiveScrolledRoot()
3589 : containerItemASR;
3590 /* List now emptied, so add the new list to the top. */
3591 resultList.AppendNewToTop<nsDisplayMasksAndClipPaths>(
3592 aBuilder, this, &resultList, maskASR);
3593 ct.TrackContainer(resultList.GetTop());
3596 // TODO(miko): We could probably create a wraplist here and avoid creating
3597 // it later in |BuildDisplayListForChild()|.
3598 ct.ResetCreatedContainer();
3600 // Also add the hoisted scroll info items. We need those for APZ scrolling
3601 // because nsDisplayMasksAndClipPaths items can't build active layers.
3602 aBuilder->ExitSVGEffectsContents();
3603 resultList.AppendToTop(&hoistedScrollInfoItemsStorage);
3606 /* If the list is non-empty and there is CSS group opacity without SVG
3607 * effects, wrap it up in an opacity item.
3609 if (useOpacity) {
3610 // Don't clip nsDisplayOpacity items. We clip their descendants instead.
3611 // The clip we would set on an element with opacity would clip
3612 // all descendant content, but some should not be clipped.
3613 DisplayListClipState::AutoSaveRestore opacityClipState(aBuilder);
3614 const bool needsActiveOpacityLayer =
3615 nsDisplayOpacity::NeedsActiveLayer(aBuilder, this);
3617 resultList.AppendNewToTop<nsDisplayOpacity>(
3618 aBuilder, this, &resultList, containerItemASR, opacityItemForEventsOnly,
3619 needsActiveOpacityLayer);
3620 ct.TrackContainer(resultList.GetTop());
3623 /* If we're going to apply a transformation and don't have preserve-3d set,
3624 * wrap everything in an nsDisplayTransform. If there's nothing in the list,
3625 * don't add anything.
3627 * For the preserve-3d case we want to individually wrap every child in the
3628 * list with a separate nsDisplayTransform instead. When the child is already
3629 * an nsDisplayTransform, we can skip this step, as the computed transform
3630 * will already include our own.
3632 * We also traverse into sublists created by nsDisplayWrapList, so that we
3633 * find all the correct children.
3635 if (isTransformed && extend3DContext) {
3636 // Install dummy nsDisplayTransform as a leaf containing
3637 // descendants not participating this 3D rendering context.
3638 nsDisplayList nonparticipants;
3639 nsDisplayList participants;
3640 int index = 1;
3642 nsDisplayItem* separator = nullptr;
3644 while (nsDisplayItem* item = resultList.RemoveBottom()) {
3645 if (ItemParticipatesIn3DContext(this, item) &&
3646 !item->GetClip().HasClip()) {
3647 // The frame of this item participates the same 3D context.
3648 WrapSeparatorTransform(aBuilder, this, &nonparticipants, &participants,
3649 index++, &separator);
3651 participants.AppendToTop(item);
3652 } else {
3653 // The frame of the item doesn't participate the current
3654 // context, or has no transform.
3656 // For items participating but not transformed, they are add
3657 // to nonparticipants to get a separator layer for handling
3658 // clips, if there is, on an intermediate surface.
3659 // \see ContainerLayer::DefaultComputeEffectiveTransforms().
3660 nonparticipants.AppendToTop(item);
3663 WrapSeparatorTransform(aBuilder, this, &nonparticipants, &participants,
3664 index++, &separator);
3666 if (separator) {
3667 ct.TrackContainer(separator);
3670 resultList.AppendToTop(&participants);
3673 if (isTransformed) {
3674 transformedCssClip.Restore();
3675 if (clipCapturedBy == ContainerItemType::Transform) {
3676 // Restore clip state now so nsDisplayTransform is clipped properly.
3677 clipState.Restore();
3679 // Revert to the dirtyrect coming in from the parent, without our transform
3680 // taken into account.
3681 aBuilder->SetVisibleRect(visibleRectOutsideTransform);
3683 if (this != aBuilder->RootReferenceFrame()) {
3684 // Revert to the outer reference frame and offset because all display
3685 // items we create from now on are outside the transform.
3686 nsPoint toOuterReferenceFrame;
3687 const nsIFrame* outerReferenceFrame =
3688 aBuilder->FindReferenceFrameFor(GetParent(), &toOuterReferenceFrame);
3690 buildingDisplayList.SetReferenceFrameAndCurrentOffset(
3691 outerReferenceFrame, toOuterReferenceFrame);
3694 // We would like to block async animations for ancestors of ones not
3695 // prerendered in the preserve-3d tree. Now that we've finished processing
3696 // all descendants, update allowAsyncAnimation to take their prerender
3697 // state into account
3698 // FIXME: We don't block async animations for previous siblings because
3699 // their prerender decisions have been made. We may have to figure out a
3700 // better way to rollback their prerender decisions.
3701 // Alternatively we could not block animations for later siblings, and only
3702 // block them for ancestors of a blocked one.
3703 if ((extend3DContext || combines3DTransformWithAncestors) &&
3704 prerenderInfo.CanUseAsyncAnimations() &&
3705 !aBuilder->GetPreserves3DAllowAsyncAnimation()) {
3706 // aBuilder->GetPreserves3DAllowAsyncAnimation() means the inner or
3707 // previous silbing frames are allowed/disallowed for async animations.
3708 prerenderInfo.mDecision = nsDisplayTransform::PrerenderDecision::No;
3711 nsDisplayTransform* transformItem = MakeDisplayItem<nsDisplayTransform>(
3712 aBuilder, this, &resultList, visibleRect, prerenderInfo.mDecision);
3713 if (transformItem) {
3714 resultList.AppendToTop(transformItem);
3715 ct.TrackContainer(transformItem);
3718 if (hasPerspective) {
3719 if (clipCapturedBy == ContainerItemType::Perspective) {
3720 clipState.Restore();
3722 resultList.AppendNewToTop<nsDisplayPerspective>(aBuilder, this,
3723 &resultList);
3724 ct.TrackContainer(resultList.GetTop());
3728 if (clipCapturedBy ==
3729 ContainerItemType::OwnLayerForTransformWithRoundedClip) {
3730 clipState.Restore();
3731 resultList.AppendNewToTopWithIndex<nsDisplayOwnLayer>(
3732 aBuilder, this,
3733 /* aIndex = */ nsDisplayOwnLayer::OwnLayerForTransformWithRoundedClip,
3734 &resultList, aBuilder->CurrentActiveScrolledRoot(),
3735 nsDisplayOwnLayerFlags::None, ScrollbarData{},
3736 /* aForceActive = */ false, false);
3737 ct.TrackContainer(resultList.GetTop());
3740 /* If we have sticky positioning, wrap it in a sticky position item.
3742 if (useFixedPosition) {
3743 if (clipCapturedBy == ContainerItemType::FixedPosition) {
3744 clipState.Restore();
3746 // The ASR for the fixed item should be the ASR of our containing block,
3747 // which has been set as the builder's current ASR, unless this frame is
3748 // invisible and we hadn't saved display item data for it. In that case,
3749 // we need to take the containerItemASR since we might have fixed children.
3750 // For WebRender, we want to the know what |containerItemASR| is for the
3751 // case where the fixed-pos item is not a "real" fixed-pos item (e.g. it's
3752 // nested inside a scrolling transform), so we stash that on the display
3753 // item as well.
3754 const ActiveScrolledRoot* fixedASR = ActiveScrolledRoot::PickAncestor(
3755 containerItemASR, aBuilder->CurrentActiveScrolledRoot());
3756 resultList.AppendNewToTop<nsDisplayFixedPosition>(
3757 aBuilder, this, &resultList, fixedASR, containerItemASR);
3758 ct.TrackContainer(resultList.GetTop());
3759 } else if (useStickyPosition) {
3760 // For position:sticky, the clip needs to be applied both to the sticky
3761 // container item and to the contents. The container item needs the clip
3762 // because a scrolled clip needs to move independently from the sticky
3763 // contents, and the contents need the clip so that they have finite
3764 // clipped bounds with respect to the container item's ASR. The latter is
3765 // a little tricky in the case where the sticky item has both fixed and
3766 // non-fixed descendants, because that means that the sticky container
3767 // item's ASR is the ASR of the fixed descendant.
3768 // For WebRender display list building, though, we still want to know the
3769 // the ASR that the sticky container item would normally have, so we stash
3770 // that on the display item as the "container ASR" (i.e. the normal ASR of
3771 // the container item, excluding the special behaviour induced by fixed
3772 // descendants).
3773 const ActiveScrolledRoot* stickyASR = ActiveScrolledRoot::PickAncestor(
3774 containerItemASR, aBuilder->CurrentActiveScrolledRoot());
3775 resultList.AppendNewToTop<nsDisplayStickyPosition>(
3776 aBuilder, this, &resultList, stickyASR,
3777 aBuilder->CurrentActiveScrolledRoot(),
3778 clipState.IsClippedToDisplayPort());
3779 ct.TrackContainer(resultList.GetTop());
3781 // If the sticky element is inside a filter, annotate the scroll frame that
3782 // scrolls the filter as having out-of-flow content inside a filter (this
3783 // inhibits paint skipping).
3784 if (aBuilder->GetFilterASR() && aBuilder->GetFilterASR() == stickyASR) {
3785 aBuilder->GetFilterASR()
3786 ->mScrollableFrame->SetHasOutOfFlowContentInsideFilter();
3790 /* If there's blending, wrap up the list in a blend-mode item. Note
3791 * that opacity can be applied before blending as the blend color is
3792 * not affected by foreground opacity (only background alpha).
3795 if (useBlendMode) {
3796 DisplayListClipState::AutoSaveRestore blendModeClipState(aBuilder);
3797 resultList.AppendNewToTop<nsDisplayBlendMode>(aBuilder, this, &resultList,
3798 effects->mMixBlendMode,
3799 containerItemASR, false);
3800 ct.TrackContainer(resultList.GetTop());
3803 bool createdOwnLayer = false;
3804 CreateOwnLayerIfNeeded(aBuilder, &resultList,
3805 nsDisplayOwnLayer::OwnLayerForStackingContext,
3806 &createdOwnLayer);
3807 if (createdOwnLayer) {
3808 ct.TrackContainer(resultList.GetTop());
3811 if (aCreatedContainerItem) {
3812 *aCreatedContainerItem = ct.mCreatedContainer;
3815 aList->AppendToTop(&resultList);
3818 static nsDisplayItem* WrapInWrapList(nsDisplayListBuilder* aBuilder,
3819 nsIFrame* aFrame, nsDisplayList* aList,
3820 const ActiveScrolledRoot* aContainerASR,
3821 bool aBuiltContainerItem = false) {
3822 nsDisplayItem* item = aList->GetBottom();
3823 if (!item) {
3824 return nullptr;
3827 // We need a wrap list if there are multiple items, or if the single
3828 // item has a different frame. This can change in a partial build depending
3829 // on which items we build, so we need to ensure that we don't transition
3830 // to/from a wrap list without invalidating correctly.
3831 bool needsWrapList =
3832 item->GetAbove() || item->Frame() != aFrame || item->GetChildren();
3834 // If we have an explicit container item (that can't change without an
3835 // invalidation) or we're doing a full build and don't need a wrap list, then
3836 // we can skip adding one.
3837 if (aBuiltContainerItem || (!aBuilder->IsPartialUpdate() && !needsWrapList)) {
3838 aList->RemoveBottom();
3839 return item;
3842 // If we're doing a partial build and we didn't need a wrap list
3843 // previously then we can try to work from there.
3844 if (aBuilder->IsPartialUpdate() &&
3845 !aFrame->HasDisplayItem(uint32_t(DisplayItemType::TYPE_CONTAINER))) {
3846 // If we now need a wrap list, we must previously have had no display items
3847 // or a single one belonging to this frame. Mark the item itself as
3848 // discarded so that RetainedDisplayListBuilder uses the ones we just built.
3849 // We don't want to mark the frame as modified as that would invalidate
3850 // positioned descendants that might be outside of this list, and might not
3851 // have been rebuilt this time.
3852 if (needsWrapList) {
3853 DiscardOldItems(aFrame);
3854 } else {
3855 aList->RemoveBottom();
3856 return item;
3860 // The last case we could try to handle is when we previously had a wrap list,
3861 // but no longer need it. Unfortunately we can't differentiate this case from
3862 // a partial build where other children exist but we just didn't build them
3863 // this time.
3864 // TODO:RetainedDisplayListBuilder's merge phase has the full list and
3865 // could strip them out.
3867 return MakeDisplayItem<nsDisplayContainer>(aBuilder, aFrame, aContainerASR,
3868 aList);
3872 * Check if a frame should be visited for building display list.
3874 static bool DescendIntoChild(nsDisplayListBuilder* aBuilder,
3875 const nsIFrame* aChild, const nsRect& aVisible,
3876 const nsRect& aDirty) {
3877 if (aChild->HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO)) {
3878 return true;
3881 // If the child is a scrollframe that we want to ignore, then we need
3882 // to descend into it because its scrolled child may intersect the dirty
3883 // area even if the scrollframe itself doesn't.
3884 if (aChild == aBuilder->GetIgnoreScrollFrame()) {
3885 return true;
3888 // There are cases where the "ignore scroll frame" on the builder is not set
3889 // correctly, and so we additionally want to catch cases where the child is
3890 // a root scrollframe and we are ignoring scrolling on the viewport.
3891 if (aChild == aBuilder->GetPresShellIgnoreScrollFrame()) {
3892 return true;
3895 nsRect overflow = aChild->InkOverflowRect();
3897 // On mobile, there may be a dynamic toolbar. The root content document's
3898 // root scroll frame's ink overflow rect does not include the toolbar
3899 // height, but if the toolbar is hidden, we still want to be able to target
3900 // content underneath the toolbar, so expand the overflow rect here to
3901 // allow display list building to descend into the scroll frame.
3902 if (aBuilder->IsForEventDelivery() &&
3903 aChild == aChild->PresShell()->GetRootScrollFrame() &&
3904 aChild->PresContext()->IsRootContentDocumentCrossProcess() &&
3905 aChild->PresContext()->HasDynamicToolbar()) {
3906 overflow.SizeTo(nsLayoutUtils::ExpandHeightForDynamicToolbar(
3907 aChild->PresContext(), overflow.Size()));
3910 if (aDirty.Intersects(overflow)) {
3911 return true;
3914 if (aChild->ForceDescendIntoIfVisible() && aVisible.Intersects(overflow)) {
3915 return true;
3918 if (aChild->IsFrameOfType(nsIFrame::eTablePart)) {
3919 // Relative positioning and transforms can cause table parts to move, but we
3920 // will still paint the backgrounds for their ancestor parts under them at
3921 // their 'normal' position. That means that we must consider the overflow
3922 // rects at both positions.
3924 // We convert the overflow rect into the nsTableFrame's coordinate
3925 // space, applying the normal position offset at each step. Then we
3926 // compare that against the builder's cached dirty rect in table
3927 // coordinate space.
3928 const nsIFrame* f = aChild;
3929 nsRect normalPositionOverflowRelativeToTable = overflow;
3931 while (f->IsFrameOfType(nsIFrame::eTablePart)) {
3932 normalPositionOverflowRelativeToTable += f->GetNormalPosition();
3933 f = f->GetParent();
3936 nsDisplayTableBackgroundSet* tableBGs = aBuilder->GetTableBackgroundSet();
3937 if (tableBGs && tableBGs->GetDirtyRect().Intersects(
3938 normalPositionOverflowRelativeToTable)) {
3939 return true;
3943 return false;
3946 void nsIFrame::BuildDisplayListForSimpleChild(nsDisplayListBuilder* aBuilder,
3947 nsIFrame* aChild,
3948 const nsDisplayListSet& aLists) {
3949 // This is the shortcut for frames been handled along the common
3950 // path, the most common one of THE COMMON CASE mentioned later.
3951 MOZ_ASSERT(aChild->Type() != LayoutFrameType::Placeholder);
3952 MOZ_ASSERT(!aBuilder->GetSelectedFramesOnly() &&
3953 !aBuilder->GetIncludeAllOutOfFlows(),
3954 "It should be held for painting to window");
3955 MOZ_ASSERT(aChild->HasAnyStateBits(NS_FRAME_SIMPLE_DISPLAYLIST));
3957 const nsPoint offset = aChild->GetOffsetTo(this);
3958 const nsRect visible = aBuilder->GetVisibleRect() - offset;
3959 const nsRect dirty = aBuilder->GetDirtyRect() - offset;
3961 if (!DescendIntoChild(aBuilder, aChild, visible, dirty)) {
3962 return;
3965 // Child cannot be transformed since it is not a stacking context.
3966 nsDisplayListBuilder::AutoBuildingDisplayList buildingForChild(
3967 aBuilder, aChild, visible, dirty, false);
3969 UpdateCurrentHitTestInfo(aBuilder, aChild);
3971 aChild->MarkAbsoluteFramesForDisplayList(aBuilder);
3972 aBuilder->AdjustWindowDraggingRegion(aChild);
3973 aBuilder->Check();
3974 aChild->BuildDisplayList(aBuilder, aLists);
3975 aBuilder->Check();
3976 aBuilder->DisplayCaret(aChild, aLists.Outlines());
3977 #ifdef DEBUG
3978 DisplayDebugBorders(aBuilder, aChild, aLists);
3979 #endif
3982 nsIFrame::DisplayChildFlag nsIFrame::DisplayFlagForFlexOrGridItem() const {
3983 MOZ_ASSERT(IsFlexOrGridItem(),
3984 "Should only be called on flex or grid items!");
3985 return DisplayChildFlag::ForcePseudoStackingContext;
3988 static bool ShouldSkipFrame(nsDisplayListBuilder* aBuilder,
3989 const nsIFrame* aFrame) {
3990 // If painting is restricted to just the background of the top level frame,
3991 // then we have nothing to do here.
3992 if (aBuilder->IsBackgroundOnly()) {
3993 return true;
3996 if (aBuilder->IsForGenerateGlyphMask() &&
3997 (!aFrame->IsTextFrame() && aFrame->IsLeaf())) {
3998 return true;
4001 // The placeholder frame should have the same content as the OOF frame.
4002 if (aBuilder->GetSelectedFramesOnly() &&
4003 (aFrame->IsLeaf() && !aFrame->IsSelected())) {
4004 return true;
4007 static const nsFrameState skipFlags =
4008 (NS_FRAME_TOO_DEEP_IN_FRAME_TREE | NS_FRAME_IS_NONDISPLAY);
4010 return aFrame->HasAnyStateBits(skipFlags);
4013 void nsIFrame::BuildDisplayListForChild(nsDisplayListBuilder* aBuilder,
4014 nsIFrame* aChild,
4015 const nsDisplayListSet& aLists,
4016 DisplayChildFlags aFlags) {
4017 AutoCheckBuilder check(aBuilder);
4019 if (ShouldSkipFrame(aBuilder, aChild)) {
4020 return;
4023 nsIFrame* child = aChild;
4024 auto* placeholder = child->IsPlaceholderFrame()
4025 ? static_cast<nsPlaceholderFrame*>(child)
4026 : nullptr;
4027 nsIFrame* childOrOutOfFlow =
4028 placeholder ? placeholder->GetOutOfFlowFrame() : child;
4030 nsIFrame* parent = childOrOutOfFlow->GetParent();
4031 const auto* parentDisplay = parent->StyleDisplay();
4032 const auto overflowClipAxes =
4033 parent->ShouldApplyOverflowClipping(parentDisplay);
4035 const bool isPaintingToWindow = aBuilder->IsPaintingToWindow();
4036 const bool doingShortcut =
4037 isPaintingToWindow &&
4038 child->HasAnyStateBits(NS_FRAME_SIMPLE_DISPLAYLIST) &&
4039 // Animations may change the stacking context state.
4040 // ShouldApplyOverflowClipping is affected by the parent style, which does
4041 // not invalidate the NS_FRAME_SIMPLE_DISPLAYLIST bit.
4042 !(overflowClipAxes != PhysicalAxes::None ||
4043 child->MayHaveTransformAnimation() || child->MayHaveOpacityAnimation());
4045 if (aBuilder->IsForPainting()) {
4046 aBuilder->ClearWillChangeBudgetStatus(child);
4049 if (StaticPrefs::layout_css_scroll_anchoring_highlight()) {
4050 if (child->FirstContinuation()->IsScrollAnchor()) {
4051 nsRect bounds = child->GetContentRectRelativeToSelf() +
4052 aBuilder->ToReferenceFrame(child);
4053 nsDisplaySolidColor* color = MakeDisplayItem<nsDisplaySolidColor>(
4054 aBuilder, child, bounds, NS_RGBA(255, 0, 255, 64));
4055 if (color) {
4056 color->SetOverrideZIndex(INT32_MAX);
4057 aLists.PositionedDescendants()->AppendToTop(color);
4062 if (doingShortcut) {
4063 BuildDisplayListForSimpleChild(aBuilder, child, aLists);
4064 return;
4067 // dirty rect in child-relative coordinates
4068 NS_ASSERTION(aBuilder->GetCurrentFrame() == this, "Wrong coord space!");
4069 const nsPoint offset = child->GetOffsetTo(this);
4070 nsRect visible = aBuilder->GetVisibleRect() - offset;
4071 nsRect dirty = aBuilder->GetDirtyRect() - offset;
4073 nsDisplayListBuilder::OutOfFlowDisplayData* savedOutOfFlowData = nullptr;
4074 if (placeholder) {
4075 if (placeholder->HasAnyStateBits(PLACEHOLDER_FOR_TOPLAYER)) {
4076 // If the out-of-flow frame is in the top layer, the viewport frame
4077 // will paint it. Skip it here. Note that, only out-of-flow frames
4078 // with this property should be skipped, because non-HTML elements
4079 // may stop their children from being out-of-flow. Those frames
4080 // should still be handled in the normal in-flow path.
4081 return;
4084 child = childOrOutOfFlow;
4085 if (aBuilder->IsForPainting()) {
4086 aBuilder->ClearWillChangeBudgetStatus(child);
4089 // If 'child' is a pushed float then it's owned by a block that's not an
4090 // ancestor of the placeholder, and it will be painted by that block and
4091 // should not be painted through the placeholder. Also recheck
4092 // NS_FRAME_TOO_DEEP_IN_FRAME_TREE and NS_FRAME_IS_NONDISPLAY.
4093 static const nsFrameState skipFlags =
4094 (NS_FRAME_IS_PUSHED_FLOAT | NS_FRAME_TOO_DEEP_IN_FRAME_TREE |
4095 NS_FRAME_IS_NONDISPLAY);
4096 if (child->HasAnyStateBits(skipFlags) || nsLayoutUtils::IsPopup(child)) {
4097 return;
4100 MOZ_ASSERT(child->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW));
4101 savedOutOfFlowData = nsDisplayListBuilder::GetOutOfFlowData(child);
4103 if (aBuilder->GetIncludeAllOutOfFlows()) {
4104 visible = child->InkOverflowRect();
4105 dirty = child->InkOverflowRect();
4106 } else if (savedOutOfFlowData) {
4107 visible =
4108 savedOutOfFlowData->GetVisibleRectForFrame(aBuilder, child, &dirty);
4109 } else {
4110 // The out-of-flow frame did not intersect the dirty area. We may still
4111 // need to traverse into it, since it may contain placeholders we need
4112 // to enter to reach other out-of-flow frames that are visible.
4113 visible.SetEmpty();
4114 dirty.SetEmpty();
4118 NS_ASSERTION(!child->IsPlaceholderFrame(),
4119 "Should have dealt with placeholders already");
4121 if (!DescendIntoChild(aBuilder, child, visible, dirty)) {
4122 return;
4125 const bool isSVG = child->HasAnyStateBits(NS_FRAME_SVG_LAYOUT);
4127 // This flag is raised if the control flow strays off the common path.
4128 // The common path is the most common one of THE COMMON CASE mentioned later.
4129 bool awayFromCommonPath = !isPaintingToWindow;
4131 // true if this is a real or pseudo stacking context
4132 bool pseudoStackingContext =
4133 aFlags.contains(DisplayChildFlag::ForcePseudoStackingContext);
4135 if (!pseudoStackingContext && !isSVG &&
4136 aFlags.contains(DisplayChildFlag::Inline) &&
4137 !child->IsFrameOfType(eLineParticipant)) {
4138 // child is a non-inline frame in an inline context, i.e.,
4139 // it acts like inline-block or inline-table. Therefore it is a
4140 // pseudo-stacking-context.
4141 pseudoStackingContext = true;
4144 const nsStyleDisplay* ourDisp = StyleDisplay();
4145 // REVIEW: Taken from nsBoxFrame::Paint
4146 // Don't paint our children if the theme object is a leaf.
4147 if (IsThemed(ourDisp) && !PresContext()->Theme()->WidgetIsContainer(
4148 ourDisp->EffectiveAppearance()))
4149 return;
4151 // Since we're now sure that we're adding this frame to the display list
4152 // (which means we're painting it, modulo occlusion), mark it as visible
4153 // within the displayport.
4154 if (isPaintingToWindow && child->TrackingVisibility()) {
4155 child->PresShell()->EnsureFrameInApproximatelyVisibleList(child);
4156 awayFromCommonPath = true;
4159 child->SetBuiltDisplayList(true);
4161 // Child is composited if it's transformed, partially transparent, or has
4162 // SVG effects or a blend mode..
4163 const nsStyleDisplay* disp = child->StyleDisplay();
4164 const nsStyleEffects* effects = child->StyleEffects();
4166 const bool isPositioned = disp->IsPositionedStyle();
4167 const bool isStackingContext =
4168 aFlags.contains(DisplayChildFlag::ForceStackingContext) ||
4169 child->IsStackingContext(disp, effects);
4171 if (pseudoStackingContext || isStackingContext || isPositioned ||
4172 placeholder || (!isSVG && disp->IsFloating(child)) ||
4173 (isSVG && effects->mClip.IsRect() && IsSVGContentWithCSSClip(child))) {
4174 pseudoStackingContext = true;
4175 awayFromCommonPath = true;
4178 NS_ASSERTION(!isStackingContext || pseudoStackingContext,
4179 "Stacking contexts must also be pseudo-stacking-contexts");
4181 nsDisplayListBuilder::AutoBuildingDisplayList buildingForChild(
4182 aBuilder, child, visible, dirty);
4184 UpdateCurrentHitTestInfo(aBuilder, child);
4186 DisplayListClipState::AutoClipMultiple clipState(aBuilder);
4187 nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter asrSetter(aBuilder);
4189 if (savedOutOfFlowData) {
4190 aBuilder->SetBuildingInvisibleItems(false);
4192 clipState.SetClipChainForContainingBlockDescendants(
4193 savedOutOfFlowData->mContainingBlockClipChain);
4194 asrSetter.SetCurrentActiveScrolledRoot(
4195 savedOutOfFlowData->mContainingBlockActiveScrolledRoot);
4196 MOZ_ASSERT(awayFromCommonPath,
4197 "It is impossible when savedOutOfFlowData is true");
4198 } else if (HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO) &&
4199 placeholder) {
4200 NS_ASSERTION(visible.IsEmpty(), "should have empty visible rect");
4201 // Every item we build from now until we descent into an out of flow that
4202 // does have saved out of flow data should be invisible. This state gets
4203 // restored when AutoBuildingDisplayList gets out of scope.
4204 aBuilder->SetBuildingInvisibleItems(true);
4206 // If we have nested out-of-flow frames and the outer one isn't visible
4207 // then we won't have stored clip data for it. We can just clear the clip
4208 // instead since we know we won't render anything, and the inner out-of-flow
4209 // frame will setup the correct clip for itself.
4210 clipState.SetClipChainForContainingBlockDescendants(nullptr);
4213 // Setup clipping for the parent's overflow:clip,
4214 // or overflow:hidden on elements that don't support scrolling (and therefore
4215 // don't create nsHTML/XULScrollFrame). This clipping needs to not clip
4216 // anything directly rendered by the parent, only the rendering of its
4217 // children.
4218 // Don't use overflowClip to restrict the dirty rect, since some of the
4219 // descendants may not be clipped by it. Even if we end up with unnecessary
4220 // display items, they'll be pruned during ComputeVisibility.
4222 // FIXME(emilio): Why can't we handle this more similarly to `clip` (on the
4223 // parent, rather than on the children)? Would ClipContentDescendants do what
4224 // we want?
4225 if (overflowClipAxes != PhysicalAxes::None) {
4226 ApplyOverflowClipping(aBuilder, parent, overflowClipAxes, clipState);
4227 awayFromCommonPath = true;
4230 nsDisplayList list;
4231 nsDisplayList extraPositionedDescendants;
4232 const ActiveScrolledRoot* wrapListASR;
4233 bool builtContainerItem = false;
4234 if (isStackingContext) {
4235 // True stacking context.
4236 // For stacking contexts, BuildDisplayListForStackingContext handles
4237 // clipping and MarkAbsoluteFramesForDisplayList.
4238 nsDisplayListBuilder::AutoContainerASRTracker contASRTracker(aBuilder);
4239 child->BuildDisplayListForStackingContext(aBuilder, &list,
4240 &builtContainerItem);
4241 wrapListASR = contASRTracker.GetContainerASR();
4242 if (aBuilder->GetCaretFrame() == child) {
4243 builtContainerItem = false;
4245 } else {
4246 Maybe<nsRect> clipPropClip =
4247 child->GetClipPropClipRect(disp, effects, child->GetSize());
4248 if (clipPropClip) {
4249 aBuilder->IntersectVisibleRect(*clipPropClip);
4250 aBuilder->IntersectDirtyRect(*clipPropClip);
4251 clipState.ClipContentDescendants(*clipPropClip +
4252 aBuilder->ToReferenceFrame(child));
4253 awayFromCommonPath = true;
4256 child->MarkAbsoluteFramesForDisplayList(aBuilder);
4258 if (!awayFromCommonPath &&
4259 // Some SVG frames might change opacity without invalidating the frame,
4260 // so exclude them from the fast-path.
4261 !child->IsFrameOfType(nsIFrame::eSVG)) {
4262 // The shortcut is available for the child for next time.
4263 child->AddStateBits(NS_FRAME_SIMPLE_DISPLAYLIST);
4266 if (!pseudoStackingContext) {
4267 // THIS IS THE COMMON CASE.
4268 // Not a pseudo or real stacking context. Do the simple thing and
4269 // return early.
4270 aBuilder->AdjustWindowDraggingRegion(child);
4271 aBuilder->Check();
4272 child->BuildDisplayList(aBuilder, aLists);
4273 aBuilder->Check();
4274 aBuilder->DisplayCaret(child, aLists.Outlines());
4275 #ifdef DEBUG
4276 DisplayDebugBorders(aBuilder, child, aLists);
4277 #endif
4278 return;
4281 // A pseudo-stacking context (e.g., a positioned element with z-index auto).
4282 // We allow positioned descendants of the child to escape to our parent
4283 // stacking context's positioned descendant list, because they might be
4284 // z-index:non-auto
4285 nsDisplayListCollection pseudoStack(aBuilder);
4287 aBuilder->AdjustWindowDraggingRegion(child);
4288 nsDisplayListBuilder::AutoContainerASRTracker contASRTracker(aBuilder);
4289 aBuilder->Check();
4290 child->BuildDisplayList(aBuilder, pseudoStack);
4291 aBuilder->Check();
4292 if (aBuilder->DisplayCaret(child, pseudoStack.Outlines())) {
4293 builtContainerItem = false;
4295 wrapListASR = contASRTracker.GetContainerASR();
4297 list.AppendToTop(pseudoStack.BorderBackground());
4298 list.AppendToTop(pseudoStack.BlockBorderBackgrounds());
4299 list.AppendToTop(pseudoStack.Floats());
4300 list.AppendToTop(pseudoStack.Content());
4301 list.AppendToTop(pseudoStack.Outlines());
4302 extraPositionedDescendants.AppendToTop(pseudoStack.PositionedDescendants());
4303 #ifdef DEBUG
4304 DisplayDebugBorders(aBuilder, child, aLists);
4305 #endif
4308 buildingForChild.RestoreBuildingInvisibleItemsValue();
4310 if (isPositioned || isStackingContext) {
4311 // Genuine stacking contexts, and positioned pseudo-stacking-contexts,
4312 // go in this level.
4313 if (!list.IsEmpty()) {
4314 nsDisplayItem* item = WrapInWrapList(aBuilder, child, &list, wrapListASR,
4315 builtContainerItem);
4316 if (isSVG) {
4317 aLists.Content()->AppendToTop(item);
4318 } else {
4319 aLists.PositionedDescendants()->AppendToTop(item);
4322 } else if (!isSVG && disp->IsFloating(child)) {
4323 if (!list.IsEmpty()) {
4324 aLists.Floats()->AppendToTop(
4325 WrapInWrapList(aBuilder, child, &list, wrapListASR));
4327 } else {
4328 aLists.Content()->AppendToTop(&list);
4330 // We delay placing the positioned descendants of positioned frames to here,
4331 // because in the absence of z-index this is the correct order for them.
4332 // This doesn't affect correctness because the positioned descendants list
4333 // is sorted by z-order and content in BuildDisplayListForStackingContext,
4334 // but it means that sort routine needs to do less work.
4335 aLists.PositionedDescendants()->AppendToTop(&extraPositionedDescendants);
4338 void nsIFrame::MarkAbsoluteFramesForDisplayList(
4339 nsDisplayListBuilder* aBuilder) {
4340 if (IsAbsoluteContainer()) {
4341 aBuilder->MarkFramesForDisplayList(
4342 this, GetAbsoluteContainingBlock()->GetChildList());
4346 nsresult nsIFrame::GetContentForEvent(WidgetEvent* aEvent,
4347 nsIContent** aContent) {
4348 nsIFrame* f = nsLayoutUtils::GetNonGeneratedAncestor(this);
4349 *aContent = f->GetContent();
4350 NS_IF_ADDREF(*aContent);
4351 return NS_OK;
4354 void nsIFrame::FireDOMEvent(const nsAString& aDOMEventName,
4355 nsIContent* aContent) {
4356 nsIContent* target = aContent ? aContent : GetContent();
4358 if (target) {
4359 RefPtr<AsyncEventDispatcher> asyncDispatcher = new AsyncEventDispatcher(
4360 target, aDOMEventName, CanBubble::eYes, ChromeOnlyDispatch::eNo);
4361 DebugOnly<nsresult> rv = asyncDispatcher->PostDOMEvent();
4362 NS_ASSERTION(NS_SUCCEEDED(rv), "AsyncEventDispatcher failed to dispatch");
4366 nsresult nsIFrame::HandleEvent(nsPresContext* aPresContext,
4367 WidgetGUIEvent* aEvent,
4368 nsEventStatus* aEventStatus) {
4369 if (aEvent->mMessage == eMouseMove) {
4370 // XXX If the second argument of HandleDrag() is WidgetMouseEvent,
4371 // the implementation becomes simpler.
4372 return HandleDrag(aPresContext, aEvent, aEventStatus);
4375 if ((aEvent->mClass == eMouseEventClass &&
4376 aEvent->AsMouseEvent()->mButton == MouseButton::ePrimary) ||
4377 aEvent->mClass == eTouchEventClass) {
4378 if (aEvent->mMessage == eMouseDown || aEvent->mMessage == eTouchStart) {
4379 HandlePress(aPresContext, aEvent, aEventStatus);
4380 } else if (aEvent->mMessage == eMouseUp || aEvent->mMessage == eTouchEnd) {
4381 HandleRelease(aPresContext, aEvent, aEventStatus);
4384 return NS_OK;
4387 nsresult nsIFrame::GetDataForTableSelection(
4388 const nsFrameSelection* aFrameSelection, mozilla::PresShell* aPresShell,
4389 WidgetMouseEvent* aMouseEvent, nsIContent** aParentContent,
4390 int32_t* aContentOffset, TableSelectionMode* aTarget) {
4391 if (!aFrameSelection || !aPresShell || !aMouseEvent || !aParentContent ||
4392 !aContentOffset || !aTarget)
4393 return NS_ERROR_NULL_POINTER;
4395 *aParentContent = nullptr;
4396 *aContentOffset = 0;
4397 *aTarget = TableSelectionMode::None;
4399 int16_t displaySelection = aPresShell->GetSelectionFlags();
4401 bool selectingTableCells = aFrameSelection->IsInTableSelectionMode();
4403 // DISPLAY_ALL means we're in an editor.
4404 // If already in cell selection mode,
4405 // continue selecting with mouse drag or end on mouse up,
4406 // or when using shift key to extend block of cells
4407 // (Mouse down does normal selection unless Ctrl/Cmd is pressed)
4408 bool doTableSelection =
4409 displaySelection == nsISelectionDisplay::DISPLAY_ALL &&
4410 selectingTableCells &&
4411 (aMouseEvent->mMessage == eMouseMove ||
4412 (aMouseEvent->mMessage == eMouseUp &&
4413 aMouseEvent->mButton == MouseButton::ePrimary) ||
4414 aMouseEvent->IsShift());
4416 if (!doTableSelection) {
4417 // In Browser, special 'table selection' key must be pressed for table
4418 // selection or when just Shift is pressed and we're already in table/cell
4419 // selection mode
4420 #ifdef XP_MACOSX
4421 doTableSelection = aMouseEvent->IsMeta() ||
4422 (aMouseEvent->IsShift() && selectingTableCells);
4423 #else
4424 doTableSelection = aMouseEvent->IsControl() ||
4425 (aMouseEvent->IsShift() && selectingTableCells);
4426 #endif
4428 if (!doTableSelection) return NS_OK;
4430 // Get the cell frame or table frame (or parent) of the current content node
4431 nsIFrame* frame = this;
4432 bool foundCell = false;
4433 bool foundTable = false;
4435 // Get the limiting node to stop parent frame search
4436 nsIContent* limiter = aFrameSelection->GetLimiter();
4438 // If our content node is an ancestor of the limiting node,
4439 // we should stop the search right now.
4440 if (limiter && limiter->IsInclusiveDescendantOf(GetContent())) return NS_OK;
4442 // We don't initiate row/col selection from here now,
4443 // but we may in future
4444 // bool selectColumn = false;
4445 // bool selectRow = false;
4447 while (frame) {
4448 // Check for a table cell by querying to a known CellFrame interface
4449 nsITableCellLayout* cellElement = do_QueryFrame(frame);
4450 if (cellElement) {
4451 foundCell = true;
4452 // TODO: If we want to use proximity to top or left border
4453 // for row and column selection, this is the place to do it
4454 break;
4455 } else {
4456 // If not a cell, check for table
4457 // This will happen when starting frame is the table or child of a table,
4458 // such as a row (we were inbetween cells or in table border)
4459 nsTableWrapperFrame* tableFrame = do_QueryFrame(frame);
4460 if (tableFrame) {
4461 foundTable = true;
4462 // TODO: How can we select row when along left table edge
4463 // or select column when along top edge?
4464 break;
4465 } else {
4466 frame = frame->GetParent();
4467 // Stop if we have hit the selection's limiting content node
4468 if (frame && frame->GetContent() == limiter) break;
4472 // We aren't in a cell or table
4473 if (!foundCell && !foundTable) return NS_OK;
4475 nsIContent* tableOrCellContent = frame->GetContent();
4476 if (!tableOrCellContent) return NS_ERROR_FAILURE;
4478 nsCOMPtr<nsIContent> parentContent = tableOrCellContent->GetParent();
4479 if (!parentContent) return NS_ERROR_FAILURE;
4481 int32_t offset = parentContent->ComputeIndexOf(tableOrCellContent);
4482 // Not likely?
4483 if (offset < 0) return NS_ERROR_FAILURE;
4485 // Everything is OK -- set the return values
4486 parentContent.forget(aParentContent);
4488 *aContentOffset = offset;
4490 #if 0
4491 if (selectRow)
4492 *aTarget = TableSelectionMode::Row;
4493 else if (selectColumn)
4494 *aTarget = TableSelectionMode::Column;
4495 else
4496 #endif
4497 if (foundCell) {
4498 *aTarget = TableSelectionMode::Cell;
4499 } else if (foundTable) {
4500 *aTarget = TableSelectionMode::Table;
4503 return NS_OK;
4506 static bool IsEditingHost(const nsIFrame* aFrame) {
4507 auto* element = nsGenericHTMLElement::FromNodeOrNull(aFrame->GetContent());
4508 return element && element->IsEditableRoot();
4511 static StyleUserSelect UsedUserSelect(const nsIFrame* aFrame) {
4512 if (aFrame->HasAnyStateBits(NS_FRAME_GENERATED_CONTENT)) {
4513 return StyleUserSelect::None;
4516 // Per https://drafts.csswg.org/css-ui-4/#content-selection:
4518 // The computed value is the specified value, except:
4520 // 1 - on editable elements where the computed value is always 'contain'
4521 // regardless of the specified value.
4522 // 2 - when the specified value is auto, which computes to one of the other
4523 // values [...]
4525 // See https://github.com/w3c/csswg-drafts/issues/3344 to see why we do this
4526 // at used-value time instead of at computed-value time.
4528 // Also, we check for auto first to allow explicitly overriding the value for
4529 // the editing host.
4530 auto style = aFrame->StyleUIReset()->mUserSelect;
4531 if (style != StyleUserSelect::Auto) {
4532 return style;
4535 if (aFrame->IsTextInputFrame() || IsEditingHost(aFrame)) {
4536 // We don't implement 'contain' itself, but we make 'text' behave as
4537 // 'contain' for contenteditable and <input> / <textarea> elements anyway so
4538 // this is ok.
4539 return StyleUserSelect::Text;
4542 auto* parent = nsLayoutUtils::GetParentOrPlaceholderFor(aFrame);
4543 return parent ? UsedUserSelect(parent) : StyleUserSelect::Text;
4546 bool nsIFrame::IsSelectable(StyleUserSelect* aSelectStyle) const {
4547 auto style = UsedUserSelect(this);
4548 if (aSelectStyle) {
4549 *aSelectStyle = style;
4551 return style != StyleUserSelect::None;
4554 bool nsIFrame::ShouldHaveLineIfEmpty() const {
4555 if (Style()->IsPseudoOrAnonBox() &&
4556 Style()->GetPseudoType() != PseudoStyleType::scrolledContent) {
4557 return false;
4559 return IsEditingHost(this);
4563 * Handles the Mouse Press Event for the frame
4565 NS_IMETHODIMP
4566 nsIFrame::HandlePress(nsPresContext* aPresContext, WidgetGUIEvent* aEvent,
4567 nsEventStatus* aEventStatus) {
4568 NS_ENSURE_ARG_POINTER(aEventStatus);
4569 if (nsEventStatus_eConsumeNoDefault == *aEventStatus) {
4570 return NS_OK;
4573 NS_ENSURE_ARG_POINTER(aEvent);
4574 if (aEvent->mClass == eTouchEventClass) {
4575 return NS_OK;
4578 // We often get out of sync state issues with mousedown events that
4579 // get interrupted by alerts/dialogs.
4580 // Check with the ESM to see if we should process this one
4581 if (!aPresContext->EventStateManager()->EventStatusOK(aEvent)) return NS_OK;
4583 mozilla::PresShell* presShell = aPresContext->GetPresShell();
4584 if (!presShell) {
4585 return NS_ERROR_FAILURE;
4588 // if we are in Navigator and the click is in a draggable node, we don't want
4589 // to start selection because we don't want to interfere with a potential
4590 // drag of said node and steal all its glory.
4591 int16_t isEditor = presShell->GetSelectionFlags();
4592 // weaaak. only the editor can display frame selection not just text and
4593 // images
4594 isEditor = isEditor == nsISelectionDisplay::DISPLAY_ALL;
4596 WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
4598 if (!mouseEvent->IsAlt()) {
4599 for (nsIContent* content = mContent; content;
4600 content = content->GetFlattenedTreeParent()) {
4601 if (nsContentUtils::ContentIsDraggable(content) &&
4602 !content->IsEditable()) {
4603 // coordinate stuff is the fix for bug #55921
4604 if ((mRect - GetPosition())
4605 .Contains(nsLayoutUtils::GetEventCoordinatesRelativeTo(
4606 mouseEvent, RelativeTo{this}))) {
4607 return NS_OK;
4613 // check whether style allows selection
4614 // if not, don't tell selection the mouse event even occurred.
4615 StyleUserSelect selectStyle;
4616 // check for select: none
4617 if (!IsSelectable(&selectStyle)) {
4618 return NS_OK;
4621 bool useFrameSelection = (selectStyle == StyleUserSelect::Text);
4623 // If the mouse is dragged outside the nearest enclosing scrollable area
4624 // while making a selection, the area will be scrolled. To do this, capture
4625 // the mouse on the nearest scrollable frame. If there isn't a scrollable
4626 // frame, or something else is already capturing the mouse, there's no
4627 // reason to capture.
4628 if (!PresShell::GetCapturingContent()) {
4629 nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetNearestScrollableFrame(
4630 this, nsLayoutUtils::SCROLLABLE_SAME_DOC |
4631 nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN);
4632 if (scrollFrame) {
4633 nsIFrame* capturingFrame = do_QueryFrame(scrollFrame);
4634 PresShell::SetCapturingContent(capturingFrame->GetContent(),
4635 CaptureFlags::IgnoreAllowedState);
4639 // XXX This is screwy; it really should use the selection frame, not the
4640 // event frame
4641 const nsFrameSelection* frameselection = nullptr;
4642 if (useFrameSelection)
4643 frameselection = GetConstFrameSelection();
4644 else
4645 frameselection = presShell->ConstFrameSelection();
4647 if (!frameselection || frameselection->GetDisplaySelection() ==
4648 nsISelectionController::SELECTION_OFF)
4649 return NS_OK; // nothing to do we cannot affect selection from here
4651 #ifdef XP_MACOSX
4652 if (mouseEvent->IsControl())
4653 return NS_OK; // short circuit. hard coded for mac due to time restraints.
4654 bool control = mouseEvent->IsMeta();
4655 #else
4656 bool control = mouseEvent->IsControl();
4657 #endif
4659 RefPtr<nsFrameSelection> fc = const_cast<nsFrameSelection*>(frameselection);
4660 if (mouseEvent->mClickCount > 1) {
4661 // These methods aren't const but can't actually delete anything,
4662 // so no need for AutoWeakFrame.
4663 fc->SetDragState(true);
4664 return HandleMultiplePress(aPresContext, mouseEvent, aEventStatus, control);
4667 nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(mouseEvent,
4668 RelativeTo{this});
4669 ContentOffsets offsets = GetContentOffsetsFromPoint(pt, SKIP_HIDDEN);
4671 if (!offsets.content) return NS_ERROR_FAILURE;
4673 // Let Ctrl/Cmd+mouse down do table selection instead of drag initiation
4674 nsCOMPtr<nsIContent> parentContent;
4675 int32_t contentOffset;
4676 TableSelectionMode target;
4677 nsresult rv;
4678 rv = GetDataForTableSelection(frameselection, presShell, mouseEvent,
4679 getter_AddRefs(parentContent), &contentOffset,
4680 &target);
4681 if (NS_SUCCEEDED(rv) && parentContent) {
4682 fc->SetDragState(true);
4683 return fc->HandleTableSelection(parentContent, contentOffset, target,
4684 mouseEvent);
4687 fc->SetDelayedCaretData(0);
4689 // Check if any part of this frame is selected, and if the
4690 // user clicked inside the selected region. If so, we delay
4691 // starting a new selection since the user may be trying to
4692 // drag the selected region to some other app.
4694 if (GetContent() && GetContent()->IsMaybeSelected()) {
4695 bool inSelection = false;
4696 UniquePtr<SelectionDetails> details = frameselection->LookUpSelection(
4697 offsets.content, 0, offsets.EndOffset(), false);
4700 // If there are any details, check to see if the user clicked
4701 // within any selected region of the frame.
4704 for (SelectionDetails* curDetail = details.get(); curDetail;
4705 curDetail = curDetail->mNext.get()) {
4707 // If the user clicked inside a selection, then just
4708 // return without doing anything. We will handle placing
4709 // the caret later on when the mouse is released. We ignore
4710 // the spellcheck, find and url formatting selections.
4712 if (curDetail->mSelectionType != SelectionType::eSpellCheck &&
4713 curDetail->mSelectionType != SelectionType::eFind &&
4714 curDetail->mSelectionType != SelectionType::eURLSecondary &&
4715 curDetail->mSelectionType != SelectionType::eURLStrikeout &&
4716 curDetail->mStart <= offsets.StartOffset() &&
4717 offsets.EndOffset() <= curDetail->mEnd) {
4718 inSelection = true;
4722 if (inSelection) {
4723 fc->SetDragState(false);
4724 fc->SetDelayedCaretData(mouseEvent);
4725 return NS_OK;
4729 fc->SetDragState(true);
4731 // Do not touch any nsFrame members after this point without adding
4732 // weakFrame checks.
4733 const nsFrameSelection::FocusMode focusMode = [&]() {
4734 // If "Shift" and "Ctrl" are both pressed, "Shift" is given precedence. This
4735 // mimics the old behaviour.
4736 if (mouseEvent->IsShift()) {
4737 return nsFrameSelection::FocusMode::kExtendSelection;
4740 if (control) {
4741 return nsFrameSelection::FocusMode::kMultiRangeSelection;
4744 return nsFrameSelection::FocusMode::kCollapseToNewPoint;
4745 }();
4747 rv = fc->HandleClick(MOZ_KnownLive(offsets.content) /* bug 1636889 */,
4748 offsets.StartOffset(), offsets.EndOffset(), focusMode,
4749 offsets.associate);
4751 if (NS_FAILED(rv)) return rv;
4753 if (offsets.offset != offsets.secondaryOffset) fc->MaintainSelection();
4755 if (isEditor && !mouseEvent->IsShift() &&
4756 (offsets.EndOffset() - offsets.StartOffset()) == 1) {
4757 // A single node is selected and we aren't extending an existing
4758 // selection, which means the user clicked directly on an object (either
4759 // -moz-user-select: all or a non-text node without children).
4760 // Therefore, disable selection extension during mouse moves.
4761 // XXX This is a bit hacky; shouldn't editor be able to deal with this?
4762 fc->SetDragState(false);
4765 return rv;
4768 nsresult nsIFrame::SelectByTypeAtPoint(nsPresContext* aPresContext,
4769 const nsPoint& aPoint,
4770 nsSelectionAmount aBeginAmountType,
4771 nsSelectionAmount aEndAmountType,
4772 uint32_t aSelectFlags) {
4773 NS_ENSURE_ARG_POINTER(aPresContext);
4775 // No point in selecting if selection is turned off
4776 if (DetermineDisplaySelection() == nsISelectionController::SELECTION_OFF) {
4777 return NS_OK;
4780 ContentOffsets offsets = GetContentOffsetsFromPoint(aPoint, SKIP_HIDDEN);
4781 if (!offsets.content) return NS_ERROR_FAILURE;
4783 int32_t offset;
4784 nsIFrame* frame = nsFrameSelection::GetFrameForNodeOffset(
4785 offsets.content, offsets.offset, offsets.associate, &offset);
4786 if (!frame) {
4787 return NS_ERROR_FAILURE;
4789 return frame->PeekBackwardAndForward(aBeginAmountType, aEndAmountType, offset,
4790 aBeginAmountType != eSelectWord,
4791 aSelectFlags);
4795 * Multiple Mouse Press -- line or paragraph selection -- for the frame.
4796 * Wouldn't it be nice if this didn't have to be hardwired into Frame code?
4798 NS_IMETHODIMP
4799 nsIFrame::HandleMultiplePress(nsPresContext* aPresContext,
4800 WidgetGUIEvent* aEvent,
4801 nsEventStatus* aEventStatus, bool aControlHeld) {
4802 NS_ENSURE_ARG_POINTER(aEvent);
4803 NS_ENSURE_ARG_POINTER(aEventStatus);
4805 if (nsEventStatus_eConsumeNoDefault == *aEventStatus ||
4806 DetermineDisplaySelection() == nsISelectionController::SELECTION_OFF) {
4807 return NS_OK;
4810 // Find out whether we're doing line or paragraph selection.
4811 // If browser.triple_click_selects_paragraph is true, triple-click selects
4812 // paragraph. Otherwise, triple-click selects line, and quadruple-click
4813 // selects paragraph (on platforms that support quadruple-click).
4814 nsSelectionAmount beginAmount, endAmount;
4815 WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
4816 if (!mouseEvent) {
4817 return NS_OK;
4820 if (mouseEvent->mClickCount == 4) {
4821 beginAmount = endAmount = eSelectParagraph;
4822 } else if (mouseEvent->mClickCount == 3) {
4823 if (Preferences::GetBool("browser.triple_click_selects_paragraph")) {
4824 beginAmount = endAmount = eSelectParagraph;
4825 } else {
4826 beginAmount = eSelectBeginLine;
4827 endAmount = eSelectEndLine;
4829 } else if (mouseEvent->mClickCount == 2) {
4830 // We only want inline frames; PeekBackwardAndForward dislikes blocks
4831 beginAmount = endAmount = eSelectWord;
4832 } else {
4833 return NS_OK;
4836 nsPoint relPoint = nsLayoutUtils::GetEventCoordinatesRelativeTo(
4837 mouseEvent, RelativeTo{this});
4838 return SelectByTypeAtPoint(aPresContext, relPoint, beginAmount, endAmount,
4839 (aControlHeld ? SELECT_ACCUMULATE : 0));
4842 nsresult nsIFrame::PeekBackwardAndForward(nsSelectionAmount aAmountBack,
4843 nsSelectionAmount aAmountForward,
4844 int32_t aStartPos, bool aJumpLines,
4845 uint32_t aSelectFlags) {
4846 nsIFrame* baseFrame = this;
4847 int32_t baseOffset = aStartPos;
4848 nsresult rv;
4850 if (aAmountBack == eSelectWord) {
4851 // To avoid selecting the previous word when at start of word,
4852 // first move one character forward.
4853 nsPeekOffsetStruct pos(eSelectCharacter, eDirNext, aStartPos, nsPoint(0, 0),
4854 aJumpLines,
4855 true, // limit on scrolled views
4856 false, false, false);
4857 rv = PeekOffset(&pos);
4858 if (NS_SUCCEEDED(rv)) {
4859 baseFrame = pos.mResultFrame;
4860 baseOffset = pos.mContentOffset;
4864 // Use peek offset one way then the other:
4865 nsPeekOffsetStruct startpos(aAmountBack, eDirPrevious, baseOffset,
4866 nsPoint(0, 0), aJumpLines,
4867 true, // limit on scrolled views
4868 false, false, false);
4869 rv = baseFrame->PeekOffset(&startpos);
4870 if (NS_FAILED(rv)) return rv;
4872 nsPeekOffsetStruct endpos(aAmountForward, eDirNext, aStartPos, nsPoint(0, 0),
4873 aJumpLines,
4874 true, // limit on scrolled views
4875 false, false, false);
4876 rv = PeekOffset(&endpos);
4877 if (NS_FAILED(rv)) return rv;
4879 // Keep frameSelection alive.
4880 RefPtr<nsFrameSelection> frameSelection = GetFrameSelection();
4882 const nsFrameSelection::FocusMode focusMode =
4883 (aSelectFlags & SELECT_ACCUMULATE)
4884 ? nsFrameSelection::FocusMode::kMultiRangeSelection
4885 : nsFrameSelection::FocusMode::kCollapseToNewPoint;
4886 rv = frameSelection->HandleClick(
4887 MOZ_KnownLive(startpos.mResultContent) /* bug 1636889 */,
4888 startpos.mContentOffset, startpos.mContentOffset, focusMode,
4889 CARET_ASSOCIATE_AFTER);
4890 if (NS_FAILED(rv)) return rv;
4892 rv = frameSelection->HandleClick(
4893 MOZ_KnownLive(endpos.mResultContent) /* bug 1636889 */,
4894 endpos.mContentOffset, endpos.mContentOffset,
4895 nsFrameSelection::FocusMode::kExtendSelection, CARET_ASSOCIATE_BEFORE);
4896 if (NS_FAILED(rv)) return rv;
4898 // maintain selection
4899 return frameSelection->MaintainSelection(aAmountBack);
4902 NS_IMETHODIMP nsIFrame::HandleDrag(nsPresContext* aPresContext,
4903 WidgetGUIEvent* aEvent,
4904 nsEventStatus* aEventStatus) {
4905 MOZ_ASSERT(aEvent->mClass == eMouseEventClass,
4906 "HandleDrag can only handle mouse event");
4908 NS_ENSURE_ARG_POINTER(aEventStatus);
4910 if (*aEventStatus == nsEventStatus_eConsumeNoDefault) {
4911 return NS_OK;
4914 RefPtr<nsFrameSelection> frameselection = GetFrameSelection();
4915 if (!frameselection) {
4916 return NS_OK;
4919 bool mouseDown = frameselection->GetDragState();
4920 if (!mouseDown) {
4921 return NS_OK;
4924 nsIFrame* scrollbar =
4925 nsLayoutUtils::GetClosestFrameOfType(this, LayoutFrameType::Scrollbar);
4926 if (!scrollbar) {
4927 // XXX Do we really need to exclude non-selectable content here?
4928 // GetContentOffsetsFromPoint can handle it just fine, although some
4929 // other stuff might not like it.
4930 // NOTE: DetermineDisplaySelection() returns SELECTION_OFF for
4931 // non-selectable frames.
4932 if (DetermineDisplaySelection() == nsISelectionController::SELECTION_OFF) {
4933 return NS_OK;
4937 frameselection->StopAutoScrollTimer();
4939 // Check if we are dragging in a table cell
4940 nsCOMPtr<nsIContent> parentContent;
4941 int32_t contentOffset;
4942 TableSelectionMode target;
4943 WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
4944 mozilla::PresShell* presShell = aPresContext->PresShell();
4945 nsresult result;
4946 result = GetDataForTableSelection(frameselection, presShell, mouseEvent,
4947 getter_AddRefs(parentContent),
4948 &contentOffset, &target);
4950 AutoWeakFrame weakThis = this;
4951 if (NS_SUCCEEDED(result) && parentContent) {
4952 result = frameselection->HandleTableSelection(parentContent, contentOffset,
4953 target, mouseEvent);
4954 if (NS_WARN_IF(NS_FAILED(result))) {
4955 return result;
4957 } else {
4958 nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(mouseEvent,
4959 RelativeTo{this});
4960 frameselection->HandleDrag(this, pt);
4963 // The frameselection object notifies selection listeners synchronously above
4964 // which might have killed us.
4965 if (!weakThis.IsAlive()) {
4966 return NS_OK;
4969 // get the nearest scrollframe
4970 nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetNearestScrollableFrame(
4971 this, nsLayoutUtils::SCROLLABLE_SAME_DOC |
4972 nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN);
4974 if (scrollFrame) {
4975 nsIFrame* capturingFrame = scrollFrame->GetScrolledFrame();
4976 if (capturingFrame) {
4977 nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(
4978 mouseEvent, RelativeTo{capturingFrame});
4979 frameselection->StartAutoScrollTimer(capturingFrame, pt, 30);
4983 return NS_OK;
4987 * This static method handles part of the nsIFrame::HandleRelease in a way
4988 * which doesn't rely on the nsFrame object to stay alive.
4990 MOZ_CAN_RUN_SCRIPT_BOUNDARY static nsresult HandleFrameSelection(
4991 nsFrameSelection* aFrameSelection, nsIFrame::ContentOffsets& aOffsets,
4992 bool aHandleTableSel, int32_t aContentOffsetForTableSel,
4993 TableSelectionMode aTargetForTableSel,
4994 nsIContent* aParentContentForTableSel, WidgetGUIEvent* aEvent,
4995 const nsEventStatus* aEventStatus) {
4996 if (!aFrameSelection) {
4997 return NS_OK;
5000 nsresult rv = NS_OK;
5002 if (nsEventStatus_eConsumeNoDefault != *aEventStatus) {
5003 if (!aHandleTableSel) {
5004 if (!aOffsets.content || !aFrameSelection->HasDelayedCaretData()) {
5005 return NS_ERROR_FAILURE;
5008 // We are doing this to simulate what we would have done on HandlePress.
5009 // We didn't do it there to give the user an opportunity to drag
5010 // the text, but since they didn't drag, we want to place the
5011 // caret.
5012 // However, we'll use the mouse position from the release, since:
5013 // * it's easier
5014 // * that's the normal click position to use (although really, in
5015 // the normal case, small movements that don't count as a drag
5016 // can do selection)
5017 aFrameSelection->SetDragState(true);
5019 const nsFrameSelection::FocusMode focusMode =
5020 aFrameSelection->IsShiftDownInDelayedCaretData()
5021 ? nsFrameSelection::FocusMode::kExtendSelection
5022 : nsFrameSelection::FocusMode::kCollapseToNewPoint;
5023 rv = aFrameSelection->HandleClick(
5024 MOZ_KnownLive(aOffsets.content) /* bug 1636889 */,
5025 aOffsets.StartOffset(), aOffsets.EndOffset(), focusMode,
5026 aOffsets.associate);
5027 if (NS_FAILED(rv)) {
5028 return rv;
5030 } else if (aParentContentForTableSel) {
5031 aFrameSelection->SetDragState(false);
5032 rv = aFrameSelection->HandleTableSelection(
5033 aParentContentForTableSel, aContentOffsetForTableSel,
5034 aTargetForTableSel, aEvent->AsMouseEvent());
5035 if (NS_FAILED(rv)) {
5036 return rv;
5039 aFrameSelection->SetDelayedCaretData(0);
5042 aFrameSelection->SetDragState(false);
5043 aFrameSelection->StopAutoScrollTimer();
5045 return NS_OK;
5048 NS_IMETHODIMP nsIFrame::HandleRelease(nsPresContext* aPresContext,
5049 WidgetGUIEvent* aEvent,
5050 nsEventStatus* aEventStatus) {
5051 if (aEvent->mClass != eMouseEventClass) {
5052 return NS_OK;
5055 nsIFrame* activeFrame = GetActiveSelectionFrame(aPresContext, this);
5057 nsCOMPtr<nsIContent> captureContent = PresShell::GetCapturingContent();
5059 bool selectionOff =
5060 (DetermineDisplaySelection() == nsISelectionController::SELECTION_OFF);
5062 RefPtr<nsFrameSelection> frameselection;
5063 ContentOffsets offsets;
5064 nsCOMPtr<nsIContent> parentContent;
5065 int32_t contentOffsetForTableSel = 0;
5066 TableSelectionMode targetForTableSel = TableSelectionMode::None;
5067 bool handleTableSelection = true;
5069 if (!selectionOff) {
5070 frameselection = GetFrameSelection();
5071 if (nsEventStatus_eConsumeNoDefault != *aEventStatus && frameselection) {
5072 // Check if the frameselection recorded the mouse going down.
5073 // If not, the user must have clicked in a part of the selection.
5074 // Place the caret before continuing!
5076 if (frameselection->MouseDownRecorded()) {
5077 nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(
5078 aEvent, RelativeTo{this});
5079 offsets = GetContentOffsetsFromPoint(pt, SKIP_HIDDEN);
5080 handleTableSelection = false;
5081 } else {
5082 GetDataForTableSelection(frameselection, PresShell(),
5083 aEvent->AsMouseEvent(),
5084 getter_AddRefs(parentContent),
5085 &contentOffsetForTableSel, &targetForTableSel);
5090 // We might be capturing in some other document and the event just happened to
5091 // trickle down here. Make sure that document's frame selection is notified.
5092 // Note, this may cause the current nsFrame object to be deleted, bug 336592.
5093 RefPtr<nsFrameSelection> frameSelection;
5094 if (activeFrame != this && activeFrame->DetermineDisplaySelection() !=
5095 nsISelectionController::SELECTION_OFF) {
5096 frameSelection = activeFrame->GetFrameSelection();
5099 // Also check the selection of the capturing content which might be in a
5100 // different document.
5101 if (!frameSelection && captureContent) {
5102 if (Document* doc = captureContent->GetComposedDoc()) {
5103 mozilla::PresShell* capturingPresShell = doc->GetPresShell();
5104 if (capturingPresShell &&
5105 capturingPresShell != PresContext()->GetPresShell()) {
5106 frameSelection = capturingPresShell->FrameSelection();
5111 if (frameSelection) {
5112 AutoWeakFrame wf(this);
5113 frameSelection->SetDragState(false);
5114 frameSelection->StopAutoScrollTimer();
5115 if (wf.IsAlive()) {
5116 nsIScrollableFrame* scrollFrame =
5117 nsLayoutUtils::GetNearestScrollableFrame(
5118 this, nsLayoutUtils::SCROLLABLE_SAME_DOC |
5119 nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN);
5120 if (scrollFrame) {
5121 // Perform any additional scrolling needed to maintain CSS snap point
5122 // requirements when autoscrolling is over.
5123 scrollFrame->ScrollSnap();
5128 // Do not call any methods of the current object after this point!!!
5129 // The object is perhaps dead!
5131 return selectionOff ? NS_OK
5132 : HandleFrameSelection(
5133 frameselection, offsets, handleTableSelection,
5134 contentOffsetForTableSel, targetForTableSel,
5135 parentContent, aEvent, aEventStatus);
5138 struct MOZ_STACK_CLASS FrameContentRange {
5139 FrameContentRange(nsIContent* aContent, int32_t aStart, int32_t aEnd)
5140 : content(aContent), start(aStart), end(aEnd) {}
5141 nsCOMPtr<nsIContent> content;
5142 int32_t start;
5143 int32_t end;
5146 // Retrieve the content offsets of a frame
5147 static FrameContentRange GetRangeForFrame(const nsIFrame* aFrame) {
5148 nsIContent* content = aFrame->GetContent();
5149 if (!content) {
5150 NS_WARNING("Frame has no content");
5151 return FrameContentRange(nullptr, -1, -1);
5154 LayoutFrameType type = aFrame->Type();
5155 if (type == LayoutFrameType::Text) {
5156 int32_t offset, offsetEnd;
5157 aFrame->GetOffsets(offset, offsetEnd);
5158 return FrameContentRange(content, offset, offsetEnd);
5161 if (type == LayoutFrameType::Br) {
5162 nsIContent* parent = content->GetParent();
5163 int32_t beginOffset = parent->ComputeIndexOf(content);
5164 return FrameContentRange(parent, beginOffset, beginOffset);
5167 while (content->IsRootOfNativeAnonymousSubtree()) {
5168 content = content->GetParent();
5171 nsIContent* parent = content->GetParent();
5172 if (aFrame->IsBlockFrameOrSubclass() || !parent) {
5173 return FrameContentRange(content, 0, content->GetChildCount());
5176 // TODO(emilio): Revise this in presence of Shadow DOM / display: contents,
5177 // it's likely that we don't want to just walk the light tree, and we need to
5178 // change the representation of FrameContentRange.
5179 int32_t index = parent->ComputeIndexOf(content);
5180 MOZ_ASSERT(index >= 0);
5181 return FrameContentRange(parent, index, index + 1);
5184 // The FrameTarget represents the closest frame to a point that can be selected
5185 // The frame is the frame represented, frameEdge says whether one end of the
5186 // frame is the result (in which case different handling is needed), and
5187 // afterFrame says which end is repersented if frameEdge is true
5188 struct FrameTarget {
5189 FrameTarget(nsIFrame* aFrame, bool aFrameEdge, bool aAfterFrame)
5190 : frame(aFrame), frameEdge(aFrameEdge), afterFrame(aAfterFrame) {}
5192 static FrameTarget Null() { return FrameTarget(nullptr, false, false); }
5194 bool IsNull() { return !frame; }
5195 nsIFrame* frame;
5196 bool frameEdge;
5197 bool afterFrame;
5200 // See function implementation for information
5201 static FrameTarget GetSelectionClosestFrame(nsIFrame* aFrame,
5202 const nsPoint& aPoint,
5203 uint32_t aFlags);
5205 static bool SelfIsSelectable(nsIFrame* aFrame, uint32_t aFlags) {
5206 if ((aFlags & nsIFrame::SKIP_HIDDEN) &&
5207 !aFrame->StyleVisibility()->IsVisible()) {
5208 return false;
5210 return !aFrame->IsGeneratedContentFrame() &&
5211 aFrame->StyleUIReset()->mUserSelect != StyleUserSelect::None;
5214 static bool SelectionDescendToKids(nsIFrame* aFrame) {
5215 // If we are only near (not directly over) then don't traverse
5216 // frames with independent selection (e.g. text and list controls, see bug
5217 // 268497). Note that this prevents any of the users of this method from
5218 // entering form controls.
5219 // XXX We might want some way to allow using the up-arrow to go into a form
5220 // control, but the focus didn't work right anyway; it'd probably be enough
5221 // if the left and right arrows could enter textboxes (which I don't believe
5222 // they can at the moment)
5223 if (aFrame->IsTextInputFrame() || aFrame->IsListControlFrame()) {
5224 MOZ_ASSERT(aFrame->HasAnyStateBits(NS_FRAME_INDEPENDENT_SELECTION));
5225 return false;
5228 // Failure in this assertion means a new type of frame forms the root of an
5229 // NS_FRAME_INDEPENDENT_SELECTION subtree. In such case, the condition above
5230 // should be changed to handle it.
5231 MOZ_ASSERT_IF(
5232 aFrame->HasAnyStateBits(NS_FRAME_INDEPENDENT_SELECTION),
5233 aFrame->GetParent()->HasAnyStateBits(NS_FRAME_INDEPENDENT_SELECTION));
5235 if (aFrame->IsGeneratedContentFrame()) {
5236 return false;
5239 auto style = aFrame->StyleUIReset()->mUserSelect;
5240 return style != StyleUserSelect::All && style != StyleUserSelect::None;
5243 static FrameTarget GetSelectionClosestFrameForChild(nsIFrame* aChild,
5244 const nsPoint& aPoint,
5245 uint32_t aFlags) {
5246 nsIFrame* parent = aChild->GetParent();
5247 if (SelectionDescendToKids(aChild)) {
5248 nsPoint pt = aPoint - aChild->GetOffsetTo(parent);
5249 return GetSelectionClosestFrame(aChild, pt, aFlags);
5251 return FrameTarget(aChild, false, false);
5254 // When the cursor needs to be at the beginning of a block, it shouldn't be
5255 // before the first child. A click on a block whose first child is a block
5256 // should put the cursor in the child. The cursor shouldn't be between the
5257 // blocks, because that's not where it's expected.
5258 // Note that this method is guaranteed to succeed.
5259 static FrameTarget DrillDownToSelectionFrame(nsIFrame* aFrame, bool aEndFrame,
5260 uint32_t aFlags) {
5261 if (SelectionDescendToKids(aFrame)) {
5262 nsIFrame* result = nullptr;
5263 nsIFrame* frame = aFrame->PrincipalChildList().FirstChild();
5264 if (!aEndFrame) {
5265 while (frame && (!SelfIsSelectable(frame, aFlags) || frame->IsEmpty()))
5266 frame = frame->GetNextSibling();
5267 if (frame) result = frame;
5268 } else {
5269 // Because the frame tree is singly linked, to find the last frame,
5270 // we have to iterate through all the frames
5271 // XXX I have a feeling this could be slow for long blocks, although
5272 // I can't find any slowdowns
5273 while (frame) {
5274 if (!frame->IsEmpty() && SelfIsSelectable(frame, aFlags))
5275 result = frame;
5276 frame = frame->GetNextSibling();
5279 if (result) return DrillDownToSelectionFrame(result, aEndFrame, aFlags);
5281 // If the current frame has no targetable children, target the current frame
5282 return FrameTarget(aFrame, true, aEndFrame);
5285 // This method finds the closest valid FrameTarget on a given line; if there is
5286 // no valid FrameTarget on the line, it returns a null FrameTarget
5287 static FrameTarget GetSelectionClosestFrameForLine(
5288 nsBlockFrame* aParent, nsBlockFrame::LineIterator aLine,
5289 const nsPoint& aPoint, uint32_t aFlags) {
5290 // Account for end of lines (any iterator from the block is valid)
5291 if (aLine == aParent->LinesEnd())
5292 return DrillDownToSelectionFrame(aParent, true, aFlags);
5293 nsIFrame* frame = aLine->mFirstChild;
5294 nsIFrame* closestFromIStart = nullptr;
5295 nsIFrame* closestFromIEnd = nullptr;
5296 nscoord closestIStart = aLine->IStart(), closestIEnd = aLine->IEnd();
5297 WritingMode wm = aLine->mWritingMode;
5298 LogicalPoint pt(wm, aPoint, aLine->mContainerSize);
5299 bool canSkipBr = false;
5300 bool lastFrameWasEditable = false;
5301 for (int32_t n = aLine->GetChildCount(); n;
5302 --n, frame = frame->GetNextSibling()) {
5303 // Skip brFrames. Can only skip if the line contains at least
5304 // one selectable and non-empty frame before. Also, avoid skipping brs if
5305 // the previous thing had a different editableness than us, since then we
5306 // may end up not being able to select after it if the br is the last thing
5307 // on the line.
5308 if (!SelfIsSelectable(frame, aFlags) || frame->IsEmpty() ||
5309 (canSkipBr && frame->IsBrFrame() &&
5310 lastFrameWasEditable == frame->GetContent()->IsEditable())) {
5311 continue;
5313 canSkipBr = true;
5314 lastFrameWasEditable =
5315 frame->GetContent() && frame->GetContent()->IsEditable();
5316 LogicalRect frameRect =
5317 LogicalRect(wm, frame->GetRect(), aLine->mContainerSize);
5318 if (pt.I(wm) >= frameRect.IStart(wm)) {
5319 if (pt.I(wm) < frameRect.IEnd(wm)) {
5320 return GetSelectionClosestFrameForChild(frame, aPoint, aFlags);
5322 if (frameRect.IEnd(wm) >= closestIStart) {
5323 closestFromIStart = frame;
5324 closestIStart = frameRect.IEnd(wm);
5326 } else {
5327 if (frameRect.IStart(wm) <= closestIEnd) {
5328 closestFromIEnd = frame;
5329 closestIEnd = frameRect.IStart(wm);
5333 if (!closestFromIStart && !closestFromIEnd) {
5334 // We should only get here if there are no selectable frames on a line
5335 // XXX Do we need more elaborate handling here?
5336 return FrameTarget::Null();
5338 if (closestFromIStart &&
5339 (!closestFromIEnd ||
5340 (abs(pt.I(wm) - closestIStart) <= abs(pt.I(wm) - closestIEnd)))) {
5341 return GetSelectionClosestFrameForChild(closestFromIStart, aPoint, aFlags);
5343 return GetSelectionClosestFrameForChild(closestFromIEnd, aPoint, aFlags);
5346 // This method is for the special handling we do for block frames; they're
5347 // special because they represent paragraphs and because they are organized
5348 // into lines, which have bounds that are not stored elsewhere in the
5349 // frame tree. Returns a null FrameTarget for frames which are not
5350 // blocks or blocks with no lines except editable one.
5351 static FrameTarget GetSelectionClosestFrameForBlock(nsIFrame* aFrame,
5352 const nsPoint& aPoint,
5353 uint32_t aFlags) {
5354 nsBlockFrame* bf = do_QueryFrame(aFrame);
5355 if (!bf) return FrameTarget::Null();
5357 // This code searches for the correct line
5358 nsBlockFrame::LineIterator end = bf->LinesEnd();
5359 nsBlockFrame::LineIterator curLine = bf->LinesBegin();
5360 nsBlockFrame::LineIterator closestLine = end;
5362 if (curLine != end) {
5363 // Convert aPoint into a LogicalPoint in the writing-mode of this block
5364 WritingMode wm = curLine->mWritingMode;
5365 LogicalPoint pt(wm, aPoint, curLine->mContainerSize);
5366 do {
5367 // Check to see if our point lies within the line's block-direction bounds
5368 nscoord BCoord = pt.B(wm) - curLine->BStart();
5369 nscoord BSize = curLine->BSize();
5370 if (BCoord >= 0 && BCoord < BSize) {
5371 closestLine = curLine;
5372 break; // We found the line; stop looking
5374 if (BCoord < 0) break;
5375 ++curLine;
5376 } while (curLine != end);
5378 if (closestLine == end) {
5379 nsBlockFrame::LineIterator prevLine = curLine.prev();
5380 nsBlockFrame::LineIterator nextLine = curLine;
5381 // Avoid empty lines
5382 while (nextLine != end && nextLine->IsEmpty()) ++nextLine;
5383 while (prevLine != end && prevLine->IsEmpty()) --prevLine;
5385 // This hidden pref dictates whether a point above or below all lines
5386 // comes up with a line or the beginning or end of the frame; 0 on
5387 // Windows, 1 on other platforms by default at the writing of this code
5388 int32_t dragOutOfFrame =
5389 Preferences::GetInt("browser.drag_out_of_frame_style");
5391 if (prevLine == end) {
5392 if (dragOutOfFrame == 1 || nextLine == end)
5393 return DrillDownToSelectionFrame(aFrame, false, aFlags);
5394 closestLine = nextLine;
5395 } else if (nextLine == end) {
5396 if (dragOutOfFrame == 1)
5397 return DrillDownToSelectionFrame(aFrame, true, aFlags);
5398 closestLine = prevLine;
5399 } else { // Figure out which line is closer
5400 if (pt.B(wm) - prevLine->BEnd() < nextLine->BStart() - pt.B(wm))
5401 closestLine = prevLine;
5402 else
5403 closestLine = nextLine;
5408 do {
5409 FrameTarget target =
5410 GetSelectionClosestFrameForLine(bf, closestLine, aPoint, aFlags);
5411 if (!target.IsNull()) return target;
5412 ++closestLine;
5413 } while (closestLine != end);
5415 // Fall back to just targeting the last targetable place
5416 return DrillDownToSelectionFrame(aFrame, true, aFlags);
5419 // GetSelectionClosestFrame is the helper function that calculates the closest
5420 // frame to the given point.
5421 // It doesn't completely account for offset styles, so needs to be used in
5422 // restricted environments.
5423 // Cannot handle overlapping frames correctly, so it should receive the output
5424 // of GetFrameForPoint
5425 // Guaranteed to return a valid FrameTarget
5426 static FrameTarget GetSelectionClosestFrame(nsIFrame* aFrame,
5427 const nsPoint& aPoint,
5428 uint32_t aFlags) {
5430 // Handle blocks; if the frame isn't a block, the method fails
5431 FrameTarget target =
5432 GetSelectionClosestFrameForBlock(aFrame, aPoint, aFlags);
5433 if (!target.IsNull()) return target;
5436 if (nsIFrame* kid = aFrame->PrincipalChildList().FirstChild()) {
5437 // Go through all the child frames to find the closest one
5438 nsIFrame::FrameWithDistance closest = {nullptr, nscoord_MAX, nscoord_MAX};
5439 for (; kid; kid = kid->GetNextSibling()) {
5440 if (!SelfIsSelectable(kid, aFlags) || kid->IsEmpty()) continue;
5442 kid->FindCloserFrameForSelection(aPoint, &closest);
5444 if (closest.mFrame) {
5445 if (SVGUtils::IsInSVGTextSubtree(closest.mFrame))
5446 return FrameTarget(closest.mFrame, false, false);
5447 return GetSelectionClosestFrameForChild(closest.mFrame, aPoint, aFlags);
5451 // Use frame edge for grid, flex, table, and non-editable image frames.
5452 const bool useFrameEdge =
5453 aFrame->IsFlexOrGridContainer() || aFrame->IsTableFrame() ||
5454 (static_cast<nsImageFrame*>(do_QueryFrame(aFrame)) &&
5455 !aFrame->GetContent()->IsEditable());
5456 return FrameTarget(aFrame, useFrameEdge, false);
5459 static nsIFrame::ContentOffsets OffsetsForSingleFrame(nsIFrame* aFrame,
5460 const nsPoint& aPoint) {
5461 nsIFrame::ContentOffsets offsets;
5462 FrameContentRange range = GetRangeForFrame(aFrame);
5463 offsets.content = range.content;
5464 // If there are continuations (meaning it's not one rectangle), this is the
5465 // best this function can do
5466 if (aFrame->GetNextContinuation() || aFrame->GetPrevContinuation()) {
5467 offsets.offset = range.start;
5468 offsets.secondaryOffset = range.end;
5469 offsets.associate = CARET_ASSOCIATE_AFTER;
5470 return offsets;
5473 // Figure out whether the offsets should be over, after, or before the frame
5474 nsRect rect(nsPoint(0, 0), aFrame->GetSize());
5476 bool isBlock = !aFrame->StyleDisplay()->IsInlineFlow();
5477 bool isRtl = (aFrame->StyleVisibility()->mDirection == StyleDirection::Rtl);
5478 if ((isBlock && rect.y < aPoint.y) ||
5479 (!isBlock && ((isRtl && rect.x + rect.width / 2 > aPoint.x) ||
5480 (!isRtl && rect.x + rect.width / 2 < aPoint.x)))) {
5481 offsets.offset = range.end;
5482 if (rect.Contains(aPoint))
5483 offsets.secondaryOffset = range.start;
5484 else
5485 offsets.secondaryOffset = range.end;
5486 } else {
5487 offsets.offset = range.start;
5488 if (rect.Contains(aPoint))
5489 offsets.secondaryOffset = range.end;
5490 else
5491 offsets.secondaryOffset = range.start;
5493 offsets.associate = offsets.offset == range.start ? CARET_ASSOCIATE_AFTER
5494 : CARET_ASSOCIATE_BEFORE;
5495 return offsets;
5498 static nsIFrame* AdjustFrameForSelectionStyles(nsIFrame* aFrame) {
5499 nsIFrame* adjustedFrame = aFrame;
5500 for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) {
5501 // These are the conditions that make all children not able to handle
5502 // a cursor.
5503 StyleUserSelect userSelect = frame->StyleUIReset()->mUserSelect;
5504 if (userSelect != StyleUserSelect::Auto &&
5505 userSelect != StyleUserSelect::All) {
5506 break;
5508 if (userSelect == StyleUserSelect::All ||
5509 frame->IsGeneratedContentFrame()) {
5510 adjustedFrame = frame;
5513 return adjustedFrame;
5516 nsIFrame::ContentOffsets nsIFrame::GetContentOffsetsFromPoint(
5517 const nsPoint& aPoint, uint32_t aFlags) {
5518 nsIFrame* adjustedFrame;
5519 if (aFlags & IGNORE_SELECTION_STYLE) {
5520 adjustedFrame = this;
5521 } else {
5522 // This section of code deals with special selection styles. Note that
5523 // -moz-all exists, even though it doesn't need to be explicitly handled.
5525 // The offset is forced not to end up in generated content; content offsets
5526 // cannot represent content outside of the document's content tree.
5528 adjustedFrame = AdjustFrameForSelectionStyles(this);
5530 // -moz-user-select: all needs special handling, because clicking on it
5531 // should lead to the whole frame being selected
5532 if (adjustedFrame->StyleUIReset()->mUserSelect == StyleUserSelect::All) {
5533 nsPoint adjustedPoint = aPoint + this->GetOffsetTo(adjustedFrame);
5534 return OffsetsForSingleFrame(adjustedFrame, adjustedPoint);
5537 // For other cases, try to find a closest frame starting from the parent of
5538 // the unselectable frame
5539 if (adjustedFrame != this) adjustedFrame = adjustedFrame->GetParent();
5542 nsPoint adjustedPoint = aPoint + this->GetOffsetTo(adjustedFrame);
5544 FrameTarget closest =
5545 GetSelectionClosestFrame(adjustedFrame, adjustedPoint, aFlags);
5547 // If the correct offset is at one end of a frame, use offset-based
5548 // calculation method
5549 if (closest.frameEdge) {
5550 ContentOffsets offsets;
5551 FrameContentRange range = GetRangeForFrame(closest.frame);
5552 offsets.content = range.content;
5553 if (closest.afterFrame)
5554 offsets.offset = range.end;
5555 else
5556 offsets.offset = range.start;
5557 offsets.secondaryOffset = offsets.offset;
5558 offsets.associate = offsets.offset == range.start ? CARET_ASSOCIATE_AFTER
5559 : CARET_ASSOCIATE_BEFORE;
5560 return offsets;
5563 nsPoint pt;
5564 if (closest.frame != this) {
5565 if (SVGUtils::IsInSVGTextSubtree(closest.frame)) {
5566 pt = nsLayoutUtils::TransformAncestorPointToFrame(
5567 RelativeTo{closest.frame}, aPoint, RelativeTo{this});
5568 } else {
5569 pt = aPoint - closest.frame->GetOffsetTo(this);
5571 } else {
5572 pt = aPoint;
5574 return closest.frame->CalcContentOffsetsFromFramePoint(pt);
5576 // XXX should I add some kind of offset standardization?
5577 // consider <b>xxxxx</b><i>zzzzz</i>; should any click between the last
5578 // x and first z put the cursor in the same logical position in addition
5579 // to the same visual position?
5582 nsIFrame::ContentOffsets nsIFrame::CalcContentOffsetsFromFramePoint(
5583 const nsPoint& aPoint) {
5584 return OffsetsForSingleFrame(this, aPoint);
5587 bool nsIFrame::AssociateImage(const StyleImage& aImage) {
5588 imgRequestProxy* req = aImage.GetImageRequest();
5589 if (!req) {
5590 return false;
5593 mozilla::css::ImageLoader* loader =
5594 PresContext()->Document()->StyleImageLoader();
5596 loader->AssociateRequestToFrame(req, this);
5597 return true;
5600 void nsIFrame::DisassociateImage(const StyleImage& aImage) {
5601 imgRequestProxy* req = aImage.GetImageRequest();
5602 if (!req) {
5603 return;
5606 mozilla::css::ImageLoader* loader =
5607 PresContext()->Document()->StyleImageLoader();
5609 loader->DisassociateRequestFromFrame(req, this);
5612 Maybe<nsIFrame::Cursor> nsIFrame::GetCursor(const nsPoint&) {
5613 StyleCursorKind kind = StyleUI()->mCursor.keyword;
5614 if (kind == StyleCursorKind::Auto) {
5615 // If this is editable, I-beam cursor is better for most elements.
5616 kind = (mContent && mContent->IsEditable()) ? StyleCursorKind::Text
5617 : StyleCursorKind::Default;
5619 if (kind == StyleCursorKind::Text && GetWritingMode().IsVertical()) {
5620 // Per CSS UI spec, UA may treat value 'text' as
5621 // 'vertical-text' for vertical text.
5622 kind = StyleCursorKind::VerticalText;
5625 return Some(Cursor{kind, AllowCustomCursorImage::Yes});
5628 // Resize and incremental reflow
5630 /* virtual */
5631 void nsIFrame::MarkIntrinsicISizesDirty() {
5632 // This version is meant only for what used to be box-to-block adaptors.
5633 // It should not be called by other derived classes.
5634 if (::IsXULBoxWrapped(this)) {
5635 nsBoxLayoutMetrics* metrics = BoxMetrics();
5637 XULSizeNeedsRecalc(metrics->mPrefSize);
5638 XULSizeNeedsRecalc(metrics->mMinSize);
5639 XULSizeNeedsRecalc(metrics->mMaxSize);
5640 XULSizeNeedsRecalc(metrics->mBlockPrefSize);
5641 XULSizeNeedsRecalc(metrics->mBlockMinSize);
5642 XULCoordNeedsRecalc(metrics->mFlex);
5643 XULCoordNeedsRecalc(metrics->mAscent);
5646 // If we're a flex item, clear our flex-item-specific cached measurements
5647 // (which likely depended on our now-stale intrinsic isize).
5648 if (IsFlexItem()) {
5649 nsFlexContainerFrame::MarkCachedFlexMeasurementsDirty(this);
5652 if (HasAnyStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT)) {
5653 nsFontInflationData::MarkFontInflationDataTextDirty(this);
5657 void nsIFrame::MarkSubtreeDirty() {
5658 if (HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
5659 return;
5661 // Unconditionally mark given frame dirty.
5662 AddStateBits(NS_FRAME_IS_DIRTY);
5664 // Mark all descendants dirty, unless:
5665 // - Already dirty.
5666 // - TableColGroup
5667 // - XULBox
5668 AutoTArray<nsIFrame*, 32> stack;
5669 for (const auto& childLists : ChildLists()) {
5670 for (nsIFrame* kid : childLists.mList) {
5671 stack.AppendElement(kid);
5674 while (!stack.IsEmpty()) {
5675 nsIFrame* f = stack.PopLastElement();
5676 if (f->HasAnyStateBits(NS_FRAME_IS_DIRTY) || f->IsTableColGroupFrame() ||
5677 f->IsXULBoxFrame()) {
5678 continue;
5681 f->AddStateBits(NS_FRAME_IS_DIRTY);
5683 for (const auto& childLists : f->ChildLists()) {
5684 for (nsIFrame* kid : childLists.mList) {
5685 stack.AppendElement(kid);
5691 /* virtual */
5692 nscoord nsIFrame::GetMinISize(gfxContext* aRenderingContext) {
5693 nscoord result = 0;
5694 DISPLAY_MIN_INLINE_SIZE(this, result);
5695 return result;
5698 /* virtual */
5699 nscoord nsIFrame::GetPrefISize(gfxContext* aRenderingContext) {
5700 nscoord result = 0;
5701 DISPLAY_PREF_INLINE_SIZE(this, result);
5702 return result;
5705 /* virtual */
5706 void nsIFrame::AddInlineMinISize(gfxContext* aRenderingContext,
5707 nsIFrame::InlineMinISizeData* aData) {
5708 nscoord isize = nsLayoutUtils::IntrinsicForContainer(
5709 aRenderingContext, this, IntrinsicISizeType::MinISize);
5710 aData->DefaultAddInlineMinISize(this, isize);
5713 /* virtual */
5714 void nsIFrame::AddInlinePrefISize(gfxContext* aRenderingContext,
5715 nsIFrame::InlinePrefISizeData* aData) {
5716 nscoord isize = nsLayoutUtils::IntrinsicForContainer(
5717 aRenderingContext, this, IntrinsicISizeType::PrefISize);
5718 aData->DefaultAddInlinePrefISize(isize);
5721 void nsIFrame::InlineMinISizeData::DefaultAddInlineMinISize(nsIFrame* aFrame,
5722 nscoord aISize,
5723 bool aAllowBreak) {
5724 auto parent = aFrame->GetParent();
5725 MOZ_ASSERT(parent, "Must have a parent if we get here!");
5726 const bool mayBreak = aAllowBreak && !aFrame->CanContinueTextRun() &&
5727 !parent->Style()->ShouldSuppressLineBreak() &&
5728 parent->StyleText()->WhiteSpaceCanWrap(parent);
5729 if (mayBreak) {
5730 OptionallyBreak();
5732 mTrailingWhitespace = 0;
5733 mSkipWhitespace = false;
5734 mCurrentLine += aISize;
5735 mAtStartOfLine = false;
5736 if (mayBreak) {
5737 OptionallyBreak();
5741 void nsIFrame::InlinePrefISizeData::DefaultAddInlinePrefISize(nscoord aISize) {
5742 mCurrentLine = NSCoordSaturatingAdd(mCurrentLine, aISize);
5743 mTrailingWhitespace = 0;
5744 mSkipWhitespace = false;
5745 mLineIsEmpty = false;
5748 void nsIFrame::InlineMinISizeData::ForceBreak() {
5749 mCurrentLine -= mTrailingWhitespace;
5750 mPrevLines = std::max(mPrevLines, mCurrentLine);
5751 mCurrentLine = mTrailingWhitespace = 0;
5753 for (uint32_t i = 0, i_end = mFloats.Length(); i != i_end; ++i) {
5754 nscoord float_min = mFloats[i].Width();
5755 if (float_min > mPrevLines) mPrevLines = float_min;
5757 mFloats.Clear();
5758 mSkipWhitespace = true;
5761 void nsIFrame::InlineMinISizeData::OptionallyBreak(nscoord aHyphenWidth) {
5762 // If we can fit more content into a smaller width by staying on this
5763 // line (because we're still at a negative offset due to negative
5764 // text-indent or negative margin), don't break. Otherwise, do the
5765 // same as ForceBreak. it doesn't really matter when we accumulate
5766 // floats.
5767 if (mCurrentLine + aHyphenWidth < 0 || mAtStartOfLine) return;
5768 mCurrentLine += aHyphenWidth;
5769 ForceBreak();
5772 void nsIFrame::InlinePrefISizeData::ForceBreak(StyleClear aBreakType) {
5773 MOZ_ASSERT(aBreakType == StyleClear::None || aBreakType == StyleClear::Both ||
5774 aBreakType == StyleClear::Left ||
5775 aBreakType == StyleClear::Right,
5776 "Must be a physical break type");
5778 // If this force break is not clearing any float, we can leave all the
5779 // floats to the next force break.
5780 if (mFloats.Length() != 0 && aBreakType != StyleClear::None) {
5781 // preferred widths accumulated for floats that have already
5782 // been cleared past
5783 nscoord floats_done = 0,
5784 // preferred widths accumulated for floats that have not yet
5785 // been cleared past
5786 floats_cur_left = 0, floats_cur_right = 0;
5788 for (uint32_t i = 0, i_end = mFloats.Length(); i != i_end; ++i) {
5789 const FloatInfo& floatInfo = mFloats[i];
5790 const nsStyleDisplay* floatDisp = floatInfo.Frame()->StyleDisplay();
5791 StyleClear breakType = floatDisp->mBreakType;
5792 if (breakType == StyleClear::Left || breakType == StyleClear::Right ||
5793 breakType == StyleClear::Both) {
5794 nscoord floats_cur =
5795 NSCoordSaturatingAdd(floats_cur_left, floats_cur_right);
5796 if (floats_cur > floats_done) {
5797 floats_done = floats_cur;
5799 if (breakType != StyleClear::Right) {
5800 floats_cur_left = 0;
5802 if (breakType != StyleClear::Left) {
5803 floats_cur_right = 0;
5807 StyleFloat floatStyle = floatDisp->mFloat;
5808 nscoord& floats_cur =
5809 floatStyle == StyleFloat::Left ? floats_cur_left : floats_cur_right;
5810 nscoord floatWidth = floatInfo.Width();
5811 // Negative-width floats don't change the available space so they
5812 // shouldn't change our intrinsic line width either.
5813 floats_cur = NSCoordSaturatingAdd(floats_cur, std::max(0, floatWidth));
5816 nscoord floats_cur =
5817 NSCoordSaturatingAdd(floats_cur_left, floats_cur_right);
5818 if (floats_cur > floats_done) floats_done = floats_cur;
5820 mCurrentLine = NSCoordSaturatingAdd(mCurrentLine, floats_done);
5822 if (aBreakType == StyleClear::Both) {
5823 mFloats.Clear();
5824 } else {
5825 // If the break type does not clear all floats, it means there may
5826 // be some floats whose isize should contribute to the intrinsic
5827 // isize of the next line. The code here scans the current mFloats
5828 // and keeps floats which are not cleared by this break. Note that
5829 // floats may be cleared directly or indirectly. See below.
5830 nsTArray<FloatInfo> newFloats;
5831 MOZ_ASSERT(
5832 aBreakType == StyleClear::Left || aBreakType == StyleClear::Right,
5833 "Other values should have been handled in other branches");
5834 StyleFloat clearFloatType =
5835 aBreakType == StyleClear::Left ? StyleFloat::Left : StyleFloat::Right;
5836 // Iterate the array in reverse so that we can stop when there are
5837 // no longer any floats we need to keep. See below.
5838 for (FloatInfo& floatInfo : Reversed(mFloats)) {
5839 const nsStyleDisplay* floatDisp = floatInfo.Frame()->StyleDisplay();
5840 if (floatDisp->mFloat != clearFloatType) {
5841 newFloats.AppendElement(floatInfo);
5842 } else {
5843 // This is a float on the side that this break directly clears
5844 // which means we're not keeping it in mFloats. However, if
5845 // this float clears floats on the opposite side (via a value
5846 // of either 'both' or one of 'left'/'right'), any remaining
5847 // (earlier) floats on that side would be indirectly cleared
5848 // as well. Thus, we should break out of this loop and stop
5849 // considering earlier floats to be kept in mFloats.
5850 StyleClear floatBreakType = floatDisp->mBreakType;
5851 if (floatBreakType != aBreakType &&
5852 floatBreakType != StyleClear::None) {
5853 break;
5857 newFloats.Reverse();
5858 mFloats = std::move(newFloats);
5862 mCurrentLine =
5863 NSCoordSaturatingSubtract(mCurrentLine, mTrailingWhitespace, nscoord_MAX);
5864 mPrevLines = std::max(mPrevLines, mCurrentLine);
5865 mCurrentLine = mTrailingWhitespace = 0;
5866 mSkipWhitespace = true;
5867 mLineIsEmpty = true;
5870 static nscoord ResolveMargin(const LengthPercentageOrAuto& aStyle,
5871 nscoord aPercentageBasis) {
5872 if (aStyle.IsAuto()) {
5873 return nscoord(0);
5875 return nsLayoutUtils::ResolveToLength<false>(aStyle.AsLengthPercentage(),
5876 aPercentageBasis);
5879 static nscoord ResolvePadding(const LengthPercentage& aStyle,
5880 nscoord aPercentageBasis) {
5881 return nsLayoutUtils::ResolveToLength<true>(aStyle, aPercentageBasis);
5884 static nsIFrame::IntrinsicSizeOffsetData IntrinsicSizeOffsets(
5885 nsIFrame* aFrame, nscoord aPercentageBasis, bool aForISize) {
5886 nsIFrame::IntrinsicSizeOffsetData result;
5887 WritingMode wm = aFrame->GetWritingMode();
5888 const auto& margin = aFrame->StyleMargin()->mMargin;
5889 bool verticalAxis = aForISize == wm.IsVertical();
5890 if (verticalAxis) {
5891 result.margin += ResolveMargin(margin.Get(eSideTop), aPercentageBasis);
5892 result.margin += ResolveMargin(margin.Get(eSideBottom), aPercentageBasis);
5893 } else {
5894 result.margin += ResolveMargin(margin.Get(eSideLeft), aPercentageBasis);
5895 result.margin += ResolveMargin(margin.Get(eSideRight), aPercentageBasis);
5898 const auto& padding = aFrame->StylePadding()->mPadding;
5899 if (verticalAxis) {
5900 result.padding += ResolvePadding(padding.Get(eSideTop), aPercentageBasis);
5901 result.padding +=
5902 ResolvePadding(padding.Get(eSideBottom), aPercentageBasis);
5903 } else {
5904 result.padding += ResolvePadding(padding.Get(eSideLeft), aPercentageBasis);
5905 result.padding += ResolvePadding(padding.Get(eSideRight), aPercentageBasis);
5908 const nsStyleBorder* styleBorder = aFrame->StyleBorder();
5909 if (verticalAxis) {
5910 result.border += styleBorder->GetComputedBorderWidth(eSideTop);
5911 result.border += styleBorder->GetComputedBorderWidth(eSideBottom);
5912 } else {
5913 result.border += styleBorder->GetComputedBorderWidth(eSideLeft);
5914 result.border += styleBorder->GetComputedBorderWidth(eSideRight);
5917 const nsStyleDisplay* disp = aFrame->StyleDisplay();
5918 if (aFrame->IsThemed(disp)) {
5919 nsPresContext* presContext = aFrame->PresContext();
5921 LayoutDeviceIntMargin border = presContext->Theme()->GetWidgetBorder(
5922 presContext->DeviceContext(), aFrame, disp->EffectiveAppearance());
5923 result.border = presContext->DevPixelsToAppUnits(
5924 verticalAxis ? border.TopBottom() : border.LeftRight());
5926 LayoutDeviceIntMargin padding;
5927 if (presContext->Theme()->GetWidgetPadding(
5928 presContext->DeviceContext(), aFrame, disp->EffectiveAppearance(),
5929 &padding)) {
5930 result.padding = presContext->DevPixelsToAppUnits(
5931 verticalAxis ? padding.TopBottom() : padding.LeftRight());
5934 return result;
5937 /* virtual */ nsIFrame::IntrinsicSizeOffsetData nsIFrame::IntrinsicISizeOffsets(
5938 nscoord aPercentageBasis) {
5939 return IntrinsicSizeOffsets(this, aPercentageBasis, true);
5942 nsIFrame::IntrinsicSizeOffsetData nsIFrame::IntrinsicBSizeOffsets(
5943 nscoord aPercentageBasis) {
5944 return IntrinsicSizeOffsets(this, aPercentageBasis, false);
5947 /* virtual */
5948 IntrinsicSize nsIFrame::GetIntrinsicSize() {
5949 return IntrinsicSize(); // default is width/height set to eStyleUnit_None
5952 AspectRatio nsIFrame::GetAspectRatio() const {
5953 // Per spec, 'aspect-ratio' property applies to all elements except inline
5954 // boxes and internal ruby or table boxes.
5955 // https://drafts.csswg.org/css-sizing-4/#aspect-ratio
5956 // For those frame types that don't support aspect-ratio, they must not have
5957 // the natural ratio, so this early return is fine.
5958 if (!IsFrameOfType(eSupportsAspectRatio)) {
5959 return AspectRatio();
5962 const StyleAspectRatio& aspectRatio = StylePosition()->mAspectRatio;
5963 // If aspect-ratio is zero or infinite, it's a degenerate ratio and behaves
5964 // as auto.
5965 // https://drafts.csswg.org/css-sizing-4/#valdef-aspect-ratio-ratio
5966 if (!aspectRatio.BehavesAsAuto()) {
5967 // Non-auto. Return the preferred aspect ratio from the aspect-ratio style.
5968 return aspectRatio.ratio.AsRatio().ToLayoutRatio(UseBoxSizing::Yes);
5971 // The rest of the cases are when aspect-ratio has 'auto'.
5972 if (auto intrinsicRatio = GetIntrinsicRatio()) {
5973 return intrinsicRatio;
5976 if (aspectRatio.HasRatio()) {
5977 // If it's a degenerate ratio, this returns 0. Just the same as the auto
5978 // case.
5979 return aspectRatio.ratio.AsRatio().ToLayoutRatio(UseBoxSizing::No);
5982 return AspectRatio();
5985 /* virtual */
5986 AspectRatio nsIFrame::GetIntrinsicRatio() const { return AspectRatio(); }
5988 static bool ShouldApplyAutomaticMinimumOnInlineAxis(
5989 WritingMode aWM, const nsStyleDisplay* aDisplay,
5990 const nsStylePosition* aPosition) {
5991 // Apply the automatic minimum size for aspect ratio:
5992 // Note: The replaced elements shouldn't be here, so we only check the scroll
5993 // container.
5994 // https://drafts.csswg.org/css-sizing-4/#aspect-ratio-minimum
5995 return !aDisplay->IsScrollableOverflow() && aPosition->MinISize(aWM).IsAuto();
5998 struct MinMaxSize {
5999 nscoord mMinSize = 0;
6000 nscoord mMaxSize = NS_UNCONSTRAINEDSIZE;
6002 nscoord ClampSizeToMinAndMax(nscoord aSize) const {
6003 return NS_CSS_MINMAX(aSize, mMinSize, mMaxSize);
6006 static MinMaxSize ComputeTransferredMinMaxInlineSize(
6007 const WritingMode aWM, const AspectRatio& aAspectRatio,
6008 const MinMaxSize& aMinMaxBSize, const LogicalSize& aBoxSizingAdjustment) {
6009 // Note: the spec mentions that
6010 // 1. This transferred minimum is capped by any definite preferred or maximum
6011 // size in the destination axis.
6012 // 2. This transferred maximum is floored by any definite preferred or minimum
6013 // size in the destination axis
6015 // https://drafts.csswg.org/css-sizing-4/#aspect-ratio
6017 // The spec requires us to clamp these by the specified size (it calls it the
6018 // preferred size). However, we actually don't need to worry about that,
6019 // because we only use this if the inline size is indefinite.
6021 // We do not need to clamp the transferred minimum and maximum as long as we
6022 // always apply the transferred min/max size before the explicit min/max size,
6023 // the result will be identical.
6025 MinMaxSize transferredISize;
6027 if (aMinMaxBSize.mMinSize > 0) {
6028 transferredISize.mMinSize = aAspectRatio.ComputeRatioDependentSize(
6029 LogicalAxis::eLogicalAxisInline, aWM, aMinMaxBSize.mMinSize,
6030 aBoxSizingAdjustment);
6033 if (aMinMaxBSize.mMaxSize != NS_UNCONSTRAINEDSIZE) {
6034 transferredISize.mMaxSize = aAspectRatio.ComputeRatioDependentSize(
6035 LogicalAxis::eLogicalAxisInline, aWM, aMinMaxBSize.mMaxSize,
6036 aBoxSizingAdjustment);
6039 // Minimum size wins over maximum size.
6040 transferredISize.mMaxSize =
6041 std::max(transferredISize.mMinSize, transferredISize.mMaxSize);
6042 return transferredISize;
6045 /* virtual */
6046 nsIFrame::SizeComputationResult nsIFrame::ComputeSize(
6047 gfxContext* aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize,
6048 nscoord aAvailableISize, const LogicalSize& aMargin,
6049 const LogicalSize& aBorderPadding, const StyleSizeOverrides& aSizeOverrides,
6050 ComputeSizeFlags aFlags) {
6051 MOZ_ASSERT(!GetIntrinsicRatio(),
6052 "Please override this method and call "
6053 "nsContainerFrame::ComputeSizeWithIntrinsicDimensions instead.");
6054 LogicalSize result =
6055 ComputeAutoSize(aRenderingContext, aWM, aCBSize, aAvailableISize, aMargin,
6056 aBorderPadding, aSizeOverrides, aFlags);
6057 const nsStylePosition* stylePos = StylePosition();
6058 const nsStyleDisplay* disp = StyleDisplay();
6059 auto aspectRatioUsage = AspectRatioUsage::None;
6061 const auto boxSizingAdjust = stylePos->mBoxSizing == StyleBoxSizing::Border
6062 ? aBorderPadding
6063 : LogicalSize(aWM);
6064 nscoord boxSizingToMarginEdgeISize = aMargin.ISize(aWM) +
6065 aBorderPadding.ISize(aWM) -
6066 boxSizingAdjust.ISize(aWM);
6068 const auto& styleISize = aSizeOverrides.mStyleISize
6069 ? *aSizeOverrides.mStyleISize
6070 : stylePos->ISize(aWM);
6071 const auto& styleBSize = aSizeOverrides.mStyleBSize
6072 ? *aSizeOverrides.mStyleBSize
6073 : stylePos->BSize(aWM);
6075 auto parentFrame = GetParent();
6076 auto alignCB = parentFrame;
6077 bool isGridItem = IsGridItem();
6078 if (parentFrame && parentFrame->IsTableWrapperFrame() && IsTableFrame()) {
6079 // An inner table frame is sized as a grid item if its table wrapper is,
6080 // because they actually have the same CB (the wrapper's CB).
6081 // @see ReflowInput::InitCBReflowInput
6082 auto tableWrapper = GetParent();
6083 auto grandParent = tableWrapper->GetParent();
6084 isGridItem = grandParent->IsGridContainerFrame() &&
6085 !tableWrapper->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW);
6086 if (isGridItem) {
6087 // When resolving justify/align-self below, we want to use the grid
6088 // container's justify/align-items value and WritingMode.
6089 alignCB = grandParent;
6092 const bool isFlexItem =
6093 IsFlexItem() &&
6094 !parentFrame->HasAnyStateBits(NS_STATE_FLEX_IS_EMULATING_LEGACY_BOX);
6095 // This variable only gets set (and used) if isFlexItem is true. It
6096 // indicates which axis (in this frame's own WM) corresponds to its
6097 // flex container's main axis.
6098 LogicalAxis flexMainAxis =
6099 eLogicalAxisInline; // (init to make valgrind happy)
6100 if (isFlexItem) {
6101 flexMainAxis = nsFlexContainerFrame::IsItemInlineAxisMainAxis(this)
6102 ? eLogicalAxisInline
6103 : eLogicalAxisBlock;
6106 const auto aspectRatio = GetAspectRatio();
6107 const bool isOrthogonal = aWM.IsOrthogonalTo(alignCB->GetWritingMode());
6108 const bool isAutoISize = styleISize.IsAuto();
6109 // Compute inline-axis size
6110 if (!isAutoISize) {
6111 auto iSizeResult =
6112 ComputeISizeValue(aRenderingContext, aWM, aCBSize, boxSizingAdjust,
6113 boxSizingToMarginEdgeISize, styleISize, aFlags);
6114 result.ISize(aWM) = iSizeResult.mISize;
6115 aspectRatioUsage = iSizeResult.mAspectRatioUsage;
6116 } else if (aspectRatio &&
6117 !nsLayoutUtils::IsAutoBSize(styleBSize, aCBSize.BSize(aWM))) {
6118 auto bSize = nsLayoutUtils::ComputeBSizeValue(
6119 aCBSize.BSize(aWM), boxSizingAdjust.BSize(aWM),
6120 styleBSize.AsLengthPercentage());
6121 result.ISize(aWM) = aspectRatio.ComputeRatioDependentSize(
6122 LogicalAxis::eLogicalAxisInline, aWM, bSize, boxSizingAdjust);
6123 aspectRatioUsage = AspectRatioUsage::ToComputeISize;
6124 } else if (MOZ_UNLIKELY(isGridItem) && !IsTrueOverflowContainer()) {
6125 // 'auto' inline-size for grid-level box - fill the CB for 'stretch' /
6126 // 'normal' and clamp it to the CB if requested:
6127 bool stretch = false;
6128 if (!aFlags.contains(ComputeSizeFlag::ShrinkWrap) &&
6129 !StyleMargin()->HasInlineAxisAuto(aWM) &&
6130 !alignCB->IsMasonry(isOrthogonal ? eLogicalAxisBlock
6131 : eLogicalAxisInline)) {
6132 auto inlineAxisAlignment =
6133 isOrthogonal ? StylePosition()->UsedAlignSelf(alignCB->Style())._0
6134 : StylePosition()->UsedJustifySelf(alignCB->Style())._0;
6135 stretch = inlineAxisAlignment == StyleAlignFlags::NORMAL ||
6136 inlineAxisAlignment == StyleAlignFlags::STRETCH;
6138 if (stretch || aFlags.contains(ComputeSizeFlag::IClampMarginBoxMinSize)) {
6139 auto iSizeToFillCB =
6140 std::max(nscoord(0), aCBSize.ISize(aWM) - aBorderPadding.ISize(aWM) -
6141 aMargin.ISize(aWM));
6142 if (stretch || result.ISize(aWM) > iSizeToFillCB) {
6143 result.ISize(aWM) = iSizeToFillCB;
6148 // Calculate and apply min max transferred size contraint.
6149 // https://github.com/w3c/csswg-drafts/issues/5257
6150 // If we have definite preferred size, the transferred minimum and maximum
6151 // are clampled by it. This means transferred minimum and maximum don't have
6152 // effects with definite preferred size.
6154 // Note: The axis in which the preferred size calculation depends on this
6155 // aspect ratio is called the ratio-dependent axis, and the resulting size
6156 // is definite if its input sizes are also definite.
6157 const bool isDefiniteISize =
6158 styleISize.IsLengthPercentage() ||
6159 aspectRatioUsage == AspectRatioUsage::ToComputeISize;
6160 const bool isFlexItemInlineAxisMainAxis =
6161 isFlexItem && flexMainAxis == eLogicalAxisInline;
6162 const auto& minBSizeCoord = stylePos->MinBSize(aWM);
6163 const auto& maxBSizeCoord = stylePos->MaxBSize(aWM);
6164 const bool isAutoMinBSize =
6165 nsLayoutUtils::IsAutoBSize(minBSizeCoord, aCBSize.BSize(aWM));
6166 const bool isAutoMaxBSize =
6167 nsLayoutUtils::IsAutoBSize(maxBSizeCoord, aCBSize.BSize(aWM));
6168 if (aspectRatio && !isDefiniteISize && !isFlexItemInlineAxisMainAxis) {
6169 const MinMaxSize minMaxBSize{
6170 isAutoMinBSize ? 0
6171 : nsLayoutUtils::ComputeBSizeValue(
6172 aCBSize.BSize(aWM), boxSizingAdjust.BSize(aWM),
6173 minBSizeCoord.AsLengthPercentage()),
6174 isAutoMaxBSize ? NS_UNCONSTRAINEDSIZE
6175 : nsLayoutUtils::ComputeBSizeValue(
6176 aCBSize.BSize(aWM), boxSizingAdjust.BSize(aWM),
6177 maxBSizeCoord.AsLengthPercentage())};
6178 MinMaxSize transferredMinMaxISize = ComputeTransferredMinMaxInlineSize(
6179 aWM, aspectRatio, minMaxBSize, boxSizingAdjust);
6181 result.ISize(aWM) =
6182 transferredMinMaxISize.ClampSizeToMinAndMax(result.ISize(aWM));
6185 // Flex items ignore their min & max sizing properties in their
6186 // flex container's main-axis. (Those properties get applied later in
6187 // the flexbox algorithm.)
6188 const auto& maxISizeCoord = stylePos->MaxISize(aWM);
6189 nscoord maxISize = NS_UNCONSTRAINEDSIZE;
6190 if (!maxISizeCoord.IsNone() && !isFlexItemInlineAxisMainAxis) {
6191 maxISize =
6192 ComputeISizeValue(aRenderingContext, aWM, aCBSize, boxSizingAdjust,
6193 boxSizingToMarginEdgeISize, maxISizeCoord, aFlags)
6194 .mISize;
6195 result.ISize(aWM) = std::min(maxISize, result.ISize(aWM));
6198 const auto& minISizeCoord = stylePos->MinISize(aWM);
6199 nscoord minISize;
6200 if (!minISizeCoord.IsAuto() && !isFlexItemInlineAxisMainAxis) {
6201 minISize =
6202 ComputeISizeValue(aRenderingContext, aWM, aCBSize, boxSizingAdjust,
6203 boxSizingToMarginEdgeISize, minISizeCoord, aFlags)
6204 .mISize;
6205 } else if (MOZ_UNLIKELY(
6206 aFlags.contains(ComputeSizeFlag::IApplyAutoMinSize))) {
6207 // This implements "Implied Minimum Size of Grid Items".
6208 // https://drafts.csswg.org/css-grid/#min-size-auto
6209 minISize = std::min(maxISize, GetMinISize(aRenderingContext));
6210 if (styleISize.IsLengthPercentage()) {
6211 minISize = std::min(minISize, result.ISize(aWM));
6212 } else if (aFlags.contains(ComputeSizeFlag::IClampMarginBoxMinSize)) {
6213 // "if the grid item spans only grid tracks that have a fixed max track
6214 // sizing function, its automatic minimum size in that dimension is
6215 // further clamped to less than or equal to the size necessary to fit
6216 // its margin box within the resulting grid area (flooring at zero)"
6217 // https://drafts.csswg.org/css-grid/#min-size-auto
6218 auto maxMinISize =
6219 std::max(nscoord(0), aCBSize.ISize(aWM) - aBorderPadding.ISize(aWM) -
6220 aMargin.ISize(aWM));
6221 minISize = std::min(minISize, maxMinISize);
6223 } else if (aspectRatioUsage == AspectRatioUsage::ToComputeISize &&
6224 ShouldApplyAutomaticMinimumOnInlineAxis(aWM, disp, stylePos)) {
6225 // This means we successfully applied aspect-ratio and now need to check
6226 // if we need to apply the implied minimum size:
6227 // https://drafts.csswg.org/css-sizing-4/#aspect-ratio-minimum
6228 MOZ_ASSERT(!IsFrameOfType(eReplaced),
6229 "aspect-ratio minimums should not apply to replaced elements");
6230 // The inline size computed by aspect-ratio shouldn't less than the content
6231 // size.
6232 minISize = GetMinISize(aRenderingContext);
6233 } else {
6234 // Treat "min-width: auto" as 0.
6235 // NOTE: Technically, "auto" is supposed to behave like "min-content" on
6236 // flex items. However, we don't need to worry about that here, because
6237 // flex items' min-sizes are intentionally ignored until the flex
6238 // container explicitly considers them during space distribution.
6239 minISize = 0;
6241 result.ISize(aWM) = std::max(minISize, result.ISize(aWM));
6243 // Compute block-axis size
6244 // (but not if we have auto bsize or if we received the "UseAutoBSize"
6245 // flag -- then, we'll just stick with the bsize that we already calculated
6246 // in the initial ComputeAutoSize() call.)
6247 if (!aFlags.contains(ComputeSizeFlag::UseAutoBSize)) {
6248 if (!nsLayoutUtils::IsAutoBSize(styleBSize, aCBSize.BSize(aWM))) {
6249 result.BSize(aWM) = nsLayoutUtils::ComputeBSizeValue(
6250 aCBSize.BSize(aWM), boxSizingAdjust.BSize(aWM),
6251 styleBSize.AsLengthPercentage());
6252 } else if (aspectRatio && result.ISize(aWM) != NS_UNCONSTRAINEDSIZE) {
6253 result.BSize(aWM) = aspectRatio.ComputeRatioDependentSize(
6254 LogicalAxis::eLogicalAxisBlock, aWM, result.ISize(aWM),
6255 boxSizingAdjust);
6256 MOZ_ASSERT(aspectRatioUsage == AspectRatioUsage::None);
6257 aspectRatioUsage = AspectRatioUsage::ToComputeBSize;
6258 } else if (MOZ_UNLIKELY(isGridItem) && styleBSize.IsAuto() &&
6259 !IsTrueOverflowContainer() &&
6260 !alignCB->IsMasonry(isOrthogonal ? eLogicalAxisInline
6261 : eLogicalAxisBlock)) {
6262 auto cbSize = aCBSize.BSize(aWM);
6263 if (cbSize != NS_UNCONSTRAINEDSIZE) {
6264 // 'auto' block-size for grid-level box - fill the CB for 'stretch' /
6265 // 'normal' and clamp it to the CB if requested:
6266 bool stretch = false;
6267 if (!StyleMargin()->HasBlockAxisAuto(aWM)) {
6268 auto blockAxisAlignment =
6269 isOrthogonal
6270 ? StylePosition()->UsedJustifySelf(alignCB->Style())._0
6271 : StylePosition()->UsedAlignSelf(alignCB->Style())._0;
6272 stretch = blockAxisAlignment == StyleAlignFlags::NORMAL ||
6273 blockAxisAlignment == StyleAlignFlags::STRETCH;
6275 if (stretch ||
6276 aFlags.contains(ComputeSizeFlag::BClampMarginBoxMinSize)) {
6277 auto bSizeToFillCB =
6278 std::max(nscoord(0),
6279 cbSize - aBorderPadding.BSize(aWM) - aMargin.BSize(aWM));
6280 if (stretch || (result.BSize(aWM) != NS_UNCONSTRAINEDSIZE &&
6281 result.BSize(aWM) > bSizeToFillCB)) {
6282 result.BSize(aWM) = bSizeToFillCB;
6289 if (result.BSize(aWM) != NS_UNCONSTRAINEDSIZE) {
6290 const bool isFlexItemBlockAxisMainAxis =
6291 isFlexItem && flexMainAxis == eLogicalAxisBlock;
6292 if (!isAutoMaxBSize && !isFlexItemBlockAxisMainAxis) {
6293 nscoord maxBSize = nsLayoutUtils::ComputeBSizeValue(
6294 aCBSize.BSize(aWM), boxSizingAdjust.BSize(aWM),
6295 maxBSizeCoord.AsLengthPercentage());
6296 result.BSize(aWM) = std::min(maxBSize, result.BSize(aWM));
6299 if (!isAutoMinBSize && !isFlexItemBlockAxisMainAxis) {
6300 nscoord minBSize = nsLayoutUtils::ComputeBSizeValue(
6301 aCBSize.BSize(aWM), boxSizingAdjust.BSize(aWM),
6302 minBSizeCoord.AsLengthPercentage());
6303 result.BSize(aWM) = std::max(minBSize, result.BSize(aWM));
6307 if (IsThemed(disp)) {
6308 LayoutDeviceIntSize widget;
6309 bool canOverride = true;
6310 nsPresContext* presContext = PresContext();
6311 presContext->Theme()->GetMinimumWidgetSize(
6312 presContext, this, disp->EffectiveAppearance(), &widget, &canOverride);
6314 // Convert themed widget's physical dimensions to logical coords
6315 LogicalSize size(aWM,
6316 nsSize(presContext->DevPixelsToAppUnits(widget.width),
6317 presContext->DevPixelsToAppUnits(widget.height)));
6319 // GetMinimumWidgetSize() returns border-box; we need content-box.
6320 size -= aBorderPadding;
6322 if (size.BSize(aWM) > result.BSize(aWM) || !canOverride) {
6323 result.BSize(aWM) = size.BSize(aWM);
6325 if (size.ISize(aWM) > result.ISize(aWM) || !canOverride) {
6326 result.ISize(aWM) = size.ISize(aWM);
6330 result.ISize(aWM) = std::max(0, result.ISize(aWM));
6331 result.BSize(aWM) = std::max(0, result.BSize(aWM));
6333 return {result, aspectRatioUsage};
6336 nsRect nsIFrame::ComputeTightBounds(DrawTarget* aDrawTarget) const {
6337 return InkOverflowRect();
6340 /* virtual */
6341 nsresult nsIFrame::GetPrefWidthTightBounds(gfxContext* aContext, nscoord* aX,
6342 nscoord* aXMost) {
6343 return NS_ERROR_NOT_IMPLEMENTED;
6346 /* virtual */
6347 LogicalSize nsIFrame::ComputeAutoSize(
6348 gfxContext* aRenderingContext, WritingMode aWM,
6349 const mozilla::LogicalSize& aCBSize, nscoord aAvailableISize,
6350 const mozilla::LogicalSize& aMargin,
6351 const mozilla::LogicalSize& aBorderPadding,
6352 const StyleSizeOverrides& aSizeOverrides, ComputeSizeFlags aFlags) {
6353 // Use basic shrink-wrapping as a default implementation.
6354 LogicalSize result(aWM, 0xdeadbeef, NS_UNCONSTRAINEDSIZE);
6356 // don't bother setting it if the result won't be used
6357 const auto& styleISize = aSizeOverrides.mStyleISize
6358 ? *aSizeOverrides.mStyleISize
6359 : StylePosition()->ISize(aWM);
6360 if (styleISize.IsAuto()) {
6361 nscoord availBased =
6362 aAvailableISize - aMargin.ISize(aWM) - aBorderPadding.ISize(aWM);
6363 result.ISize(aWM) = ShrinkWidthToFit(aRenderingContext, availBased, aFlags);
6365 return result;
6368 nscoord nsIFrame::ShrinkWidthToFit(gfxContext* aRenderingContext,
6369 nscoord aISizeInCB,
6370 ComputeSizeFlags aFlags) {
6371 // If we're a container for font size inflation, then shrink
6372 // wrapping inside of us should not apply font size inflation.
6373 AutoMaybeDisableFontInflation an(this);
6375 nscoord result;
6376 nscoord minISize = GetMinISize(aRenderingContext);
6377 if (minISize > aISizeInCB) {
6378 const bool clamp = aFlags.contains(ComputeSizeFlag::IClampMarginBoxMinSize);
6379 result = MOZ_UNLIKELY(clamp) ? aISizeInCB : minISize;
6380 } else {
6381 nscoord prefISize = GetPrefISize(aRenderingContext);
6382 if (prefISize > aISizeInCB) {
6383 result = aISizeInCB;
6384 } else {
6385 result = prefISize;
6388 return result;
6391 Maybe<nscoord> nsIFrame::ComputeInlineSizeFromAspectRatio(
6392 WritingMode aWM, const LogicalSize& aCBSize,
6393 const LogicalSize& aContentEdgeToBoxSizing, ComputeSizeFlags aFlags) const {
6394 // FIXME: Bug 1670151: Use GetAspectRatio() to cover replaced elements (and
6395 // then we can drop the check of eSupportsAspectRatio).
6396 const AspectRatio aspectRatio = StylePosition()->mAspectRatio.ToLayoutRatio();
6397 if (!IsFrameOfType(eSupportsAspectRatio) || !aspectRatio) {
6398 return Nothing();
6401 const StyleSize& styleBSize = StylePosition()->BSize(aWM);
6402 if (aFlags.contains(ComputeSizeFlag::UseAutoBSize) ||
6403 nsLayoutUtils::IsAutoBSize(styleBSize, aCBSize.BSize(aWM))) {
6404 return Nothing();
6407 MOZ_ASSERT(styleBSize.IsLengthPercentage());
6408 nscoord bSize = nsLayoutUtils::ComputeBSizeValue(
6409 aCBSize.BSize(aWM), aContentEdgeToBoxSizing.BSize(aWM),
6410 styleBSize.AsLengthPercentage());
6411 return Some(aspectRatio.ComputeRatioDependentSize(
6412 LogicalAxis::eLogicalAxisInline, aWM, bSize, aContentEdgeToBoxSizing));
6415 nsIFrame::ISizeComputationResult nsIFrame::ComputeISizeValue(
6416 gfxContext* aRenderingContext, const WritingMode aWM,
6417 const LogicalSize& aContainingBlockSize,
6418 const LogicalSize& aContentEdgeToBoxSizing, nscoord aBoxSizingToMarginEdge,
6419 StyleExtremumLength aSize, ComputeSizeFlags aFlags) {
6420 // If 'this' is a container for font size inflation, then shrink
6421 // wrapping inside of it should not apply font size inflation.
6422 AutoMaybeDisableFontInflation an(this);
6423 // If we have an aspect-ratio and a definite block size, we resolve the
6424 // min-content and max-content size by the aspect-ratio and the block size.
6425 // https://github.com/w3c/csswg-drafts/issues/5032
6426 Maybe<nscoord> intrinsicSizeFromAspectRatio =
6427 aSize == StyleExtremumLength::MozAvailable
6428 ? Nothing()
6429 : ComputeInlineSizeFromAspectRatio(aWM, aContainingBlockSize,
6430 aContentEdgeToBoxSizing, aFlags);
6431 nscoord result;
6432 switch (aSize) {
6433 case StyleExtremumLength::MaxContent:
6434 result = intrinsicSizeFromAspectRatio ? *intrinsicSizeFromAspectRatio
6435 : GetPrefISize(aRenderingContext);
6436 NS_ASSERTION(result >= 0, "inline-size less than zero");
6437 return {result, intrinsicSizeFromAspectRatio
6438 ? AspectRatioUsage::ToComputeISize
6439 : AspectRatioUsage::None};
6440 case StyleExtremumLength::MinContent:
6441 result = intrinsicSizeFromAspectRatio ? *intrinsicSizeFromAspectRatio
6442 : GetMinISize(aRenderingContext);
6443 NS_ASSERTION(result >= 0, "inline-size less than zero");
6444 if (MOZ_UNLIKELY(
6445 aFlags.contains(ComputeSizeFlag::IClampMarginBoxMinSize))) {
6446 auto available =
6447 aContainingBlockSize.ISize(aWM) -
6448 (aBoxSizingToMarginEdge + aContentEdgeToBoxSizing.ISize(aWM));
6449 result = std::min(available, result);
6451 return {result, intrinsicSizeFromAspectRatio
6452 ? AspectRatioUsage::ToComputeISize
6453 : AspectRatioUsage::None};
6454 case StyleExtremumLength::MozFitContent: {
6455 nscoord pref = NS_UNCONSTRAINEDSIZE;
6456 nscoord min = 0;
6457 if (intrinsicSizeFromAspectRatio) {
6458 // The min-content and max-content size are identical and equal to the
6459 // size computed from the block size and the aspect ratio.
6460 pref = min = *intrinsicSizeFromAspectRatio;
6461 } else {
6462 pref = GetPrefISize(aRenderingContext);
6463 min = GetMinISize(aRenderingContext);
6465 nscoord fill =
6466 aContainingBlockSize.ISize(aWM) -
6467 (aBoxSizingToMarginEdge + aContentEdgeToBoxSizing.ISize(aWM));
6468 if (MOZ_UNLIKELY(
6469 aFlags.contains(ComputeSizeFlag::IClampMarginBoxMinSize))) {
6470 min = std::min(min, fill);
6472 result = std::max(min, std::min(pref, fill));
6473 NS_ASSERTION(result >= 0, "inline-size less than zero");
6474 return {result};
6476 case StyleExtremumLength::MozAvailable:
6477 return {aContainingBlockSize.ISize(aWM) -
6478 (aBoxSizingToMarginEdge + aContentEdgeToBoxSizing.ISize(aWM))};
6480 MOZ_ASSERT_UNREACHABLE("Unknown extremum length?");
6481 return {};
6484 nscoord nsIFrame::ComputeISizeValue(const WritingMode aWM,
6485 const LogicalSize& aContainingBlockSize,
6486 const LogicalSize& aContentEdgeToBoxSizing,
6487 const LengthPercentage& aSize) {
6488 LAYOUT_WARN_IF_FALSE(
6489 aContainingBlockSize.ISize(aWM) != NS_UNCONSTRAINEDSIZE,
6490 "have unconstrained inline-size; this should only result from "
6491 "very large sizes, not attempts at intrinsic inline-size "
6492 "calculation");
6493 NS_ASSERTION(aContainingBlockSize.ISize(aWM) >= 0,
6494 "inline-size less than zero");
6496 nscoord result = aSize.Resolve(aContainingBlockSize.ISize(aWM));
6497 // The result of a calc() expression might be less than 0; we
6498 // should clamp at runtime (below). (Percentages and coords that
6499 // are less than 0 have already been dropped by the parser.)
6500 result -= aContentEdgeToBoxSizing.ISize(aWM);
6501 return std::max(0, result);
6504 void nsIFrame::DidReflow(nsPresContext* aPresContext,
6505 const ReflowInput* aReflowInput) {
6506 NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, ("nsIFrame::DidReflow"));
6508 SVGObserverUtils::InvalidateDirectRenderingObservers(
6509 this, SVGObserverUtils::INVALIDATE_REFLOW);
6511 RemoveStateBits(NS_FRAME_IN_REFLOW | NS_FRAME_FIRST_REFLOW |
6512 NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN);
6514 // Clear state that was used in ReflowInput::InitResizeFlags (see
6515 // comment there for why we can't clear it there).
6516 SetHasBSizeChange(false);
6518 // Notify the percent bsize observer if there is a percent bsize.
6519 // The observer may be able to initiate another reflow with a computed
6520 // bsize. This happens in the case where a table cell has no computed
6521 // bsize but can fabricate one when the cell bsize is known.
6522 if (aReflowInput && aReflowInput->mPercentBSizeObserver && !GetPrevInFlow()) {
6523 const auto& bsize =
6524 aReflowInput->mStylePosition->BSize(aReflowInput->GetWritingMode());
6525 if (bsize.HasPercent()) {
6526 aReflowInput->mPercentBSizeObserver->NotifyPercentBSize(*aReflowInput);
6530 aPresContext->ReflowedFrame();
6533 void nsIFrame::FinishReflowWithAbsoluteFrames(nsPresContext* aPresContext,
6534 ReflowOutput& aDesiredSize,
6535 const ReflowInput& aReflowInput,
6536 nsReflowStatus& aStatus,
6537 bool aConstrainBSize) {
6538 ReflowAbsoluteFrames(aPresContext, aDesiredSize, aReflowInput, aStatus,
6539 aConstrainBSize);
6541 FinishAndStoreOverflow(&aDesiredSize, aReflowInput.mStyleDisplay);
6544 void nsIFrame::ReflowAbsoluteFrames(nsPresContext* aPresContext,
6545 ReflowOutput& aDesiredSize,
6546 const ReflowInput& aReflowInput,
6547 nsReflowStatus& aStatus,
6548 bool aConstrainBSize) {
6549 if (HasAbsolutelyPositionedChildren()) {
6550 nsAbsoluteContainingBlock* absoluteContainer = GetAbsoluteContainingBlock();
6552 // Let the absolutely positioned container reflow any absolutely positioned
6553 // child frames that need to be reflowed
6555 // The containing block for the abs pos kids is formed by our padding edge.
6556 nsMargin usedBorder = GetUsedBorder();
6557 nscoord containingBlockWidth =
6558 std::max(0, aDesiredSize.Width() - usedBorder.LeftRight());
6559 nscoord containingBlockHeight =
6560 std::max(0, aDesiredSize.Height() - usedBorder.TopBottom());
6561 nsContainerFrame* container = do_QueryFrame(this);
6562 NS_ASSERTION(container,
6563 "Abs-pos children only supported on container frames for now");
6565 nsRect containingBlock(0, 0, containingBlockWidth, containingBlockHeight);
6566 AbsPosReflowFlags flags =
6567 AbsPosReflowFlags::CBWidthAndHeightChanged; // XXX could be optimized
6568 if (aConstrainBSize) {
6569 flags |= AbsPosReflowFlags::ConstrainHeight;
6571 absoluteContainer->Reflow(container, aPresContext, aReflowInput, aStatus,
6572 containingBlock, flags,
6573 &aDesiredSize.mOverflowAreas);
6577 /* virtual */
6578 bool nsIFrame::CanContinueTextRun() const {
6579 // By default, a frame will *not* allow a text run to be continued
6580 // through it.
6581 return false;
6584 void nsIFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
6585 const ReflowInput& aReflowInput,
6586 nsReflowStatus& aStatus) {
6587 MarkInReflow();
6588 DO_GLOBAL_REFLOW_COUNT("nsFrame");
6589 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
6590 aDesiredSize.ClearSize();
6591 NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
6594 bool nsIFrame::IsContentDisabled() const {
6595 // FIXME(emilio): Doing this via CSS means callers must ensure the style is up
6596 // to date, and they don't!
6597 if (StyleUI()->mUserInput == StyleUserInput::None) {
6598 return true;
6601 auto* element = nsGenericHTMLElement::FromNodeOrNull(GetContent());
6602 return element && element->IsDisabled();
6605 nsresult nsIFrame::CharacterDataChanged(const CharacterDataChangeInfo&) {
6606 MOZ_ASSERT_UNREACHABLE("should only be called for text frames");
6607 return NS_OK;
6610 nsresult nsIFrame::AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
6611 int32_t aModType) {
6612 return NS_OK;
6615 // Flow member functions
6617 nsIFrame* nsIFrame::GetPrevContinuation() const { return nullptr; }
6619 void nsIFrame::SetPrevContinuation(nsIFrame* aPrevContinuation) {
6620 MOZ_ASSERT(false, "not splittable");
6623 nsIFrame* nsIFrame::GetNextContinuation() const { return nullptr; }
6625 void nsIFrame::SetNextContinuation(nsIFrame*) {
6626 MOZ_ASSERT(false, "not splittable");
6629 nsIFrame* nsIFrame::GetPrevInFlow() const { return nullptr; }
6631 void nsIFrame::SetPrevInFlow(nsIFrame* aPrevInFlow) {
6632 MOZ_ASSERT(false, "not splittable");
6635 nsIFrame* nsIFrame::GetNextInFlow() const { return nullptr; }
6637 void nsIFrame::SetNextInFlow(nsIFrame*) { MOZ_ASSERT(false, "not splittable"); }
6639 nsIFrame* nsIFrame::GetTailContinuation() {
6640 nsIFrame* frame = this;
6641 while (frame->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
6642 frame = frame->GetPrevContinuation();
6643 NS_ASSERTION(frame, "first continuation can't be overflow container");
6645 for (nsIFrame* next = frame->GetNextContinuation();
6646 next && !next->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
6647 next = frame->GetNextContinuation()) {
6648 frame = next;
6651 MOZ_ASSERT(frame, "illegal state in continuation chain.");
6652 return frame;
6655 // Associated view object
6656 void nsIFrame::SetView(nsView* aView) {
6657 if (aView) {
6658 aView->SetFrame(this);
6660 #ifdef DEBUG
6661 LayoutFrameType frameType = Type();
6662 NS_ASSERTION(frameType == LayoutFrameType::SubDocument ||
6663 frameType == LayoutFrameType::ListControl ||
6664 frameType == LayoutFrameType::Viewport ||
6665 frameType == LayoutFrameType::MenuPopup,
6666 "Only specific frame types can have an nsView");
6667 #endif
6669 // Store the view on the frame.
6670 SetViewInternal(aView);
6672 // Set the frame state bit that says the frame has a view
6673 AddStateBits(NS_FRAME_HAS_VIEW);
6675 // Let all of the ancestors know they have a descendant with a view.
6676 for (nsIFrame* f = GetParent();
6677 f && !f->HasAnyStateBits(NS_FRAME_HAS_CHILD_WITH_VIEW);
6678 f = f->GetParent())
6679 f->AddStateBits(NS_FRAME_HAS_CHILD_WITH_VIEW);
6680 } else {
6681 MOZ_ASSERT_UNREACHABLE("Destroying a view while the frame is alive?");
6682 RemoveStateBits(NS_FRAME_HAS_VIEW);
6683 SetViewInternal(nullptr);
6687 // Find the first geometric parent that has a view
6688 nsIFrame* nsIFrame::GetAncestorWithView() const {
6689 for (nsIFrame* f = GetParent(); nullptr != f; f = f->GetParent()) {
6690 if (f->HasView()) {
6691 return f;
6694 return nullptr;
6697 template <nsPoint (nsIFrame::*PositionGetter)() const>
6698 static nsPoint OffsetCalculator(const nsIFrame* aThis, const nsIFrame* aOther) {
6699 MOZ_ASSERT(aOther, "Must have frame for destination coordinate system!");
6701 NS_ASSERTION(aThis->PresContext() == aOther->PresContext(),
6702 "GetOffsetTo called on frames in different documents");
6704 nsPoint offset(0, 0);
6705 const nsIFrame* f;
6706 for (f = aThis; f != aOther && f; f = f->GetParent()) {
6707 offset += (f->*PositionGetter)();
6710 if (f != aOther) {
6711 // Looks like aOther wasn't an ancestor of |this|. So now we have
6712 // the root-frame-relative position of |this| in |offset|. Convert back
6713 // to the coordinates of aOther
6714 while (aOther) {
6715 offset -= (aOther->*PositionGetter)();
6716 aOther = aOther->GetParent();
6720 return offset;
6723 nsPoint nsIFrame::GetOffsetTo(const nsIFrame* aOther) const {
6724 return OffsetCalculator<&nsIFrame::GetPosition>(this, aOther);
6727 nsPoint nsIFrame::GetOffsetToIgnoringScrolling(const nsIFrame* aOther) const {
6728 return OffsetCalculator<&nsIFrame::GetPositionIgnoringScrolling>(this,
6729 aOther);
6732 nsPoint nsIFrame::GetOffsetToCrossDoc(const nsIFrame* aOther) const {
6733 return GetOffsetToCrossDoc(aOther, PresContext()->AppUnitsPerDevPixel());
6736 nsPoint nsIFrame::GetOffsetToCrossDoc(const nsIFrame* aOther,
6737 const int32_t aAPD) const {
6738 MOZ_ASSERT(aOther, "Must have frame for destination coordinate system!");
6739 NS_ASSERTION(PresContext()->GetRootPresContext() ==
6740 aOther->PresContext()->GetRootPresContext(),
6741 "trying to get the offset between frames in different document "
6742 "hierarchies?");
6743 if (PresContext()->GetRootPresContext() !=
6744 aOther->PresContext()->GetRootPresContext()) {
6745 // crash right away, we are almost certainly going to crash anyway.
6746 MOZ_CRASH(
6747 "trying to get the offset between frames in different "
6748 "document hierarchies?");
6751 const nsIFrame* root = nullptr;
6752 // offset will hold the final offset
6753 // docOffset holds the currently accumulated offset at the current APD, it
6754 // will be converted and added to offset when the current APD changes.
6755 nsPoint offset(0, 0), docOffset(0, 0);
6756 const nsIFrame* f = this;
6757 int32_t currAPD = PresContext()->AppUnitsPerDevPixel();
6758 while (f && f != aOther) {
6759 docOffset += f->GetPosition();
6760 nsIFrame* parent = f->GetParent();
6761 if (parent) {
6762 f = parent;
6763 } else {
6764 nsPoint newOffset(0, 0);
6765 root = f;
6766 f = nsLayoutUtils::GetCrossDocParentFrame(f, &newOffset);
6767 int32_t newAPD = f ? f->PresContext()->AppUnitsPerDevPixel() : 0;
6768 if (!f || newAPD != currAPD) {
6769 // Convert docOffset to the right APD and add it to offset.
6770 offset += docOffset.ScaleToOtherAppUnits(currAPD, aAPD);
6771 docOffset.x = docOffset.y = 0;
6773 currAPD = newAPD;
6774 docOffset += newOffset;
6777 if (f == aOther) {
6778 offset += docOffset.ScaleToOtherAppUnits(currAPD, aAPD);
6779 } else {
6780 // Looks like aOther wasn't an ancestor of |this|. So now we have
6781 // the root-document-relative position of |this| in |offset|. Subtract the
6782 // root-document-relative position of |aOther| from |offset|.
6783 // This call won't try to recurse again because root is an ancestor of
6784 // aOther.
6785 nsPoint negOffset = aOther->GetOffsetToCrossDoc(root, aAPD);
6786 offset -= negOffset;
6789 return offset;
6792 CSSIntRect nsIFrame::GetScreenRect() const {
6793 return CSSIntRect::FromAppUnitsToNearest(GetScreenRectInAppUnits());
6796 nsRect nsIFrame::GetScreenRectInAppUnits() const {
6797 nsPresContext* presContext = PresContext();
6798 nsIFrame* rootFrame = presContext->PresShell()->GetRootFrame();
6799 nsPoint rootScreenPos(0, 0);
6800 nsPoint rootFrameOffsetInParent(0, 0);
6801 nsIFrame* rootFrameParent = nsLayoutUtils::GetCrossDocParentFrame(
6802 rootFrame, &rootFrameOffsetInParent);
6803 if (rootFrameParent) {
6804 nsRect parentScreenRectAppUnits =
6805 rootFrameParent->GetScreenRectInAppUnits();
6806 nsPresContext* parentPresContext = rootFrameParent->PresContext();
6807 double parentScale = double(presContext->AppUnitsPerDevPixel()) /
6808 parentPresContext->AppUnitsPerDevPixel();
6809 nsPoint rootPt =
6810 parentScreenRectAppUnits.TopLeft() + rootFrameOffsetInParent;
6811 rootScreenPos.x = NS_round(parentScale * rootPt.x);
6812 rootScreenPos.y = NS_round(parentScale * rootPt.y);
6813 } else {
6814 nsCOMPtr<nsIWidget> rootWidget;
6815 presContext->PresShell()->GetViewManager()->GetRootWidget(
6816 getter_AddRefs(rootWidget));
6817 if (rootWidget) {
6818 LayoutDeviceIntPoint rootDevPx = rootWidget->WidgetToScreenOffset();
6819 rootScreenPos.x = presContext->DevPixelsToAppUnits(rootDevPx.x);
6820 rootScreenPos.y = presContext->DevPixelsToAppUnits(rootDevPx.y);
6824 return nsRect(rootScreenPos + GetOffsetTo(rootFrame), GetSize());
6827 // Returns the offset from this frame to the closest geometric parent that
6828 // has a view. Also returns the containing view or null in case of error
6829 void nsIFrame::GetOffsetFromView(nsPoint& aOffset, nsView** aView) const {
6830 MOZ_ASSERT(nullptr != aView, "null OUT parameter pointer");
6831 nsIFrame* frame = const_cast<nsIFrame*>(this);
6833 *aView = nullptr;
6834 aOffset.MoveTo(0, 0);
6835 do {
6836 aOffset += frame->GetPosition();
6837 frame = frame->GetParent();
6838 } while (frame && !frame->HasView());
6840 if (frame) {
6841 *aView = frame->GetView();
6845 nsIWidget* nsIFrame::GetNearestWidget() const {
6846 return GetClosestView()->GetNearestWidget(nullptr);
6849 nsIWidget* nsIFrame::GetNearestWidget(nsPoint& aOffset) const {
6850 nsPoint offsetToView;
6851 nsPoint offsetToWidget;
6852 nsIWidget* widget =
6853 GetClosestView(&offsetToView)->GetNearestWidget(&offsetToWidget);
6854 aOffset = offsetToView + offsetToWidget;
6855 return widget;
6858 Matrix4x4Flagged nsIFrame::GetTransformMatrix(ViewportType aViewportType,
6859 RelativeTo aStopAtAncestor,
6860 nsIFrame** aOutAncestor,
6861 uint32_t aFlags) const {
6862 MOZ_ASSERT(aOutAncestor, "Need a place to put the ancestor!");
6864 /* If we're transformed, we want to hand back the combination
6865 * transform/translate matrix that will apply our current transform, then
6866 * shift us to our parent.
6868 bool isTransformed = IsTransformed();
6869 const nsIFrame* zoomedContentRoot = nullptr;
6870 if (aStopAtAncestor.mViewportType == ViewportType::Visual) {
6871 zoomedContentRoot = ViewportUtils::IsZoomedContentRoot(this);
6872 if (zoomedContentRoot) {
6873 MOZ_ASSERT(aViewportType != ViewportType::Visual);
6877 if (isTransformed || zoomedContentRoot) {
6878 Matrix4x4 result;
6879 int32_t scaleFactor =
6880 ((aFlags & IN_CSS_UNITS) ? AppUnitsPerCSSPixel()
6881 : PresContext()->AppUnitsPerDevPixel());
6883 /* Compute the delta to the parent, which we need because we are converting
6884 * coordinates to our parent.
6886 if (isTransformed) {
6887 NS_ASSERTION(nsLayoutUtils::GetCrossDocParentFrame(this),
6888 "Cannot transform the viewport frame!");
6890 result = result * nsDisplayTransform::GetResultingTransformMatrix(
6891 this, nsPoint(0, 0), scaleFactor,
6892 nsDisplayTransform::INCLUDE_PERSPECTIVE |
6893 nsDisplayTransform::OFFSET_BY_ORIGIN);
6896 // The offset from a zoomed content root to its parent (e.g. from
6897 // a canvas frame to a scroll frame) is in layout coordinates, so
6898 // apply it before applying any layout-to-visual transform.
6899 *aOutAncestor = nsLayoutUtils::GetCrossDocParentFrame(this);
6900 nsPoint delta = GetOffsetToCrossDoc(*aOutAncestor);
6901 /* Combine the raw transform with a translation to our parent. */
6902 result.PostTranslate(NSAppUnitsToFloatPixels(delta.x, scaleFactor),
6903 NSAppUnitsToFloatPixels(delta.y, scaleFactor), 0.0f);
6905 if (zoomedContentRoot) {
6906 Matrix4x4 layoutToVisual;
6907 ScrollableLayerGuid::ViewID targetScrollId =
6908 nsLayoutUtils::FindOrCreateIDFor(zoomedContentRoot->GetContent());
6909 if (aFlags & nsIFrame::IN_CSS_UNITS) {
6910 layoutToVisual =
6911 ViewportUtils::GetVisualToLayoutTransform(targetScrollId)
6912 .Inverse()
6913 .ToUnknownMatrix();
6914 } else {
6915 layoutToVisual =
6916 ViewportUtils::GetVisualToLayoutTransform<LayoutDevicePixel>(
6917 targetScrollId)
6918 .Inverse()
6919 .ToUnknownMatrix();
6921 result = result * layoutToVisual;
6924 return result;
6927 if (nsLayoutUtils::IsPopup(this) && IsListControlFrame()) {
6928 nsPresContext* presContext = PresContext();
6929 nsIFrame* docRootFrame = presContext->PresShell()->GetRootFrame();
6931 // Compute a matrix that transforms from the popup widget to the toplevel
6932 // widget. We use the widgets because they're the simplest and most
6933 // accurate approach --- this should work no matter how the widget position
6934 // was chosen.
6935 nsIWidget* widget = GetView()->GetWidget();
6936 nsPresContext* rootPresContext = PresContext()->GetRootPresContext();
6937 // Maybe the widget hasn't been created yet? Popups without widgets are
6938 // treated as regular frames. That should work since they'll be rendered
6939 // as part of the page if they're rendered at all.
6940 if (widget && rootPresContext) {
6941 nsIWidget* toplevel = rootPresContext->GetNearestWidget();
6942 if (toplevel) {
6943 LayoutDeviceIntRect screenBounds = widget->GetClientBounds();
6944 LayoutDeviceIntRect toplevelScreenBounds = toplevel->GetClientBounds();
6945 LayoutDeviceIntPoint translation =
6946 screenBounds.TopLeft() - toplevelScreenBounds.TopLeft();
6948 Matrix4x4 transformToTop;
6949 transformToTop._41 = translation.x;
6950 transformToTop._42 = translation.y;
6952 *aOutAncestor = docRootFrame;
6953 Matrix4x4 docRootTransformToTop =
6954 nsLayoutUtils::GetTransformToAncestor(RelativeTo{docRootFrame},
6955 RelativeTo{nullptr})
6956 .GetMatrix();
6957 if (docRootTransformToTop.IsSingular()) {
6958 NS_WARNING(
6959 "Containing document is invisible, we can't compute a valid "
6960 "transform");
6961 } else {
6962 docRootTransformToTop.Invert();
6963 return transformToTop * docRootTransformToTop;
6969 *aOutAncestor = nsLayoutUtils::GetCrossDocParentFrame(this);
6971 /* Otherwise, we're not transformed. In that case, we'll walk up the frame
6972 * tree until we either hit the root frame or something that may be
6973 * transformed. We'll then change coordinates into that frame, since we're
6974 * guaranteed that nothing in-between can be transformed. First, however,
6975 * we have to check to see if we have a parent. If not, we'll set the
6976 * outparam to null (indicating that there's nothing left) and will hand back
6977 * the identity matrix.
6979 if (!*aOutAncestor) return Matrix4x4();
6981 /* Keep iterating while the frame can't possibly be transformed. */
6982 const nsIFrame* current = this;
6983 auto shouldStopAt = [](const nsIFrame* aCurrent, nsIFrame* aAncestor,
6984 uint32_t aFlags) {
6985 return aAncestor->IsTransformed() || nsLayoutUtils::IsPopup(aAncestor) ||
6986 ViewportUtils::IsZoomedContentRoot(aAncestor) ||
6987 ((aFlags & STOP_AT_STACKING_CONTEXT_AND_DISPLAY_PORT) &&
6988 (aAncestor->IsStackingContext() ||
6989 DisplayPortUtils::FrameHasDisplayPort(aAncestor, aCurrent)));
6991 while (*aOutAncestor != aStopAtAncestor.mFrame &&
6992 !shouldStopAt(current, *aOutAncestor, aFlags)) {
6993 /* If no parent, stop iterating. Otherwise, update the ancestor. */
6994 nsIFrame* parent = nsLayoutUtils::GetCrossDocParentFrame(*aOutAncestor);
6995 if (!parent) break;
6997 current = *aOutAncestor;
6998 *aOutAncestor = parent;
7001 NS_ASSERTION(*aOutAncestor, "Somehow ended up with a null ancestor...?");
7003 /* Translate from this frame to our ancestor, if it exists. That's the
7004 * entire transform, so we're done.
7006 nsPoint delta = GetOffsetToCrossDoc(*aOutAncestor);
7007 int32_t scaleFactor =
7008 ((aFlags & IN_CSS_UNITS) ? AppUnitsPerCSSPixel()
7009 : PresContext()->AppUnitsPerDevPixel());
7010 return Matrix4x4::Translation(NSAppUnitsToFloatPixels(delta.x, scaleFactor),
7011 NSAppUnitsToFloatPixels(delta.y, scaleFactor),
7012 0.0f);
7015 static void InvalidateRenderingObservers(nsIFrame* aDisplayRoot,
7016 nsIFrame* aFrame,
7017 bool aFrameChanged = true) {
7018 MOZ_ASSERT(aDisplayRoot == nsLayoutUtils::GetDisplayRootFrame(aFrame));
7019 SVGObserverUtils::InvalidateDirectRenderingObservers(aFrame);
7020 nsIFrame* parent = aFrame;
7021 while (parent != aDisplayRoot &&
7022 (parent = nsLayoutUtils::GetCrossDocParentFrame(parent)) &&
7023 !parent->HasAnyStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT)) {
7024 SVGObserverUtils::InvalidateDirectRenderingObservers(parent);
7027 if (!aFrameChanged) {
7028 return;
7031 aFrame->MarkNeedsDisplayItemRebuild();
7034 static void SchedulePaintInternal(
7035 nsIFrame* aDisplayRoot, nsIFrame* aFrame,
7036 nsIFrame::PaintType aType = nsIFrame::PAINT_DEFAULT) {
7037 MOZ_ASSERT(aDisplayRoot == nsLayoutUtils::GetDisplayRootFrame(aFrame));
7038 nsPresContext* pres = aDisplayRoot->PresContext()->GetRootPresContext();
7040 // No need to schedule a paint for an external document since they aren't
7041 // painted directly.
7042 if (!pres || (pres->Document() && pres->Document()->IsResourceDoc())) {
7043 return;
7045 if (!pres->GetContainerWeak()) {
7046 NS_WARNING("Shouldn't call SchedulePaint in a detached pres context");
7047 return;
7050 pres->PresShell()->ScheduleViewManagerFlush(
7051 aType == nsIFrame::PAINT_DELAYED_COMPRESS ? PaintType::DelayedCompress
7052 : PaintType::Default);
7054 if (aType == nsIFrame::PAINT_DELAYED_COMPRESS) {
7055 return;
7058 if (aType == nsIFrame::PAINT_DEFAULT) {
7059 aDisplayRoot->AddStateBits(NS_FRAME_UPDATE_LAYER_TREE);
7063 static void InvalidateFrameInternal(nsIFrame* aFrame, bool aHasDisplayItem,
7064 bool aRebuildDisplayItems) {
7065 if (aHasDisplayItem) {
7066 aFrame->AddStateBits(NS_FRAME_NEEDS_PAINT);
7069 if (aRebuildDisplayItems) {
7070 aFrame->MarkNeedsDisplayItemRebuild();
7072 SVGObserverUtils::InvalidateDirectRenderingObservers(aFrame);
7073 bool needsSchedulePaint = false;
7074 if (nsLayoutUtils::IsPopup(aFrame)) {
7075 needsSchedulePaint = true;
7076 } else {
7077 nsIFrame* parent = nsLayoutUtils::GetCrossDocParentFrame(aFrame);
7078 while (parent &&
7079 !parent->HasAnyStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT)) {
7080 if (aHasDisplayItem && !parent->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
7081 parent->AddStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT);
7083 SVGObserverUtils::InvalidateDirectRenderingObservers(parent);
7085 // If we're inside a popup, then we need to make sure that we
7086 // call schedule paint so that the NS_FRAME_UPDATE_LAYER_TREE
7087 // flag gets added to the popup display root frame.
7088 if (nsLayoutUtils::IsPopup(parent)) {
7089 needsSchedulePaint = true;
7090 break;
7092 parent = nsLayoutUtils::GetCrossDocParentFrame(parent);
7094 if (!parent) {
7095 needsSchedulePaint = true;
7098 if (!aHasDisplayItem) {
7099 return;
7101 if (needsSchedulePaint) {
7102 nsIFrame* displayRoot = nsLayoutUtils::GetDisplayRootFrame(aFrame);
7103 SchedulePaintInternal(displayRoot, aFrame);
7105 if (aFrame->HasAnyStateBits(NS_FRAME_HAS_INVALID_RECT)) {
7106 aFrame->RemoveProperty(nsIFrame::InvalidationRect());
7107 aFrame->RemoveStateBits(NS_FRAME_HAS_INVALID_RECT);
7111 void nsIFrame::InvalidateFrameSubtree(bool aRebuildDisplayItems /* = true */) {
7112 InvalidateFrame(0, aRebuildDisplayItems);
7114 if (HasAnyStateBits(NS_FRAME_ALL_DESCENDANTS_NEED_PAINT)) {
7115 return;
7118 AddStateBits(NS_FRAME_ALL_DESCENDANTS_NEED_PAINT);
7120 for (const auto& childList : CrossDocChildLists()) {
7121 for (nsIFrame* child : childList.mList) {
7122 // Don't explicitly rebuild display items for our descendants,
7123 // since we should be marked and it implicitly includes all
7124 // descendants.
7125 child->InvalidateFrameSubtree(false);
7130 void nsIFrame::ClearInvalidationStateBits() {
7131 if (HasAnyStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT)) {
7132 for (const auto& childList : CrossDocChildLists()) {
7133 for (nsIFrame* child : childList.mList) {
7134 child->ClearInvalidationStateBits();
7139 RemoveStateBits(NS_FRAME_NEEDS_PAINT | NS_FRAME_DESCENDANT_NEEDS_PAINT |
7140 NS_FRAME_ALL_DESCENDANTS_NEED_PAINT);
7143 void nsIFrame::InvalidateFrame(uint32_t aDisplayItemKey,
7144 bool aRebuildDisplayItems /* = true */) {
7145 bool hasDisplayItem =
7146 !aDisplayItemKey ||
7147 FrameLayerBuilder::HasRetainedDataFor(this, aDisplayItemKey);
7148 InvalidateFrameInternal(this, hasDisplayItem, aRebuildDisplayItems);
7151 void nsIFrame::InvalidateFrameWithRect(const nsRect& aRect,
7152 uint32_t aDisplayItemKey,
7153 bool aRebuildDisplayItems /* = true */) {
7154 if (aRect.IsEmpty()) {
7155 return;
7157 bool hasDisplayItem =
7158 !aDisplayItemKey ||
7159 FrameLayerBuilder::HasRetainedDataFor(this, aDisplayItemKey);
7160 bool alreadyInvalid = false;
7161 if (!HasAnyStateBits(NS_FRAME_NEEDS_PAINT)) {
7162 InvalidateFrameInternal(this, hasDisplayItem, aRebuildDisplayItems);
7163 } else {
7164 alreadyInvalid = true;
7167 if (!hasDisplayItem) {
7168 return;
7171 nsRect* rect;
7172 if (HasAnyStateBits(NS_FRAME_HAS_INVALID_RECT)) {
7173 rect = GetProperty(InvalidationRect());
7174 MOZ_ASSERT(rect);
7175 } else {
7176 if (alreadyInvalid) {
7177 return;
7179 rect = new nsRect();
7180 AddProperty(InvalidationRect(), rect);
7181 AddStateBits(NS_FRAME_HAS_INVALID_RECT);
7184 *rect = rect->Union(aRect);
7187 /*static*/
7188 uint8_t nsIFrame::sLayerIsPrerenderedDataKey;
7190 static bool DoesLayerHaveOutOfDateFrameMetrics(Layer* aLayer) {
7191 for (uint32_t i = 0; i < aLayer->GetScrollMetadataCount(); i++) {
7192 const FrameMetrics& metrics = aLayer->GetFrameMetrics(i);
7193 if (!metrics.IsScrollable()) {
7194 continue;
7196 nsIScrollableFrame* scrollableFrame =
7197 nsLayoutUtils::FindScrollableFrameFor(metrics.GetScrollId());
7198 if (!scrollableFrame) {
7199 // This shouldn't happen, so let's do the safe thing and trigger a full
7200 // paint if it does.
7201 return true;
7203 nsPoint scrollPosition = scrollableFrame->GetScrollPosition();
7204 if (metrics.GetLayoutScrollOffset() !=
7205 CSSPoint::FromAppUnits(scrollPosition)) {
7206 return true;
7209 return false;
7212 static bool DoesLayerOrAncestorsHaveOutOfDateFrameMetrics(Layer* aLayer) {
7213 for (Layer* layer = aLayer; layer; layer = layer->GetParent()) {
7214 if (DoesLayerHaveOutOfDateFrameMetrics(layer)) {
7215 return true;
7218 return false;
7221 bool nsIFrame::TryUpdateTransformOnly(Layer** aLayerResult) {
7222 // If we move a transformed layer when we have a merged display
7223 // list, then it can end up intersecting other items for which
7224 // we don't have a defined ordering.
7225 // We could allow this if the display list is in the canonical
7226 // ordering (correctly sorted for all intersections), but we
7227 // don't have a way to check that yet.
7228 if (nsLayoutUtils::AreRetainedDisplayListsEnabled()) {
7229 return false;
7232 Layer* layer = FrameLayerBuilder::GetDedicatedLayer(
7233 this, DisplayItemType::TYPE_TRANSFORM);
7234 if (!layer || !layer->HasUserData(LayerIsPrerenderedDataKey())) {
7235 // If this layer isn't prerendered or we clip composites to our OS
7236 // window, then we can't correctly optimize to an empty
7237 // transaction in general.
7238 return false;
7241 if (DoesLayerOrAncestorsHaveOutOfDateFrameMetrics(layer)) {
7242 // At least one scroll frame that can affect the position of this layer
7243 // has changed its scroll offset since the last paint. Schedule a full
7244 // paint to make sure that this layer's transform and all the frame
7245 // metrics that affect it are in sync.
7246 return false;
7249 gfx::Matrix4x4Flagged transform3d;
7250 if (!nsLayoutUtils::GetLayerTransformForFrame(this, &transform3d)) {
7251 // We're not able to compute a layer transform that we know would
7252 // be used at the next layers transaction, so we can't only update
7253 // the transform and will need to schedule an invalidating paint.
7254 return false;
7256 gfx::Matrix transform;
7257 gfx::Matrix previousTransform;
7258 // FIXME/bug 796690 and 796705: in general, changes to 3D
7259 // transforms, or transform changes to properties other than
7260 // translation, may lead us to choose a different rendering
7261 // resolution for our layer. So if the transform is 3D or has a
7262 // non-translation change, bail and schedule an invalidating paint.
7263 // (We can often do better than this, for example for scale-down
7264 // changes.)
7265 static const gfx::Float kError = 0.0001f;
7266 if (!transform3d.Is2D(&transform) ||
7267 !layer->GetBaseTransform().Is2D(&previousTransform) ||
7268 !gfx::FuzzyEqual(transform._11, previousTransform._11, kError) ||
7269 !gfx::FuzzyEqual(transform._22, previousTransform._22, kError) ||
7270 !gfx::FuzzyEqual(transform._21, previousTransform._21, kError) ||
7271 !gfx::FuzzyEqual(transform._12, previousTransform._12, kError)) {
7272 return false;
7274 layer->SetBaseTransformForNextTransaction(transform3d.GetMatrix());
7275 *aLayerResult = layer;
7276 return true;
7279 bool nsIFrame::IsInvalid(nsRect& aRect) {
7280 if (!HasAnyStateBits(NS_FRAME_NEEDS_PAINT)) {
7281 return false;
7284 if (HasAnyStateBits(NS_FRAME_HAS_INVALID_RECT)) {
7285 nsRect* rect = GetProperty(InvalidationRect());
7286 NS_ASSERTION(
7287 rect, "Must have an invalid rect if NS_FRAME_HAS_INVALID_RECT is set!");
7288 aRect = *rect;
7289 } else {
7290 aRect.SetEmpty();
7292 return true;
7295 void nsIFrame::SchedulePaint(PaintType aType, bool aFrameChanged) {
7296 nsIFrame* displayRoot = nsLayoutUtils::GetDisplayRootFrame(this);
7297 InvalidateRenderingObservers(displayRoot, this, aFrameChanged);
7298 SchedulePaintInternal(displayRoot, this, aType);
7301 void nsIFrame::SchedulePaintWithoutInvalidatingObservers(PaintType aType) {
7302 nsIFrame* displayRoot = nsLayoutUtils::GetDisplayRootFrame(this);
7303 SchedulePaintInternal(displayRoot, this, aType);
7306 Layer* nsIFrame::InvalidateLayer(DisplayItemType aDisplayItemKey,
7307 const nsIntRect* aDamageRect,
7308 const nsRect* aFrameDamageRect,
7309 uint32_t aFlags /* = 0 */) {
7310 NS_ASSERTION(aDisplayItemKey > DisplayItemType::TYPE_ZERO, "Need a key");
7312 Layer* layer = FrameLayerBuilder::GetDedicatedLayer(this, aDisplayItemKey);
7314 nsIFrame* displayRoot = nsLayoutUtils::GetDisplayRootFrame(this);
7315 InvalidateRenderingObservers(displayRoot, this, false);
7317 // Check if frame supports WebRender's async update
7318 if ((aFlags & UPDATE_IS_ASYNC) &&
7319 WebRenderUserData::SupportsAsyncUpdate(this)) {
7320 // WebRender does not use layer, then return nullptr.
7321 return nullptr;
7324 // If the layer is being updated asynchronously, and it's being forwarded
7325 // to a compositor, then we don't need to invalidate.
7326 if ((aFlags & UPDATE_IS_ASYNC) && layer && layer->SupportsAsyncUpdate()) {
7327 return layer;
7330 if (!layer) {
7331 if (aFrameDamageRect && aFrameDamageRect->IsEmpty()) {
7332 return nullptr;
7335 // In the bug 930056, dialer app startup but not shown on the
7336 // screen because sometimes we don't have any retainned data
7337 // for remote type displayitem and thus Repaint event is not
7338 // triggered. So, always invalidate in this case.
7339 DisplayItemType displayItemKey = aDisplayItemKey;
7340 if (aDisplayItemKey == DisplayItemType::TYPE_REMOTE) {
7341 displayItemKey = DisplayItemType::TYPE_ZERO;
7344 if (aFrameDamageRect) {
7345 InvalidateFrameWithRect(*aFrameDamageRect,
7346 static_cast<uint32_t>(displayItemKey));
7347 } else {
7348 InvalidateFrame(static_cast<uint32_t>(displayItemKey));
7351 return nullptr;
7354 if (aDamageRect && aDamageRect->IsEmpty()) {
7355 return layer;
7358 if (aDamageRect) {
7359 layer->AddInvalidRect(*aDamageRect);
7360 } else {
7361 layer->SetInvalidRectToVisibleRegion();
7364 SchedulePaintInternal(displayRoot, this, PAINT_COMPOSITE_ONLY);
7365 return layer;
7368 static nsRect ComputeEffectsRect(nsIFrame* aFrame, const nsRect& aOverflowRect,
7369 const nsSize& aNewSize) {
7370 nsRect r = aOverflowRect;
7372 if (aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
7373 // For SVG frames, we only need to account for filters.
7374 // TODO: We could also take account of clipPath and mask to reduce the
7375 // ink overflow, but that's not essential.
7376 if (aFrame->StyleEffects()->HasFilters()) {
7377 SetOrUpdateRectValuedProperty(aFrame, nsIFrame::PreEffectsBBoxProperty(),
7379 r = SVGUtils::GetPostFilterInkOverflowRect(aFrame, aOverflowRect);
7381 return r;
7384 // box-shadow
7385 r.UnionRect(r, nsLayoutUtils::GetBoxShadowRectForFrame(aFrame, aNewSize));
7387 // border-image-outset.
7388 // We need to include border-image-outset because it can cause the
7389 // border image to be drawn beyond the border box.
7391 // (1) It's important we not check whether there's a border-image
7392 // since the style hint for a change in border image doesn't cause
7393 // reflow, and that's probably more important than optimizing the
7394 // overflow areas for the silly case of border-image-outset without
7395 // border-image
7396 // (2) It's important that we not check whether the border-image
7397 // is actually loaded, since that would require us to reflow when
7398 // the image loads.
7399 const nsStyleBorder* styleBorder = aFrame->StyleBorder();
7400 nsMargin outsetMargin = styleBorder->GetImageOutset();
7402 if (outsetMargin != nsMargin(0, 0, 0, 0)) {
7403 nsRect outsetRect(nsPoint(0, 0), aNewSize);
7404 outsetRect.Inflate(outsetMargin);
7405 r.UnionRect(r, outsetRect);
7408 // Note that we don't remove the outlineInnerRect if a frame loses outline
7409 // style. That would require an extra property lookup for every frame,
7410 // or a new frame state bit to track whether a property had been stored,
7411 // or something like that. It's not worth doing that here. At most it's
7412 // only one heap-allocated rect per frame and it will be cleaned up when
7413 // the frame dies.
7415 if (SVGIntegrationUtils::UsingOverflowAffectingEffects(aFrame)) {
7416 SetOrUpdateRectValuedProperty(aFrame, nsIFrame::PreEffectsBBoxProperty(),
7418 r = SVGIntegrationUtils::ComputePostEffectsInkOverflowRect(aFrame, r);
7421 return r;
7424 void nsIFrame::MovePositionBy(const nsPoint& aTranslation) {
7425 nsPoint position = GetNormalPosition() + aTranslation;
7427 const nsMargin* computedOffsets = nullptr;
7428 if (IsRelativelyPositioned()) {
7429 computedOffsets = GetProperty(nsIFrame::ComputedOffsetProperty());
7431 ReflowInput::ApplyRelativePositioning(
7432 this, computedOffsets ? *computedOffsets : nsMargin(), &position);
7433 SetPosition(position);
7436 nsRect nsIFrame::GetNormalRect() const {
7437 // It might be faster to first check
7438 // StyleDisplay()->IsRelativelyPositionedStyle().
7439 nsPoint* normalPosition = GetProperty(NormalPositionProperty());
7440 if (normalPosition) {
7441 return nsRect(*normalPosition, GetSize());
7443 return GetRect();
7446 nsPoint nsIFrame::GetPositionIgnoringScrolling() const {
7447 return GetParent() ? GetParent()->GetPositionOfChildIgnoringScrolling(this)
7448 : GetPosition();
7451 nsRect nsIFrame::GetOverflowRect(OverflowType aType) const {
7452 // Note that in some cases the overflow area might not have been
7453 // updated (yet) to reflect any outline set on the frame or the area
7454 // of child frames. That's OK because any reflow that updates these
7455 // areas will invalidate the appropriate area, so any (mis)uses of
7456 // this method will be fixed up.
7458 if (mOverflow.mType == NS_FRAME_OVERFLOW_LARGE) {
7459 // there is an overflow rect, and it's not stored as deltas but as
7460 // a separately-allocated rect
7461 return GetOverflowAreasProperty()->Overflow(aType);
7464 if (aType == OverflowType::Ink && mOverflow.mType != NS_FRAME_OVERFLOW_NONE) {
7465 return InkOverflowFromDeltas();
7468 return nsRect(nsPoint(0, 0), GetSize());
7471 OverflowAreas nsIFrame::GetOverflowAreas() const {
7472 if (mOverflow.mType == NS_FRAME_OVERFLOW_LARGE) {
7473 // there is an overflow rect, and it's not stored as deltas but as
7474 // a separately-allocated rect
7475 return *GetOverflowAreasProperty();
7478 return OverflowAreas(InkOverflowFromDeltas(),
7479 nsRect(nsPoint(0, 0), GetSize()));
7482 OverflowAreas nsIFrame::GetOverflowAreasRelativeToSelf() const {
7483 if (IsTransformed()) {
7484 OverflowAreas* preTransformOverflows =
7485 GetProperty(PreTransformOverflowAreasProperty());
7486 if (preTransformOverflows) {
7487 return OverflowAreas(preTransformOverflows->InkOverflow(),
7488 preTransformOverflows->ScrollableOverflow());
7491 return OverflowAreas(InkOverflowRect(), ScrollableOverflowRect());
7494 nsRect nsIFrame::ScrollableOverflowRectRelativeToParent() const {
7495 return ScrollableOverflowRect() + mRect.TopLeft();
7498 nsRect nsIFrame::InkOverflowRectRelativeToParent() const {
7499 return InkOverflowRect() + mRect.TopLeft();
7502 nsRect nsIFrame::ScrollableOverflowRectRelativeToSelf() const {
7503 if (IsTransformed()) {
7504 OverflowAreas* preTransformOverflows =
7505 GetProperty(PreTransformOverflowAreasProperty());
7506 if (preTransformOverflows)
7507 return preTransformOverflows->ScrollableOverflow();
7509 return ScrollableOverflowRect();
7512 nsRect nsIFrame::InkOverflowRectRelativeToSelf() const {
7513 if (IsTransformed()) {
7514 OverflowAreas* preTransformOverflows =
7515 GetProperty(PreTransformOverflowAreasProperty());
7516 if (preTransformOverflows) return preTransformOverflows->InkOverflow();
7518 return InkOverflowRect();
7521 nsRect nsIFrame::PreEffectsInkOverflowRect() const {
7522 nsRect* r = GetProperty(nsIFrame::PreEffectsBBoxProperty());
7523 return r ? *r : InkOverflowRectRelativeToSelf();
7526 bool nsIFrame::UpdateOverflow() {
7527 MOZ_ASSERT(FrameMaintainsOverflow(),
7528 "Non-display SVG do not maintain ink overflow rects");
7530 nsRect rect(nsPoint(0, 0), GetSize());
7531 OverflowAreas overflowAreas(rect, rect);
7533 if (!ComputeCustomOverflow(overflowAreas)) {
7534 // If updating overflow wasn't supported by this frame, then it should
7535 // have scheduled any necessary reflows. We can return false to say nothing
7536 // changed, and wait for reflow to correct it.
7537 return false;
7540 UnionChildOverflow(overflowAreas);
7542 if (FinishAndStoreOverflow(overflowAreas, GetSize())) {
7543 nsView* view = GetView();
7544 if (view) {
7545 ReflowChildFlags flags = GetXULLayoutFlags();
7546 if (!(flags & ReflowChildFlags::NoSizeView)) {
7547 // Make sure the frame's view is properly sized.
7548 nsViewManager* vm = view->GetViewManager();
7549 vm->ResizeView(view, overflowAreas.InkOverflow(), true);
7553 return true;
7556 // Frames that combine their 3d transform with their ancestors
7557 // only compute a pre-transform overflow rect, and then contribute
7558 // to the normal overflow rect of the preserve-3d root. Always return
7559 // true here so that we propagate changes up to the root for final
7560 // calculation.
7561 return Combines3DTransformWithAncestors();
7564 /* virtual */
7565 bool nsIFrame::ComputeCustomOverflow(OverflowAreas& aOverflowAreas) {
7566 return true;
7569 /* virtual */
7570 void nsIFrame::UnionChildOverflow(OverflowAreas& aOverflowAreas) {
7571 if (!DoesClipChildrenInBothAxes() &&
7572 !(IsXULCollapsed() && (IsXULBoxFrame() || ::IsXULBoxWrapped(this)))) {
7573 nsLayoutUtils::UnionChildOverflow(this, aOverflowAreas);
7577 // Return true if this form control element's preferred size property (but not
7578 // percentage max size property) contains a percentage value that should be
7579 // resolved against zero when calculating its min-content contribution in the
7580 // corresponding axis.
7582 // For proper replaced elements, the percentage value in both their max size
7583 // property or preferred size property should be resolved against zero. This is
7584 // handled in IsPercentageResolvedAgainstZero().
7585 inline static bool FormControlShrinksForPercentSize(const nsIFrame* aFrame) {
7586 if (!aFrame->IsFrameOfType(nsIFrame::eReplaced)) {
7587 // Quick test to reject most frames.
7588 return false;
7591 LayoutFrameType fType = aFrame->Type();
7592 if (fType == LayoutFrameType::Meter || fType == LayoutFrameType::Progress ||
7593 fType == LayoutFrameType::Range) {
7594 // progress, meter and range do have this shrinking behavior
7595 // FIXME: Maybe these should be nsIFormControlFrame?
7596 return true;
7599 if (!static_cast<nsIFormControlFrame*>(do_QueryFrame(aFrame))) {
7600 // Not a form control. This includes fieldsets, which do not
7601 // shrink.
7602 return false;
7605 if (fType == LayoutFrameType::GfxButtonControl ||
7606 fType == LayoutFrameType::HTMLButtonControl) {
7607 // Buttons don't have this shrinking behavior. (Note that color
7608 // inputs do, even though they inherit from button, so we can't use
7609 // do_QueryFrame here.)
7610 return false;
7613 return true;
7616 bool nsIFrame::IsPercentageResolvedAgainstZero(
7617 const StyleSize& aStyleSize, const StyleMaxSize& aStyleMaxSize) const {
7618 const bool sizeHasPercent = aStyleSize.HasPercent();
7619 return ((sizeHasPercent || aStyleMaxSize.HasPercent()) &&
7620 IsFrameOfType(nsIFrame::eReplacedSizing)) ||
7621 (sizeHasPercent && FormControlShrinksForPercentSize(this));
7624 bool nsIFrame::IsBlockWrapper() const {
7625 auto pseudoType = Style()->GetPseudoType();
7626 return pseudoType == PseudoStyleType::mozBlockInsideInlineWrapper ||
7627 pseudoType == PseudoStyleType::buttonContent ||
7628 pseudoType == PseudoStyleType::cellContent ||
7629 pseudoType == PseudoStyleType::columnSpanWrapper;
7632 bool nsIFrame::IsBlockFrameOrSubclass() const {
7633 const nsBlockFrame* thisAsBlock = do_QueryFrame(this);
7634 return !!thisAsBlock;
7637 static nsIFrame* GetNearestBlockContainer(nsIFrame* frame) {
7638 // The block wrappers we use to wrap blocks inside inlines aren't
7639 // described in the CSS spec. We need to make them not be containing
7640 // blocks.
7641 // Since the parent of such a block is either a normal block or
7642 // another such pseudo, this shouldn't cause anything bad to happen.
7643 // Also the anonymous blocks inside table cells are not containing blocks.
7645 // If we ever start skipping table row groups from being containing blocks,
7646 // you need to remove the StickyScrollContainer hack referencing bug 1421660.
7647 while (frame->IsFrameOfType(nsIFrame::eLineParticipant) ||
7648 frame->IsBlockWrapper() ||
7649 // Table rows are not containing blocks either
7650 frame->IsTableRowFrame()) {
7651 frame = frame->GetParent();
7652 NS_ASSERTION(
7653 frame,
7654 "How come we got to the root frame without seeing a containing block?");
7656 return frame;
7659 nsIFrame* nsIFrame::GetContainingBlock(
7660 uint32_t aFlags, const nsStyleDisplay* aStyleDisplay) const {
7661 MOZ_ASSERT(aStyleDisplay == StyleDisplay());
7662 if (!GetParent()) {
7663 return nullptr;
7665 // MathML frames might have absolute positioning style, but they would
7666 // still be in-flow. So we have to check to make sure that the frame
7667 // is really out-of-flow too.
7668 nsIFrame* f;
7669 if (IsAbsolutelyPositioned(aStyleDisplay) &&
7670 HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
7671 f = GetParent(); // the parent is always the containing block
7672 } else {
7673 f = GetNearestBlockContainer(GetParent());
7676 if (aFlags & SKIP_SCROLLED_FRAME && f &&
7677 f->Style()->GetPseudoType() == PseudoStyleType::scrolledContent) {
7678 f = f->GetParent();
7680 return f;
7683 #ifdef DEBUG_FRAME_DUMP
7685 int32_t nsIFrame::ContentIndexInContainer(const nsIFrame* aFrame) {
7686 int32_t result = -1;
7688 nsIContent* content = aFrame->GetContent();
7689 if (content) {
7690 nsIContent* parentContent = content->GetParent();
7691 if (parentContent) {
7692 result = parentContent->ComputeIndexOf(content);
7696 return result;
7699 nsAutoCString nsIFrame::ListTag() const {
7700 nsAutoString tmp;
7701 GetFrameName(tmp);
7703 nsAutoCString tag;
7704 tag += NS_ConvertUTF16toUTF8(tmp);
7705 tag += nsPrintfCString("@%p", static_cast<const void*>(this));
7706 return tag;
7709 std::string nsIFrame::ConvertToString(const LogicalRect& aRect,
7710 const WritingMode aWM, ListFlags aFlags) {
7711 if (aFlags.contains(ListFlag::DisplayInCSSPixels)) {
7712 // Abuse CSSRect to store all LogicalRect's dimensions in CSS pixels.
7713 return ToString(mozilla::CSSRect(CSSPixel::FromAppUnits(aRect.IStart(aWM)),
7714 CSSPixel::FromAppUnits(aRect.BStart(aWM)),
7715 CSSPixel::FromAppUnits(aRect.ISize(aWM)),
7716 CSSPixel::FromAppUnits(aRect.BSize(aWM))));
7718 return ToString(aRect);
7721 std::string nsIFrame::ConvertToString(const LogicalSize& aSize,
7722 const WritingMode aWM, ListFlags aFlags) {
7723 if (aFlags.contains(ListFlag::DisplayInCSSPixels)) {
7724 // Abuse CSSSize to store all LogicalSize's dimensions in CSS pixels.
7725 return ToString(CSSSize(CSSPixel::FromAppUnits(aSize.ISize(aWM)),
7726 CSSPixel::FromAppUnits(aSize.BSize(aWM))));
7728 return ToString(aSize);
7731 // Debugging
7732 void nsIFrame::ListGeneric(nsACString& aTo, const char* aPrefix,
7733 ListFlags aFlags) const {
7734 aTo += aPrefix;
7735 aTo += ListTag();
7736 if (HasView()) {
7737 aTo += nsPrintfCString(" [view=%p]", static_cast<void*>(GetView()));
7739 if (GetParent()) {
7740 aTo += nsPrintfCString(" parent=%p", static_cast<void*>(GetParent()));
7742 if (GetNextSibling()) {
7743 aTo += nsPrintfCString(" next=%p", static_cast<void*>(GetNextSibling()));
7745 if (GetPrevContinuation()) {
7746 bool fluid = GetPrevInFlow() == GetPrevContinuation();
7747 aTo += nsPrintfCString(" prev-%s=%p", fluid ? "in-flow" : "continuation",
7748 static_cast<void*>(GetPrevContinuation()));
7750 if (GetNextContinuation()) {
7751 bool fluid = GetNextInFlow() == GetNextContinuation();
7752 aTo += nsPrintfCString(" next-%s=%p", fluid ? "in-flow" : "continuation",
7753 static_cast<void*>(GetNextContinuation()));
7755 void* IBsibling = GetProperty(IBSplitSibling());
7756 if (IBsibling) {
7757 aTo += nsPrintfCString(" IBSplitSibling=%p", IBsibling);
7759 void* IBprevsibling = GetProperty(IBSplitPrevSibling());
7760 if (IBprevsibling) {
7761 aTo += nsPrintfCString(" IBSplitPrevSibling=%p", IBprevsibling);
7763 if (nsLayoutUtils::FontSizeInflationEnabled(PresContext())) {
7764 if (HasAnyStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT)) {
7765 aTo += nsPrintfCString(" FFR");
7766 if (nsFontInflationData* data =
7767 nsFontInflationData::FindFontInflationDataFor(this)) {
7768 aTo += nsPrintfCString(
7769 ",enabled=%s,UIS=%s", data->InflationEnabled() ? "yes" : "no",
7770 ConvertToString(data->UsableISize(), aFlags).c_str());
7773 if (HasAnyStateBits(NS_FRAME_FONT_INFLATION_CONTAINER)) {
7774 aTo += nsPrintfCString(" FIC");
7776 aTo += nsPrintfCString(" FI=%f", nsLayoutUtils::FontSizeInflationFor(this));
7778 aTo += nsPrintfCString(" %s", ConvertToString(mRect, aFlags).c_str());
7780 mozilla::WritingMode wm = GetWritingMode();
7781 if (wm.IsVertical() || wm.IsBidiRTL()) {
7782 aTo +=
7783 nsPrintfCString(" wm=%s logical-size=(%s)", ToString(wm).c_str(),
7784 ConvertToString(GetLogicalSize(), wm, aFlags).c_str());
7787 nsIFrame* parent = GetParent();
7788 if (parent) {
7789 WritingMode pWM = parent->GetWritingMode();
7790 if (pWM.IsVertical() || pWM.IsBidiRTL()) {
7791 nsSize containerSize = parent->mRect.Size();
7792 LogicalRect lr(pWM, mRect, containerSize);
7793 aTo += nsPrintfCString(" parent-wm=%s cs=(%s) logical-rect=%s",
7794 ToString(pWM).c_str(),
7795 ConvertToString(containerSize, aFlags).c_str(),
7796 ConvertToString(lr, pWM, aFlags).c_str());
7799 nsIFrame* f = const_cast<nsIFrame*>(this);
7800 if (f->HasOverflowAreas()) {
7801 nsRect vo = f->InkOverflowRect();
7802 if (!vo.IsEqualEdges(mRect)) {
7803 aTo += nsPrintfCString(" ink-overflow=%s",
7804 ConvertToString(vo, aFlags).c_str());
7806 nsRect so = f->ScrollableOverflowRect();
7807 if (!so.IsEqualEdges(mRect)) {
7808 aTo += nsPrintfCString(" scr-overflow=%s",
7809 ConvertToString(so, aFlags).c_str());
7812 bool hasNormalPosition;
7813 nsPoint normalPosition = GetNormalPosition(&hasNormalPosition);
7814 if (hasNormalPosition) {
7815 aTo += nsPrintfCString(" normal-position=%s",
7816 ConvertToString(normalPosition, aFlags).c_str());
7818 if (HasProperty(BidiDataProperty())) {
7819 FrameBidiData bidi = GetBidiData();
7820 aTo += nsPrintfCString(" bidi(%d,%d,%d)", bidi.baseLevel,
7821 bidi.embeddingLevel, bidi.precedingControl);
7823 if (IsTransformed()) {
7824 aTo += nsPrintfCString(" transformed");
7826 if (ChildrenHavePerspective()) {
7827 aTo += nsPrintfCString(" perspective");
7829 if (Extend3DContext()) {
7830 aTo += nsPrintfCString(" extend-3d");
7832 if (Combines3DTransformWithAncestors()) {
7833 aTo += nsPrintfCString(" combines-3d-transform-with-ancestors");
7835 if (mContent) {
7836 aTo += nsPrintfCString(" [content=%p]", static_cast<void*>(mContent));
7838 aTo += nsPrintfCString(" [cs=%p", static_cast<void*>(mComputedStyle));
7839 if (mComputedStyle) {
7840 auto pseudoType = mComputedStyle->GetPseudoType();
7841 aTo += ToString(pseudoType).c_str();
7843 aTo += "]";
7846 void nsIFrame::List(FILE* out, const char* aPrefix, ListFlags aFlags) const {
7847 nsCString str;
7848 ListGeneric(str, aPrefix, aFlags);
7849 fprintf_stderr(out, "%s\n", str.get());
7852 void nsIFrame::ListTextRuns(FILE* out) const {
7853 nsTHashtable<nsVoidPtrHashKey> seen;
7854 ListTextRuns(out, seen);
7857 void nsIFrame::ListTextRuns(FILE* out,
7858 nsTHashtable<nsVoidPtrHashKey>& aSeen) const {
7859 for (const auto& childList : ChildLists()) {
7860 for (const nsIFrame* kid : childList.mList) {
7861 kid->ListTextRuns(out, aSeen);
7866 void nsIFrame::ListMatchedRules(FILE* out, const char* aPrefix) const {
7867 nsTArray<const RawServoStyleRule*> rawRuleList;
7868 Servo_ComputedValues_GetStyleRuleList(mComputedStyle, &rawRuleList);
7869 for (const RawServoStyleRule* rawRule : rawRuleList) {
7870 nsAutoCString ruleText;
7871 Servo_StyleRule_GetCssText(rawRule, &ruleText);
7872 fprintf_stderr(out, "%s%s\n", aPrefix, ruleText.get());
7876 void nsIFrame::ListWithMatchedRules(FILE* out, const char* aPrefix) const {
7877 fprintf_stderr(out, "%s%s\n", aPrefix, ListTag().get());
7879 nsCString rulePrefix;
7880 rulePrefix += aPrefix;
7881 rulePrefix += " ";
7882 ListMatchedRules(out, rulePrefix.get());
7885 nsresult nsIFrame::GetFrameName(nsAString& aResult) const {
7886 return MakeFrameName(u"Frame"_ns, aResult);
7889 nsresult nsIFrame::MakeFrameName(const nsAString& aType,
7890 nsAString& aResult) const {
7891 aResult = aType;
7892 if (mContent && !mContent->IsText()) {
7893 nsAutoString buf;
7894 mContent->NodeInfo()->NameAtom()->ToString(buf);
7895 if (IsSubDocumentFrame()) {
7896 nsAutoString src;
7897 mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::src, src);
7898 buf.AppendLiteral(" src=");
7899 buf.Append(src);
7901 aResult.Append('(');
7902 aResult.Append(buf);
7903 aResult.Append(')');
7905 aResult.Append('(');
7906 aResult.AppendInt(ContentIndexInContainer(this));
7907 aResult.Append(')');
7908 return NS_OK;
7911 void nsIFrame::DumpFrameTree() const {
7912 PresShell()->GetRootFrame()->List(stderr);
7915 void nsIFrame::DumpFrameTreeInCSSPixels() const {
7916 PresShell()->GetRootFrame()->List(stderr, "", ListFlag::DisplayInCSSPixels);
7919 void nsIFrame::DumpFrameTreeLimited() const { List(stderr); }
7920 void nsIFrame::DumpFrameTreeLimitedInCSSPixels() const {
7921 List(stderr, "", ListFlag::DisplayInCSSPixels);
7924 #endif
7926 bool nsIFrame::IsVisibleForPainting() { return StyleVisibility()->IsVisible(); }
7928 bool nsIFrame::IsVisibleOrCollapsedForPainting() {
7929 return StyleVisibility()->IsVisibleOrCollapsed();
7932 /* virtual */
7933 bool nsIFrame::IsEmpty() { return false; }
7935 bool nsIFrame::CachedIsEmpty() {
7936 MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_DIRTY),
7937 "Must only be called on reflowed lines");
7938 return IsEmpty();
7941 /* virtual */
7942 bool nsIFrame::IsSelfEmpty() { return false; }
7944 nsresult nsIFrame::GetSelectionController(nsPresContext* aPresContext,
7945 nsISelectionController** aSelCon) {
7946 if (!aPresContext || !aSelCon) return NS_ERROR_INVALID_ARG;
7948 nsIFrame* frame = this;
7949 while (frame && frame->HasAnyStateBits(NS_FRAME_INDEPENDENT_SELECTION)) {
7950 nsITextControlFrame* tcf = do_QueryFrame(frame);
7951 if (tcf) {
7952 return tcf->GetOwnedSelectionController(aSelCon);
7954 frame = frame->GetParent();
7957 *aSelCon = do_AddRef(aPresContext->PresShell()).take();
7958 return NS_OK;
7961 already_AddRefed<nsFrameSelection> nsIFrame::GetFrameSelection() {
7962 RefPtr<nsFrameSelection> fs =
7963 const_cast<nsFrameSelection*>(GetConstFrameSelection());
7964 return fs.forget();
7967 const nsFrameSelection* nsIFrame::GetConstFrameSelection() const {
7968 nsIFrame* frame = const_cast<nsIFrame*>(this);
7969 while (frame && frame->HasAnyStateBits(NS_FRAME_INDEPENDENT_SELECTION)) {
7970 nsITextControlFrame* tcf = do_QueryFrame(frame);
7971 if (tcf) {
7972 return tcf->GetOwnedFrameSelection();
7974 frame = frame->GetParent();
7977 return PresShell()->ConstFrameSelection();
7980 bool nsIFrame::IsFrameSelected() const {
7981 NS_ASSERTION(!GetContent() || GetContent()->IsMaybeSelected(),
7982 "use the public IsSelected() instead");
7983 return GetContent()->IsSelected(0, GetContent()->GetChildCount());
7986 nsresult nsIFrame::GetPointFromOffset(int32_t inOffset, nsPoint* outPoint) {
7987 MOZ_ASSERT(outPoint != nullptr, "Null parameter");
7988 nsRect contentRect = GetContentRectRelativeToSelf();
7989 nsPoint pt = contentRect.TopLeft();
7990 if (mContent) {
7991 nsIContent* newContent = mContent->GetParent();
7992 if (newContent) {
7993 int32_t newOffset = newContent->ComputeIndexOf(mContent);
7995 // Find the direction of the frame from the EmbeddingLevelProperty,
7996 // which is the resolved bidi level set in
7997 // nsBidiPresUtils::ResolveParagraph (odd levels = right-to-left).
7998 // If the embedding level isn't set, just use the CSS direction
7999 // property.
8000 bool hasBidiData;
8001 FrameBidiData bidiData = GetProperty(BidiDataProperty(), &hasBidiData);
8002 bool isRTL = hasBidiData
8003 ? IS_LEVEL_RTL(bidiData.embeddingLevel)
8004 : StyleVisibility()->mDirection == StyleDirection::Rtl;
8005 if ((!isRTL && inOffset > newOffset) ||
8006 (isRTL && inOffset <= newOffset)) {
8007 pt = contentRect.TopRight();
8011 *outPoint = pt;
8012 return NS_OK;
8015 nsresult nsIFrame::GetCharacterRectsInRange(int32_t aInOffset, int32_t aLength,
8016 nsTArray<nsRect>& aOutRect) {
8017 /* no text */
8018 return NS_ERROR_FAILURE;
8021 nsresult nsIFrame::GetChildFrameContainingOffset(int32_t inContentOffset,
8022 bool inHint,
8023 int32_t* outFrameContentOffset,
8024 nsIFrame** outChildFrame) {
8025 MOZ_ASSERT(outChildFrame && outFrameContentOffset, "Null parameter");
8026 *outFrameContentOffset = (int32_t)inHint;
8027 // the best frame to reflect any given offset would be a visible frame if
8028 // possible i.e. we are looking for a valid frame to place the blinking caret
8029 nsRect rect = GetRect();
8030 if (!rect.width || !rect.height) {
8031 // if we have a 0 width or height then lets look for another frame that
8032 // possibly has the same content. If we have no frames in flow then just
8033 // let us return 'this' frame
8034 nsIFrame* nextFlow = GetNextInFlow();
8035 if (nextFlow)
8036 return nextFlow->GetChildFrameContainingOffset(
8037 inContentOffset, inHint, outFrameContentOffset, outChildFrame);
8039 *outChildFrame = this;
8040 return NS_OK;
8044 // What I've pieced together about this routine:
8045 // Starting with a block frame (from which a line frame can be gotten)
8046 // and a line number, drill down and get the first/last selectable
8047 // frame on that line, depending on aPos->mDirection.
8048 // aOutSideLimit != 0 means ignore aLineStart, instead work from
8049 // the end (if > 0) or beginning (if < 0).
8051 nsresult nsIFrame::GetNextPrevLineFromeBlockFrame(nsPresContext* aPresContext,
8052 nsPeekOffsetStruct* aPos,
8053 nsIFrame* aBlockFrame,
8054 int32_t aLineStart,
8055 int8_t aOutSideLimit) {
8056 // magic numbers aLineStart will be -1 for end of block 0 will be start of
8057 // block
8058 if (!aBlockFrame || !aPos) return NS_ERROR_NULL_POINTER;
8060 aPos->mResultFrame = nullptr;
8061 aPos->mResultContent = nullptr;
8062 aPos->mAttach = aPos->mDirection == eDirNext ? CARET_ASSOCIATE_AFTER
8063 : CARET_ASSOCIATE_BEFORE;
8065 const nsAutoLineIterator it = aBlockFrame->GetLineIterator();
8066 if (!it) {
8067 return NS_ERROR_FAILURE;
8069 int32_t searchingLine = aLineStart;
8070 int32_t countLines = it->GetNumLines();
8071 if (aOutSideLimit > 0) // start at end
8072 searchingLine = countLines;
8073 else if (aOutSideLimit < 0) // start at beginning
8074 searchingLine = -1; //"next" will be 0
8075 else if ((aPos->mDirection == eDirPrevious && searchingLine == 0) ||
8076 (aPos->mDirection == eDirNext &&
8077 searchingLine >= (countLines - 1))) {
8078 // we need to jump to new block frame.
8079 return NS_ERROR_FAILURE;
8081 nsIFrame* resultFrame = nullptr;
8082 nsIFrame* farStoppingFrame = nullptr; // we keep searching until we find a
8083 // "this" frame then we go to next line
8084 nsIFrame* nearStoppingFrame = nullptr; // if we are backing up from edge,
8085 // stop here
8086 nsIFrame* firstFrame;
8087 nsIFrame* lastFrame;
8088 bool isBeforeFirstFrame, isAfterLastFrame;
8089 bool found = false;
8091 nsresult result = NS_OK;
8092 while (!found) {
8093 if (aPos->mDirection == eDirPrevious)
8094 searchingLine--;
8095 else
8096 searchingLine++;
8097 if ((aPos->mDirection == eDirPrevious && searchingLine < 0) ||
8098 (aPos->mDirection == eDirNext && searchingLine >= countLines)) {
8099 // we need to jump to new block frame.
8100 return NS_ERROR_FAILURE;
8102 auto line = it->GetLine(searchingLine).unwrap();
8103 if (!line.mNumFramesOnLine) {
8104 continue;
8106 lastFrame = firstFrame = line.mFirstFrameOnLine;
8107 for (int32_t lineFrameCount = line.mNumFramesOnLine; lineFrameCount > 1;
8108 lineFrameCount--) {
8109 result = it->GetNextSiblingOnLine(lastFrame, searchingLine);
8110 if (NS_FAILED(result) || !lastFrame) {
8111 NS_ERROR("GetLine promised more frames than could be found");
8112 return NS_ERROR_FAILURE;
8115 GetLastLeaf(&lastFrame);
8117 if (aPos->mDirection == eDirNext) {
8118 nearStoppingFrame = firstFrame;
8119 farStoppingFrame = lastFrame;
8120 } else {
8121 nearStoppingFrame = lastFrame;
8122 farStoppingFrame = firstFrame;
8124 nsPoint offset;
8125 nsView* view; // used for call of get offset from view
8126 aBlockFrame->GetOffsetFromView(offset, &view);
8127 nsPoint newDesiredPos =
8128 aPos->mDesiredCaretPos -
8129 offset; // get desired position into blockframe coords
8130 result = it->FindFrameAt(searchingLine, newDesiredPos, &resultFrame,
8131 &isBeforeFirstFrame, &isAfterLastFrame);
8132 if (NS_FAILED(result)) {
8133 continue;
8136 if (resultFrame) {
8137 // check to see if this is ANOTHER blockframe inside the other one if so
8138 // then call into its lines
8139 nsAutoLineIterator newIt = resultFrame->GetLineIterator();
8140 if (newIt) {
8141 aPos->mResultFrame = resultFrame;
8142 return NS_OK;
8144 // resultFrame is not a block frame
8145 result = NS_ERROR_FAILURE;
8147 nsCOMPtr<nsIFrameEnumerator> frameTraversal;
8148 result = NS_NewFrameTraversal(getter_AddRefs(frameTraversal),
8149 aPresContext, resultFrame, ePostOrder,
8150 false, // aVisual
8151 aPos->mScrollViewStop,
8152 false, // aFollowOOFs
8153 false // aSkipPopupChecks
8155 if (NS_FAILED(result)) return result;
8157 auto FoundValidFrame = [aPos](const ContentOffsets& aOffsets,
8158 const nsIFrame* aFrame) {
8159 if (!aOffsets.content) {
8160 return false;
8162 if (!aFrame->IsSelectable(nullptr)) {
8163 return false;
8165 if (aPos->mForceEditableRegion && !aOffsets.content->IsEditable()) {
8166 return false;
8168 return true;
8171 nsIFrame* storeOldResultFrame = resultFrame;
8172 while (!found) {
8173 nsPoint point;
8174 nsRect tempRect = resultFrame->GetRect();
8175 nsPoint offset;
8176 nsView* view; // used for call of get offset from view
8177 resultFrame->GetOffsetFromView(offset, &view);
8178 if (!view) {
8179 return NS_ERROR_FAILURE;
8181 if (resultFrame->GetWritingMode().IsVertical()) {
8182 point.y = aPos->mDesiredCaretPos.y;
8183 point.x = tempRect.width + offset.x;
8184 } else {
8185 point.y = tempRect.height + offset.y;
8186 point.x = aPos->mDesiredCaretPos.x;
8189 // special check. if we allow non-text selection then we can allow a hit
8190 // location to fall before a table. otherwise there is no way to get and
8191 // click signal to fall before a table (it being a line iterator itself)
8192 mozilla::PresShell* presShell = aPresContext->GetPresShell();
8193 if (!presShell) {
8194 return NS_ERROR_FAILURE;
8196 int16_t isEditor = presShell->GetSelectionFlags();
8197 isEditor = isEditor == nsISelectionDisplay::DISPLAY_ALL;
8198 if (isEditor) {
8199 if (resultFrame->IsTableWrapperFrame()) {
8200 if (((point.x - offset.x + tempRect.x) < 0) ||
8201 ((point.x - offset.x + tempRect.x) >
8202 tempRect.width)) // off left/right side
8204 nsIContent* content = resultFrame->GetContent();
8205 if (content) {
8206 nsIContent* parent = content->GetParent();
8207 if (parent) {
8208 aPos->mResultContent = parent;
8209 aPos->mContentOffset = parent->ComputeIndexOf(content);
8210 aPos->mAttach = CARET_ASSOCIATE_BEFORE;
8211 if ((point.x - offset.x + tempRect.x) > tempRect.width) {
8212 aPos->mContentOffset++; // go to end of this frame
8213 aPos->mAttach = CARET_ASSOCIATE_AFTER;
8215 // result frame is the result frames parent.
8216 aPos->mResultFrame = resultFrame->GetParent();
8217 return NS_POSITION_BEFORE_TABLE;
8224 if (!resultFrame->HasView()) {
8225 nsView* view;
8226 nsPoint offset;
8227 resultFrame->GetOffsetFromView(offset, &view);
8228 ContentOffsets offsets =
8229 resultFrame->GetContentOffsetsFromPoint(point - offset);
8230 aPos->mResultContent = offsets.content;
8231 aPos->mContentOffset = offsets.offset;
8232 aPos->mAttach = offsets.associate;
8233 if (FoundValidFrame(offsets, resultFrame)) {
8234 found = true;
8235 break;
8239 if (aPos->mDirection == eDirPrevious &&
8240 (resultFrame == farStoppingFrame))
8241 break;
8242 if (aPos->mDirection == eDirNext && (resultFrame == nearStoppingFrame))
8243 break;
8244 // always try previous on THAT line if that fails go the other way
8245 resultFrame = frameTraversal->Traverse(/* aForward = */ false);
8246 if (!resultFrame) return NS_ERROR_FAILURE;
8249 if (!found) {
8250 resultFrame = storeOldResultFrame;
8252 result = NS_NewFrameTraversal(getter_AddRefs(frameTraversal),
8253 aPresContext, resultFrame, eLeaf,
8254 false, // aVisual
8255 aPos->mScrollViewStop,
8256 false, // aFollowOOFs
8257 false // aSkipPopupChecks
8260 while (!found) {
8261 nsPoint point = aPos->mDesiredCaretPos;
8262 nsView* view;
8263 nsPoint offset;
8264 resultFrame->GetOffsetFromView(offset, &view);
8265 ContentOffsets offsets =
8266 resultFrame->GetContentOffsetsFromPoint(point - offset);
8267 aPos->mResultContent = offsets.content;
8268 aPos->mContentOffset = offsets.offset;
8269 aPos->mAttach = offsets.associate;
8270 if (FoundValidFrame(offsets, resultFrame)) {
8271 found = true;
8272 if (resultFrame == farStoppingFrame)
8273 aPos->mAttach = CARET_ASSOCIATE_BEFORE;
8274 else
8275 aPos->mAttach = CARET_ASSOCIATE_AFTER;
8276 break;
8278 if (aPos->mDirection == eDirPrevious &&
8279 (resultFrame == nearStoppingFrame))
8280 break;
8281 if (aPos->mDirection == eDirNext && (resultFrame == farStoppingFrame))
8282 break;
8283 // previous didnt work now we try "next"
8284 nsIFrame* tempFrame = frameTraversal->Traverse(/* aForward = */ true);
8285 if (!tempFrame) break;
8286 resultFrame = tempFrame;
8288 aPos->mResultFrame = resultFrame;
8289 } else {
8290 // we need to jump to new block frame.
8291 aPos->mAmount = eSelectLine;
8292 aPos->mStartOffset = 0;
8293 aPos->mAttach = aPos->mDirection == eDirNext ? CARET_ASSOCIATE_BEFORE
8294 : CARET_ASSOCIATE_AFTER;
8295 if (aPos->mDirection == eDirPrevious)
8296 aPos->mStartOffset = -1; // start from end
8297 return aBlockFrame->PeekOffset(aPos);
8300 return NS_OK;
8303 nsIFrame::CaretPosition nsIFrame::GetExtremeCaretPosition(bool aStart) {
8304 CaretPosition result;
8306 FrameTarget targetFrame = DrillDownToSelectionFrame(this, !aStart, 0);
8307 FrameContentRange range = GetRangeForFrame(targetFrame.frame);
8308 result.mResultContent = range.content;
8309 result.mContentOffset = aStart ? range.start : range.end;
8310 return result;
8313 // If this is a preformatted text frame, see if it ends with a newline
8314 static nsContentAndOffset FindLineBreakInText(nsIFrame* aFrame,
8315 nsDirection aDirection) {
8316 nsContentAndOffset result;
8318 if (aFrame->IsGeneratedContentFrame() ||
8319 !aFrame->HasSignificantTerminalNewline()) {
8320 return result;
8323 int32_t startOffset, endOffset;
8324 aFrame->GetOffsets(startOffset, endOffset);
8325 result.mContent = aFrame->GetContent();
8326 result.mOffset = endOffset - (aDirection == eDirPrevious ? 0 : 1);
8327 return result;
8330 // Find the first (or last) descendant of the given frame
8331 // which is either a block-level frame or a BRFrame, or some other kind of break
8332 // which stops the line.
8333 static nsContentAndOffset FindLineBreakingFrame(nsIFrame* aFrame,
8334 nsDirection aDirection) {
8335 nsContentAndOffset result;
8337 if (aFrame->IsGeneratedContentFrame()) {
8338 return result;
8341 // Treat form controls as inline leaves
8342 // XXX we really need a way to determine whether a frame is inline-level
8343 if (static_cast<nsIFormControlFrame*>(do_QueryFrame(aFrame))) {
8344 return result;
8347 // Check the frame itself
8348 // Fall through block-in-inline split frames because their mContent is
8349 // the content of the inline frames they were created from. The
8350 // first/last child of such frames is the real block frame we're
8351 // looking for.
8352 if ((aFrame->IsBlockOutside() &&
8353 !aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) ||
8354 aFrame->IsBrFrame()) {
8355 nsIContent* content = aFrame->GetContent();
8356 result.mContent = content->GetParent();
8357 // In some cases (bug 310589, bug 370174) we end up here with a null
8358 // content. This probably shouldn't ever happen, but since it sometimes
8359 // does, we want to avoid crashing here.
8360 NS_ASSERTION(result.mContent, "Unexpected orphan content");
8361 if (result.mContent)
8362 result.mOffset = result.mContent->ComputeIndexOf(content) +
8363 (aDirection == eDirPrevious ? 1 : 0);
8364 return result;
8367 result = FindLineBreakInText(aFrame, aDirection);
8368 if (result.mContent) {
8369 return result;
8372 // Iterate over children and call ourselves recursively
8373 if (aDirection == eDirPrevious) {
8374 nsIFrame* child =
8375 aFrame->GetChildList(nsIFrame::kPrincipalList).LastChild();
8376 while (child && !result.mContent) {
8377 result = FindLineBreakingFrame(child, aDirection);
8378 child = child->GetPrevSibling();
8380 } else { // eDirNext
8381 nsIFrame* child = aFrame->PrincipalChildList().FirstChild();
8382 while (child && !result.mContent) {
8383 result = FindLineBreakingFrame(child, aDirection);
8384 child = child->GetNextSibling();
8387 return result;
8390 nsresult nsIFrame::PeekOffsetForParagraph(nsPeekOffsetStruct* aPos) {
8391 nsIFrame* frame = this;
8392 nsContentAndOffset blockFrameOrBR;
8393 blockFrameOrBR.mContent = nullptr;
8394 bool reachedLimit = frame->IsBlockOutside() || IsEditingHost(frame);
8396 auto traverse = [&aPos](nsIFrame* current) {
8397 return aPos->mDirection == eDirPrevious ? current->GetPrevSibling()
8398 : current->GetNextSibling();
8401 // Go through containing frames until reaching a block frame.
8402 // In each step, search the previous (or next) siblings for the closest
8403 // "stop frame" (a block frame or a BRFrame).
8404 // If found, set it to be the selection boundary and abort.
8405 while (!reachedLimit) {
8406 nsIFrame* parent = frame->GetParent();
8407 // Treat a frame associated with the root content as if it were a block
8408 // frame.
8409 if (!frame->mContent || !frame->mContent->GetParent()) {
8410 reachedLimit = true;
8411 break;
8414 if (aPos->mDirection == eDirNext) {
8415 // Try to find our own line-break before looking at our siblings.
8416 blockFrameOrBR = FindLineBreakInText(frame, eDirNext);
8419 nsIFrame* sibling = traverse(frame);
8420 while (sibling && !blockFrameOrBR.mContent) {
8421 blockFrameOrBR = FindLineBreakingFrame(sibling, aPos->mDirection);
8422 sibling = traverse(sibling);
8424 if (blockFrameOrBR.mContent) {
8425 aPos->mResultContent = blockFrameOrBR.mContent;
8426 aPos->mContentOffset = blockFrameOrBR.mOffset;
8427 break;
8429 frame = parent;
8430 reachedLimit = frame && (frame->IsBlockOutside() || IsEditingHost(frame));
8433 if (reachedLimit) { // no "stop frame" found
8434 aPos->mResultContent = frame->GetContent();
8435 if (aPos->mDirection == eDirPrevious) {
8436 aPos->mContentOffset = 0;
8437 } else if (aPos->mResultContent) {
8438 aPos->mContentOffset = aPos->mResultContent->GetChildCount();
8441 return NS_OK;
8444 // Determine movement direction relative to frame
8445 static bool IsMovingInFrameDirection(const nsIFrame* frame,
8446 nsDirection aDirection, bool aVisual) {
8447 bool isReverseDirection =
8448 aVisual && nsBidiPresUtils::IsReversedDirectionFrame(frame);
8449 return aDirection == (isReverseDirection ? eDirPrevious : eDirNext);
8452 // Determines "are we looking for a boundary between whitespace and
8453 // non-whitespace (in the direction we're moving in)". It is true when moving
8454 // forward and looking for a beginning of a word, or when moving backwards and
8455 // looking for an end of a word.
8456 static bool ShouldWordSelectionEatSpace(const nsPeekOffsetStruct& aPos) {
8457 if (aPos.mWordMovementType != eDefaultBehavior) {
8458 // aPos->mWordMovementType possible values:
8459 // eEndWord: eat the space if we're moving backwards
8460 // eStartWord: eat the space if we're moving forwards
8461 return (aPos.mWordMovementType == eEndWord) ==
8462 (aPos.mDirection == eDirPrevious);
8464 // Use the hidden preference which is based on operating system
8465 // behavior. This pref only affects whether moving forward by word
8466 // should go to the end of this word or start of the next word. When
8467 // going backwards, the start of the word is always used, on every
8468 // operating system.
8469 return aPos.mDirection == eDirNext &&
8470 StaticPrefs::layout_word_select_eat_space_to_next_word();
8473 enum class OffsetIsAtLineEdge : bool { No, Yes };
8475 static void SetPeekResultFromFrame(nsPeekOffsetStruct& aPos, nsIFrame* aFrame,
8476 int32_t aOffset,
8477 OffsetIsAtLineEdge aAtLineEdge) {
8478 FrameContentRange range = GetRangeForFrame(aFrame);
8479 aPos.mResultFrame = aFrame;
8480 aPos.mResultContent = range.content;
8481 // Output offset is relative to content, not frame
8482 aPos.mContentOffset =
8483 aOffset < 0 ? range.end + aOffset + 1 : range.start + aOffset;
8484 if (aAtLineEdge == OffsetIsAtLineEdge::Yes) {
8485 aPos.mAttach = aPos.mContentOffset == range.start ? CARET_ASSOCIATE_AFTER
8486 : CARET_ASSOCIATE_BEFORE;
8490 void nsIFrame::SelectablePeekReport::TransferTo(
8491 nsPeekOffsetStruct& aPos) const {
8492 return SetPeekResultFromFrame(aPos, mFrame, mOffset, OffsetIsAtLineEdge::No);
8495 nsIFrame::SelectablePeekReport::SelectablePeekReport(
8496 const mozilla::GenericErrorResult<nsresult>&& aErr) {
8497 MOZ_ASSERT(NS_FAILED(aErr.operator nsresult()));
8498 // Return an empty report
8501 nsresult nsIFrame::PeekOffsetForCharacter(nsPeekOffsetStruct* aPos,
8502 int32_t aOffset) {
8503 SelectablePeekReport current{this, aOffset};
8505 nsIFrame::FrameSearchResult peekSearchState = CONTINUE;
8507 while (peekSearchState != FOUND) {
8508 bool movingInFrameDirection = IsMovingInFrameDirection(
8509 current.mFrame, aPos->mDirection, aPos->mVisual);
8511 if (current.mJumpedLine) {
8512 // If we jumped lines, it's as if we found a character, but we still need
8513 // to eat non-renderable content on the new line.
8514 peekSearchState = current.PeekOffsetNoAmount(movingInFrameDirection);
8515 } else {
8516 PeekOffsetCharacterOptions options;
8517 options.mRespectClusters = aPos->mAmount == eSelectCluster;
8518 peekSearchState =
8519 current.PeekOffsetCharacter(movingInFrameDirection, options);
8522 current.mMovedOverNonSelectableText |=
8523 peekSearchState == CONTINUE_UNSELECTABLE;
8525 if (peekSearchState != FOUND) {
8526 SelectablePeekReport next = current.mFrame->GetFrameFromDirection(*aPos);
8527 if (next.Failed()) {
8528 return NS_ERROR_FAILURE;
8530 next.mJumpedLine |= current.mJumpedLine;
8531 next.mMovedOverNonSelectableText |= current.mMovedOverNonSelectableText;
8532 next.mHasSelectableFrame |= current.mHasSelectableFrame;
8533 current = next;
8536 // Found frame, but because we moved over non selectable text we want
8537 // the offset to be at the frame edge. Note that if we are extending the
8538 // selection, this doesn't matter.
8539 if (peekSearchState == FOUND && current.mMovedOverNonSelectableText &&
8540 (!aPos->mExtend || current.mHasSelectableFrame)) {
8541 int32_t start, end;
8542 current.mFrame->GetOffsets(start, end);
8543 current.mOffset = aPos->mDirection == eDirNext ? 0 : end - start;
8547 // Set outputs
8548 current.TransferTo(*aPos);
8549 // If we're dealing with a text frame and moving backward positions us at
8550 // the end of that line, decrease the offset by one to make sure that
8551 // we're placed before the linefeed character on the previous line.
8552 if (current.mOffset < 0 && current.mJumpedLine &&
8553 aPos->mDirection == eDirPrevious &&
8554 current.mFrame->HasSignificantTerminalNewline() &&
8555 !current.mIgnoredBrFrame) {
8556 --aPos->mContentOffset;
8558 return NS_OK;
8561 nsresult nsIFrame::PeekOffsetForWord(nsPeekOffsetStruct* aPos,
8562 int32_t aOffset) {
8563 SelectablePeekReport current{this, aOffset};
8564 bool shouldStopAtHardBreak =
8565 aPos->mWordMovementType == eDefaultBehavior &&
8566 StaticPrefs::layout_word_select_eat_space_to_next_word();
8567 bool wordSelectEatSpace = ShouldWordSelectionEatSpace(*aPos);
8569 PeekWordState state;
8570 while (true) {
8571 bool movingInFrameDirection = IsMovingInFrameDirection(
8572 current.mFrame, aPos->mDirection, aPos->mVisual);
8574 FrameSearchResult searchResult = current.mFrame->PeekOffsetWord(
8575 movingInFrameDirection, wordSelectEatSpace, aPos->mIsKeyboardSelect,
8576 &current.mOffset, &state, aPos->mTrimSpaces);
8577 if (searchResult == FOUND) {
8578 break;
8581 SelectablePeekReport next = current.mFrame->GetFrameFromDirection(*aPos);
8582 if (next.Failed()) {
8583 // If we've crossed the line boundary, check to make sure that we
8584 // have not consumed a trailing newline as whitespace if it's
8585 // significant.
8586 if (next.mJumpedLine && wordSelectEatSpace &&
8587 current.mFrame->HasSignificantTerminalNewline() &&
8588 current.mFrame->StyleText()->mWhiteSpace !=
8589 StyleWhiteSpace::PreLine) {
8590 current.mOffset -= 1;
8592 break;
8595 if (next.mJumpedLine && !wordSelectEatSpace && state.mSawBeforeType) {
8596 // We can't jump lines if we're looking for whitespace following
8597 // non-whitespace, and we already encountered non-whitespace.
8598 break;
8601 if (shouldStopAtHardBreak && next.mJumpedHardBreak) {
8603 * Prev, always: Jump and stop right there
8604 * Next, saw inline: just stop
8605 * Next, no inline: Jump and consume whitespaces
8607 if (aPos->mDirection == eDirPrevious) {
8608 // Try moving to the previous line if exists
8609 current.TransferTo(*aPos);
8610 current.mFrame->PeekOffsetForCharacter(aPos, current.mOffset);
8611 return NS_OK;
8613 if (state.mSawInlineCharacter || current.mJumpedHardBreak) {
8614 if (current.mFrame->HasSignificantTerminalNewline()) {
8615 current.mOffset -= 1;
8617 current.TransferTo(*aPos);
8618 return NS_OK;
8620 // Mark the state as whitespace and continue
8621 state.Update(false, true);
8624 if (next.mJumpedLine) {
8625 state.mContext.Truncate();
8627 current = next;
8628 // Jumping a line is equivalent to encountering whitespace
8629 // This affects only when it already met an actual character
8630 if (wordSelectEatSpace && next.mJumpedLine) {
8631 state.SetSawBeforeType();
8635 // Set outputs
8636 current.TransferTo(*aPos);
8637 return NS_OK;
8640 nsresult nsIFrame::PeekOffsetForLine(nsPeekOffsetStruct* aPos) {
8641 nsAutoLineIterator iter;
8642 nsIFrame* blockFrame = this;
8643 nsresult result = NS_ERROR_FAILURE;
8645 while (NS_FAILED(result)) {
8646 int32_t thisLine;
8647 MOZ_TRY_VAR(thisLine,
8648 blockFrame->GetLineNumber(aPos->mScrollViewStop, &blockFrame));
8649 iter = blockFrame->GetLineIterator();
8650 MOZ_ASSERT(iter, "GetLineNumber() succeeded but no block frame?");
8652 int edgeCase = 0; // no edge case. this should look at thisLine
8654 bool doneLooping = false; // tells us when no more block frames hit.
8655 // this part will find a frame or a block frame. if it's a block frame
8656 // it will "drill down" to find a viable frame or it will return an
8657 // error.
8658 nsIFrame* lastFrame = this;
8659 do {
8660 result = nsIFrame::GetNextPrevLineFromeBlockFrame(
8661 PresContext(), aPos, blockFrame, thisLine,
8662 edgeCase); // start from thisLine
8664 // we came back to same spot! keep going
8665 if (NS_SUCCEEDED(result) &&
8666 (!aPos->mResultFrame || aPos->mResultFrame == lastFrame)) {
8667 aPos->mResultFrame = nullptr;
8668 if (aPos->mDirection == eDirPrevious) {
8669 thisLine--;
8670 } else {
8671 thisLine++;
8673 } else { // if failure or success with different frame.
8674 doneLooping = true; // do not continue with while loop
8677 lastFrame = aPos->mResultFrame; // set last frame
8679 // make sure block element is not the same as the one we had before
8680 if (NS_SUCCEEDED(result) && aPos->mResultFrame &&
8681 blockFrame != aPos->mResultFrame) {
8682 /* SPECIAL CHECK FOR TABLE NAVIGATION
8683 tables need to navigate also and the frame that supports it is
8684 nsTableRowGroupFrame which is INSIDE nsTableWrapperFrame.
8685 If we have stumbled onto an nsTableWrapperFrame we need to drill
8686 into nsTableRowGroup if we hit a header or footer that's ok just
8687 go into them.
8689 bool searchTableBool = false;
8690 if (aPos->mResultFrame->IsTableWrapperFrame() ||
8691 aPos->mResultFrame->IsTableCellFrame()) {
8692 nsIFrame* frame =
8693 aPos->mResultFrame->PrincipalChildList().FirstChild();
8694 // got the table frame now
8695 // ok time to drill down to find iterator
8696 while (frame) {
8697 iter = frame->GetLineIterator();
8698 if (iter) {
8699 aPos->mResultFrame = frame;
8700 searchTableBool = true;
8701 result = NS_OK;
8702 break; // while(frame)
8704 result = NS_ERROR_FAILURE;
8705 frame = frame->PrincipalChildList().FirstChild();
8709 if (!searchTableBool) {
8710 iter = aPos->mResultFrame->GetLineIterator();
8711 result = iter ? NS_OK : NS_ERROR_FAILURE;
8714 // we've struck another block element!
8715 if (NS_SUCCEEDED(result) && iter) {
8716 doneLooping = false;
8717 if (aPos->mDirection == eDirPrevious) {
8718 edgeCase = 1; // far edge, search from end backwards
8719 } else {
8720 edgeCase = -1; // near edge search from beginning onwards
8722 thisLine = 0; // this line means nothing now.
8723 // everything else means something so keep looking "inside" the
8724 // block
8725 blockFrame = aPos->mResultFrame;
8726 } else {
8727 // THIS is to mean that everything is ok to the containing while
8728 // loop
8729 result = NS_OK;
8730 break;
8733 } while (!doneLooping);
8735 return result;
8738 nsresult nsIFrame::PeekOffsetForLineEdge(nsPeekOffsetStruct* aPos) {
8739 // Adjusted so that the caret can't get confused when content changes
8740 nsIFrame* blockFrame = AdjustFrameForSelectionStyles(this);
8741 int32_t thisLine;
8742 MOZ_TRY_VAR(thisLine,
8743 blockFrame->GetLineNumber(aPos->mScrollViewStop, &blockFrame));
8744 nsAutoLineIterator it = blockFrame->GetLineIterator();
8745 MOZ_ASSERT(it, "GetLineNumber() succeeded but no block frame?");
8747 nsIFrame* baseFrame = nullptr;
8748 bool endOfLine = (eSelectEndLine == aPos->mAmount);
8750 if (aPos->mVisual && PresContext()->BidiEnabled()) {
8751 nsIFrame* firstFrame;
8752 bool isReordered;
8753 nsIFrame* lastFrame;
8754 MOZ_TRY(
8755 it->CheckLineOrder(thisLine, &isReordered, &firstFrame, &lastFrame));
8756 baseFrame = endOfLine ? lastFrame : firstFrame;
8757 } else {
8758 auto line = it->GetLine(thisLine).unwrap();
8760 nsIFrame* frame = line.mFirstFrameOnLine;
8761 bool lastFrameWasEditable = false;
8762 for (int32_t count = line.mNumFramesOnLine; count;
8763 --count, frame = frame->GetNextSibling()) {
8764 if (frame->IsGeneratedContentFrame()) {
8765 continue;
8767 // When jumping to the end of the line with the "end" key,
8768 // try to skip over brFrames
8769 if (endOfLine && line.mNumFramesOnLine > 1 && frame->IsBrFrame() &&
8770 lastFrameWasEditable == frame->GetContent()->IsEditable()) {
8771 continue;
8773 lastFrameWasEditable =
8774 frame->GetContent() && frame->GetContent()->IsEditable();
8775 baseFrame = frame;
8776 if (!endOfLine) {
8777 break;
8781 if (!baseFrame) {
8782 return NS_ERROR_FAILURE;
8784 FrameTarget targetFrame = DrillDownToSelectionFrame(baseFrame, endOfLine, 0);
8785 SetPeekResultFromFrame(*aPos, targetFrame.frame, endOfLine ? -1 : 0,
8786 OffsetIsAtLineEdge::Yes);
8787 if (endOfLine && targetFrame.frame->HasSignificantTerminalNewline()) {
8788 // Do not position the caret after the terminating newline if we're
8789 // trying to move to the end of line (see bug 596506)
8790 --aPos->mContentOffset;
8792 if (!aPos->mResultContent) {
8793 return NS_ERROR_FAILURE;
8795 return NS_OK;
8798 nsresult nsIFrame::PeekOffset(nsPeekOffsetStruct* aPos) {
8799 MOZ_ASSERT(aPos);
8801 if (NS_WARN_IF(HasAnyStateBits(NS_FRAME_IS_DIRTY))) {
8802 // FIXME(Bug 1654362): <caption> currently can remain dirty.
8803 return NS_ERROR_UNEXPECTED;
8806 // Translate content offset to be relative to frame
8807 int32_t offset = aPos->mStartOffset - GetRangeForFrame(this).start;
8809 switch (aPos->mAmount) {
8810 case eSelectCharacter:
8811 case eSelectCluster:
8812 return PeekOffsetForCharacter(aPos, offset);
8813 case eSelectWordNoSpace:
8814 // eSelectWordNoSpace means that we should not be eating any whitespace
8815 // when moving to the adjacent word. This means that we should set aPos->
8816 // mWordMovementType to eEndWord if we're moving forwards, and to
8817 // eStartWord if we're moving backwards.
8818 if (aPos->mDirection == eDirPrevious) {
8819 aPos->mWordMovementType = eStartWord;
8820 } else {
8821 aPos->mWordMovementType = eEndWord;
8823 // Intentionally fall through the eSelectWord case.
8824 [[fallthrough]];
8825 case eSelectWord:
8826 return PeekOffsetForWord(aPos, offset);
8827 case eSelectLine:
8828 return PeekOffsetForLine(aPos);
8829 case eSelectBeginLine:
8830 case eSelectEndLine:
8831 return PeekOffsetForLineEdge(aPos);
8832 case eSelectParagraph:
8833 return PeekOffsetForParagraph(aPos);
8834 default: {
8835 NS_ASSERTION(false, "Invalid amount");
8836 return NS_ERROR_FAILURE;
8839 return NS_OK;
8842 nsIFrame::FrameSearchResult nsIFrame::PeekOffsetNoAmount(bool aForward,
8843 int32_t* aOffset) {
8844 NS_ASSERTION(aOffset && *aOffset <= 1, "aOffset out of range");
8845 // Sure, we can stop right here.
8846 return FOUND;
8849 nsIFrame::FrameSearchResult nsIFrame::PeekOffsetCharacter(
8850 bool aForward, int32_t* aOffset, PeekOffsetCharacterOptions aOptions) {
8851 NS_ASSERTION(aOffset && *aOffset <= 1, "aOffset out of range");
8852 int32_t startOffset = *aOffset;
8853 // A negative offset means "end of frame", which in our case means offset 1.
8854 if (startOffset < 0) startOffset = 1;
8855 if (aForward == (startOffset == 0)) {
8856 // We're before the frame and moving forward, or after it and moving
8857 // backwards: skip to the other side and we're done.
8858 *aOffset = 1 - startOffset;
8859 return FOUND;
8861 return CONTINUE;
8864 nsIFrame::FrameSearchResult nsIFrame::PeekOffsetWord(
8865 bool aForward, bool aWordSelectEatSpace, bool aIsKeyboardSelect,
8866 int32_t* aOffset, PeekWordState* aState, bool /*aTrimSpaces*/) {
8867 NS_ASSERTION(aOffset && *aOffset <= 1, "aOffset out of range");
8868 int32_t startOffset = *aOffset;
8869 // This isn't text, so truncate the context
8870 aState->mContext.Truncate();
8871 if (startOffset < 0) startOffset = 1;
8872 if (aForward == (startOffset == 0)) {
8873 // We're before the frame and moving forward, or after it and moving
8874 // backwards. If we're looking for non-whitespace, we found it (without
8875 // skipping this frame).
8876 if (!aState->mAtStart) {
8877 if (aState->mLastCharWasPunctuation) {
8878 // We're not punctuation, so this is a punctuation boundary.
8879 if (BreakWordBetweenPunctuation(aState, aForward, false, false,
8880 aIsKeyboardSelect))
8881 return FOUND;
8882 } else {
8883 // This is not a punctuation boundary.
8884 if (aWordSelectEatSpace && aState->mSawBeforeType) return FOUND;
8887 // Otherwise skip to the other side and note that we encountered
8888 // non-whitespace.
8889 *aOffset = 1 - startOffset;
8890 aState->Update(false, // not punctuation
8891 false // not whitespace
8893 if (!aWordSelectEatSpace) aState->SetSawBeforeType();
8895 return CONTINUE;
8898 // static
8899 bool nsIFrame::BreakWordBetweenPunctuation(const PeekWordState* aState,
8900 bool aForward, bool aPunctAfter,
8901 bool aWhitespaceAfter,
8902 bool aIsKeyboardSelect) {
8903 NS_ASSERTION(aPunctAfter != aState->mLastCharWasPunctuation,
8904 "Call this only at punctuation boundaries");
8905 if (aState->mLastCharWasWhitespace) {
8906 // We always stop between whitespace and punctuation
8907 return true;
8909 if (!StaticPrefs::layout_word_select_stop_at_punctuation()) {
8910 // When this pref is false, we never stop at a punctuation boundary unless
8911 // it's followed by whitespace (in the relevant direction).
8912 return aWhitespaceAfter;
8914 if (!aIsKeyboardSelect) {
8915 // mouse caret movement (e.g. word selection) always stops at every
8916 // punctuation boundary
8917 return true;
8919 bool afterPunct = aForward ? aState->mLastCharWasPunctuation : aPunctAfter;
8920 if (!afterPunct) {
8921 // keyboard caret movement only stops after punctuation (in content order)
8922 return false;
8924 // Stop only if we've seen some non-punctuation since the last whitespace;
8925 // don't stop after punctuation that follows whitespace.
8926 return aState->mSeenNonPunctuationSinceWhitespace;
8929 nsresult nsIFrame::CheckVisibility(nsPresContext*, int32_t, int32_t, bool,
8930 bool*, bool*) {
8931 return NS_ERROR_NOT_IMPLEMENTED;
8934 Result<int32_t, nsresult> nsIFrame::GetLineNumber(bool aLockScroll,
8935 nsIFrame** aContainingBlock) {
8936 MOZ_ASSERT(aContainingBlock);
8938 nsIFrame* parentFrame = this;
8939 nsIFrame* frame;
8940 nsAutoLineIterator it;
8941 while (!it && parentFrame) {
8942 frame = parentFrame;
8943 if (frame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
8944 // if we are searching for a frame that is not in flow we will not find
8945 // it. we must instead look for its placeholder
8946 if (frame->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
8947 // abspos continuations don't have placeholders, get the fif
8948 frame = frame->FirstInFlow();
8950 frame = frame->GetPlaceholderFrame();
8951 if (!frame) {
8952 return Err(NS_ERROR_FAILURE);
8955 parentFrame = frame->GetParent();
8956 if (parentFrame) {
8957 if (aLockScroll && parentFrame->IsScrollFrame()) {
8958 return Err(NS_ERROR_FAILURE);
8960 it = parentFrame->GetLineIterator();
8963 if (!parentFrame || !it) {
8964 return Err(NS_ERROR_FAILURE);
8967 *aContainingBlock = parentFrame;
8968 int32_t line = it->FindLineContaining(frame);
8969 if (line < 0) {
8970 return Err(NS_ERROR_FAILURE);
8972 return line;
8975 Result<bool, nsresult> nsIFrame::IsVisuallyAtLineEdge(
8976 nsILineIterator* aLineIterator, int32_t aLine, nsDirection aDirection) {
8977 nsIFrame* firstFrame;
8978 nsIFrame* lastFrame;
8980 bool lineIsRTL = aLineIterator->GetDirection();
8981 bool isReordered;
8983 MOZ_TRY(aLineIterator->CheckLineOrder(aLine, &isReordered, &firstFrame,
8984 &lastFrame));
8986 nsIFrame** framePtr = aDirection == eDirPrevious ? &firstFrame : &lastFrame;
8987 if (!*framePtr) {
8988 return true;
8991 bool frameIsRTL = (nsBidiPresUtils::FrameDirection(*framePtr) == NSBIDI_RTL);
8992 if ((frameIsRTL == lineIsRTL) == (aDirection == eDirPrevious)) {
8993 nsIFrame::GetFirstLeaf(framePtr);
8994 } else {
8995 nsIFrame::GetLastLeaf(framePtr);
8997 return *framePtr == this;
9000 Result<bool, nsresult> nsIFrame::IsLogicallyAtLineEdge(
9001 nsILineIterator* aLineIterator, int32_t aLine, nsDirection aDirection) {
9002 auto line = aLineIterator->GetLine(aLine).unwrap();
9004 if (aDirection == eDirPrevious) {
9005 nsIFrame* firstFrame = line.mFirstFrameOnLine;
9006 nsIFrame::GetFirstLeaf(&firstFrame);
9007 return firstFrame == this;
9010 // eDirNext
9011 nsIFrame* lastFrame = line.mFirstFrameOnLine;
9012 for (int32_t lineFrameCount = line.mNumFramesOnLine; lineFrameCount > 1;
9013 lineFrameCount--) {
9014 MOZ_TRY(aLineIterator->GetNextSiblingOnLine(lastFrame, aLine));
9015 if (!lastFrame) {
9016 NS_ERROR("should not be reached nsIFrame");
9017 return Err(NS_ERROR_FAILURE);
9020 nsIFrame::GetLastLeaf(&lastFrame);
9021 return lastFrame == this;
9024 nsIFrame::SelectablePeekReport nsIFrame::GetFrameFromDirection(
9025 nsDirection aDirection, bool aVisual, bool aJumpLines, bool aScrollViewStop,
9026 bool aForceEditableRegion) {
9027 SelectablePeekReport result;
9029 nsPresContext* presContext = PresContext();
9030 bool needsVisualTraversal = aVisual && presContext->BidiEnabled();
9031 nsCOMPtr<nsIFrameEnumerator> frameTraversal;
9032 MOZ_TRY(NS_NewFrameTraversal(getter_AddRefs(frameTraversal), presContext,
9033 this, eLeaf, needsVisualTraversal,
9034 aScrollViewStop,
9035 true, // aFollowOOFs
9036 false // aSkipPopupChecks
9039 // Find the prev/next selectable frame
9040 bool selectable = false;
9041 nsIFrame* traversedFrame = this;
9042 while (!selectable) {
9043 nsIFrame* blockFrame;
9045 int32_t thisLine;
9046 MOZ_TRY_VAR(thisLine,
9047 traversedFrame->GetLineNumber(aScrollViewStop, &blockFrame));
9049 nsAutoLineIterator it = blockFrame->GetLineIterator();
9051 bool atLineEdge;
9052 MOZ_TRY_VAR(
9053 atLineEdge,
9054 needsVisualTraversal
9055 ? traversedFrame->IsVisuallyAtLineEdge(it, thisLine, aDirection)
9056 : traversedFrame->IsLogicallyAtLineEdge(it, thisLine, aDirection));
9057 if (atLineEdge) {
9058 result.mJumpedLine = true;
9059 if (!aJumpLines) {
9060 return result; // we are done. cannot jump lines
9062 int32_t lineToCheckWrap =
9063 aDirection == eDirPrevious ? thisLine - 1 : thisLine;
9064 if (lineToCheckWrap < 0 ||
9065 !it->GetLine(lineToCheckWrap).unwrap().mIsWrapped) {
9066 result.mJumpedHardBreak = true;
9070 traversedFrame = frameTraversal->Traverse(aDirection == eDirNext);
9071 if (!traversedFrame) {
9072 return result;
9075 auto IsSelectable = [aForceEditableRegion](const nsIFrame* aFrame) {
9076 if (!aFrame->IsSelectable(nullptr)) {
9077 return false;
9079 return !aForceEditableRegion || aFrame->GetContent()->IsEditable();
9082 // Skip br frames, but only if we can select something before hitting the
9083 // end of the line or a non-selectable region.
9084 if (atLineEdge && aDirection == eDirPrevious &&
9085 traversedFrame->IsBrFrame()) {
9086 for (nsIFrame* current = traversedFrame->GetPrevSibling(); current;
9087 current = current->GetPrevSibling()) {
9088 if (!current->IsBlockOutside() && IsSelectable(current)) {
9089 if (!current->IsBrFrame()) {
9090 result.mIgnoredBrFrame = true;
9092 break;
9095 if (result.mIgnoredBrFrame) {
9096 continue;
9100 selectable = IsSelectable(traversedFrame);
9101 if (!selectable) {
9102 if (traversedFrame->IsSelectable(nullptr)) {
9103 result.mHasSelectableFrame = true;
9105 result.mMovedOverNonSelectableText = true;
9107 } // while (!selectable)
9109 result.mOffset = (aDirection == eDirNext) ? 0 : -1;
9111 if (aVisual && nsBidiPresUtils::IsReversedDirectionFrame(traversedFrame)) {
9112 // The new frame is reverse-direction, go to the other end
9113 result.mOffset = -1 - result.mOffset;
9115 result.mFrame = traversedFrame;
9116 return result;
9119 nsIFrame::SelectablePeekReport nsIFrame::GetFrameFromDirection(
9120 const nsPeekOffsetStruct& aPos) {
9121 return GetFrameFromDirection(aPos.mDirection, aPos.mVisual, aPos.mJumpLines,
9122 aPos.mScrollViewStop, aPos.mForceEditableRegion);
9125 nsView* nsIFrame::GetClosestView(nsPoint* aOffset) const {
9126 nsPoint offset(0, 0);
9127 for (const nsIFrame* f = this; f; f = f->GetParent()) {
9128 if (f->HasView()) {
9129 if (aOffset) *aOffset = offset;
9130 return f->GetView();
9132 offset += f->GetPosition();
9135 MOZ_ASSERT_UNREACHABLE("No view on any parent? How did that happen?");
9136 return nullptr;
9139 /* virtual */
9140 void nsIFrame::ChildIsDirty(nsIFrame* aChild) {
9141 MOZ_ASSERT_UNREACHABLE(
9142 "should never be called on a frame that doesn't "
9143 "inherit from nsContainerFrame");
9146 #ifdef ACCESSIBILITY
9147 a11y::AccType nsIFrame::AccessibleType() {
9148 if (IsTableCaption() && !GetRect().IsEmpty()) {
9149 return a11y::eHTMLCaptionType;
9151 return a11y::eNoType;
9153 #endif
9155 bool nsIFrame::ClearOverflowRects() {
9156 if (mOverflow.mType == NS_FRAME_OVERFLOW_NONE) {
9157 return false;
9159 if (mOverflow.mType == NS_FRAME_OVERFLOW_LARGE) {
9160 RemoveProperty(OverflowAreasProperty());
9162 mOverflow.mType = NS_FRAME_OVERFLOW_NONE;
9163 return true;
9166 /** Set the overflowArea rect, storing it as deltas or a separate rect
9167 * depending on its size in relation to the primary frame rect.
9169 bool nsIFrame::SetOverflowAreas(const OverflowAreas& aOverflowAreas) {
9170 if (mOverflow.mType == NS_FRAME_OVERFLOW_LARGE) {
9171 OverflowAreas* overflow = GetOverflowAreasProperty();
9172 bool changed = *overflow != aOverflowAreas;
9173 *overflow = aOverflowAreas;
9175 // Don't bother with converting to the deltas form if we already
9176 // have a property.
9177 return changed;
9180 const nsRect& vis = aOverflowAreas.InkOverflow();
9181 uint32_t l = -vis.x, // left edge: positive delta is leftwards
9182 t = -vis.y, // top: positive is upwards
9183 r = vis.XMost() - mRect.width, // right: positive is rightwards
9184 b = vis.YMost() - mRect.height; // bottom: positive is downwards
9185 if (aOverflowAreas.ScrollableOverflow().IsEqualEdges(
9186 nsRect(nsPoint(0, 0), GetSize())) &&
9187 l <= NS_FRAME_OVERFLOW_DELTA_MAX && t <= NS_FRAME_OVERFLOW_DELTA_MAX &&
9188 r <= NS_FRAME_OVERFLOW_DELTA_MAX && b <= NS_FRAME_OVERFLOW_DELTA_MAX &&
9189 // we have to check these against zero because we *never* want to
9190 // set a frame as having no overflow in this function. This is
9191 // because FinishAndStoreOverflow calls this function prior to
9192 // SetRect based on whether the overflow areas match aNewSize.
9193 // In the case where the overflow areas exactly match mRect but
9194 // do not match aNewSize, we need to store overflow in a property
9195 // so that our eventual SetRect/SetSize will know that it has to
9196 // reset our overflow areas.
9197 (l | t | r | b) != 0) {
9198 VisualDeltas oldDeltas = mOverflow.mVisualDeltas;
9199 // It's a "small" overflow area so we store the deltas for each edge
9200 // directly in the frame, rather than allocating a separate rect.
9201 // If they're all zero, that's fine; we're setting things to
9202 // no-overflow.
9203 mOverflow.mVisualDeltas.mLeft = l;
9204 mOverflow.mVisualDeltas.mTop = t;
9205 mOverflow.mVisualDeltas.mRight = r;
9206 mOverflow.mVisualDeltas.mBottom = b;
9207 // There was no scrollable overflow before, and there isn't now.
9208 return oldDeltas != mOverflow.mVisualDeltas;
9209 } else {
9210 bool changed =
9211 !aOverflowAreas.ScrollableOverflow().IsEqualEdges(
9212 nsRect(nsPoint(0, 0), GetSize())) ||
9213 !aOverflowAreas.InkOverflow().IsEqualEdges(InkOverflowFromDeltas());
9215 // it's a large overflow area that we need to store as a property
9216 mOverflow.mType = NS_FRAME_OVERFLOW_LARGE;
9217 AddProperty(OverflowAreasProperty(), new OverflowAreas(aOverflowAreas));
9218 return changed;
9223 * Compute the union of the border boxes of aFrame and its descendants,
9224 * in aFrame's coordinate space (if aApplyTransform is false) or its
9225 * post-transform coordinate space (if aApplyTransform is true).
9227 static nsRect UnionBorderBoxes(
9228 nsIFrame* aFrame, bool aApplyTransform, bool& aOutValid,
9229 const nsSize* aSizeOverride = nullptr,
9230 const OverflowAreas* aOverflowOverride = nullptr) {
9231 const nsRect bounds(nsPoint(0, 0),
9232 aSizeOverride ? *aSizeOverride : aFrame->GetSize());
9234 // The SVG container frames besides SVGTextFrame do not maintain
9235 // an accurate mRect. It will make the outline be larger than
9236 // we expect, we need to make them narrow to their children's outline.
9237 // aOutValid is set to false if the returned nsRect is not valid
9238 // and should not be included in the outline rectangle.
9239 aOutValid = !aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT) ||
9240 !aFrame->IsFrameOfType(nsIFrame::eSVGContainer) ||
9241 aFrame->IsSVGTextFrame();
9243 nsRect u;
9245 if (!aFrame->FrameMaintainsOverflow()) {
9246 return u;
9249 // Start from our border-box, transformed. See comment below about
9250 // transform of children.
9251 bool doTransform = aApplyTransform && aFrame->IsTransformed();
9252 TransformReferenceBox boundsRefBox(nullptr, bounds);
9253 if (doTransform) {
9254 u = nsDisplayTransform::TransformRect(bounds, aFrame, boundsRefBox);
9255 } else {
9256 u = bounds;
9259 // Only iterate through the children if the overflow areas suggest
9260 // that we might need to, and if the frame doesn't clip its overflow
9261 // anyway.
9262 if (aOverflowOverride) {
9263 if (!doTransform && bounds.IsEqualEdges(aOverflowOverride->InkOverflow()) &&
9264 bounds.IsEqualEdges(aOverflowOverride->ScrollableOverflow())) {
9265 return u;
9267 } else {
9268 if (!doTransform && bounds.IsEqualEdges(aFrame->InkOverflowRect()) &&
9269 bounds.IsEqualEdges(aFrame->ScrollableOverflowRect())) {
9270 return u;
9273 const nsStyleDisplay* disp = aFrame->StyleDisplay();
9274 LayoutFrameType fType = aFrame->Type();
9275 auto overflowClipAxes = aFrame->ShouldApplyOverflowClipping(disp);
9276 if (overflowClipAxes == nsIFrame::PhysicalAxes::Both ||
9277 fType == LayoutFrameType::Scroll ||
9278 fType == LayoutFrameType::ListControl ||
9279 fType == LayoutFrameType::SVGOuterSVG) {
9280 return u;
9283 const nsStyleEffects* effects = aFrame->StyleEffects();
9284 Maybe<nsRect> clipPropClipRect =
9285 aFrame->GetClipPropClipRect(disp, effects, bounds.Size());
9287 // Iterate over all children except pop-up, absolutely-positioned,
9288 // float, and overflow ones.
9289 const nsIFrame::ChildListIDs skip = {
9290 nsIFrame::kPopupList, nsIFrame::kSelectPopupList,
9291 nsIFrame::kAbsoluteList, nsIFrame::kFixedList,
9292 nsIFrame::kFloatList, nsIFrame::kOverflowList};
9293 for (const auto& [list, listID] : aFrame->ChildLists()) {
9294 if (skip.contains(listID)) {
9295 continue;
9298 for (nsIFrame* child : list) {
9299 if (child->IsPlaceholderFrame()) {
9300 continue;
9303 // Note that passing |true| for aApplyTransform when
9304 // child->Combines3DTransformWithAncestors() is incorrect if our
9305 // aApplyTransform is false... but the opposite would be as
9306 // well. This is because elements within a preserve-3d scene
9307 // are always transformed up to the top of the scene. This
9308 // means we don't have a mechanism for getting a transform up to
9309 // an intermediate point within the scene. We choose to
9310 // over-transform rather than under-transform because this is
9311 // consistent with other overflow areas.
9312 bool validRect = true;
9313 nsRect childRect =
9314 UnionBorderBoxes(child, true, validRect) + child->GetPosition();
9316 if (!validRect) {
9317 continue;
9320 if (clipPropClipRect) {
9321 // Intersect with the clip before transforming.
9322 childRect.IntersectRect(childRect, *clipPropClipRect);
9325 // Note that we transform each child separately according to
9326 // aFrame's transform, and then union, which gives a different
9327 // (smaller) result from unioning and then transforming the
9328 // union. This doesn't match the way we handle overflow areas
9329 // with 2-D transforms, though it does match the way we handle
9330 // overflow areas in preserve-3d 3-D scenes.
9331 if (doTransform && !child->Combines3DTransformWithAncestors()) {
9332 childRect =
9333 nsDisplayTransform::TransformRect(childRect, aFrame, boundsRefBox);
9336 // If a SVGContainer has a non-SVGContainer child, we assign
9337 // its child's outline to this SVGContainer directly.
9338 if (!aOutValid && validRect) {
9339 u = childRect;
9340 aOutValid = true;
9341 } else {
9342 u.UnionRectEdges(u, childRect);
9347 if (overflowClipAxes & nsIFrame::PhysicalAxes::Vertical) {
9348 u.y = bounds.y;
9349 u.height = bounds.height;
9351 if (overflowClipAxes & nsIFrame::PhysicalAxes::Horizontal) {
9352 u.x = bounds.x;
9353 u.width = bounds.width;
9356 return u;
9359 static void ComputeAndIncludeOutlineArea(nsIFrame* aFrame,
9360 OverflowAreas& aOverflowAreas,
9361 const nsSize& aNewSize) {
9362 const nsStyleOutline* outline = aFrame->StyleOutline();
9363 if (!outline->ShouldPaintOutline()) {
9364 return;
9367 // When the outline property is set on a :-moz-block-inside-inline-wrapper
9368 // pseudo-element, it inherited that outline from the inline that was broken
9369 // because it contained a block. In that case, we don't want a really wide
9370 // outline if the block inside the inline is narrow, so union the actual
9371 // contents of the anonymous blocks.
9372 nsIFrame* frameForArea = aFrame;
9373 do {
9374 PseudoStyleType pseudoType = frameForArea->Style()->GetPseudoType();
9375 if (pseudoType != PseudoStyleType::mozBlockInsideInlineWrapper) break;
9376 // If we're done, we really want it and all its later siblings.
9377 frameForArea = frameForArea->PrincipalChildList().FirstChild();
9378 NS_ASSERTION(frameForArea, "anonymous block with no children?");
9379 } while (frameForArea);
9381 // Find the union of the border boxes of all descendants, or in
9382 // the block-in-inline case, all descendants we care about.
9384 // Note that the interesting perspective-related cases are taken
9385 // care of by the code that handles those issues for overflow
9386 // calling FinishAndStoreOverflow again, which in turn calls this
9387 // function again. We still need to deal with preserve-3d a bit.
9388 nsRect innerRect;
9389 bool validRect;
9390 if (frameForArea == aFrame) {
9391 innerRect =
9392 UnionBorderBoxes(aFrame, false, validRect, &aNewSize, &aOverflowAreas);
9393 } else {
9394 for (; frameForArea; frameForArea = frameForArea->GetNextSibling()) {
9395 nsRect r(UnionBorderBoxes(frameForArea, true, validRect));
9397 // Adjust for offsets transforms up to aFrame's pre-transform
9398 // (i.e., normal) coordinate space; see comments in
9399 // UnionBorderBoxes for some of the subtlety here.
9400 for (nsIFrame *f = frameForArea, *parent = f->GetParent();
9401 /* see middle of loop */; f = parent, parent = f->GetParent()) {
9402 r += f->GetPosition();
9403 if (parent == aFrame) {
9404 break;
9406 if (parent->IsTransformed() && !f->Combines3DTransformWithAncestors()) {
9407 TransformReferenceBox refBox(parent);
9408 r = nsDisplayTransform::TransformRect(r, parent, refBox);
9412 innerRect.UnionRect(innerRect, r);
9416 // Keep this code in sync with GetOutlineInnerRect in nsCSSRendering.cpp.
9417 SetOrUpdateRectValuedProperty(aFrame, nsIFrame::OutlineInnerRectProperty(),
9418 innerRect);
9419 const nscoord offset = outline->mOutlineOffset.ToAppUnits();
9420 nsRect outerRect(innerRect);
9421 bool useOutlineAuto = false;
9422 if (StaticPrefs::layout_css_outline_style_auto_enabled()) {
9423 useOutlineAuto = outline->mOutlineStyle.IsAuto();
9424 if (MOZ_UNLIKELY(useOutlineAuto)) {
9425 nsPresContext* presContext = aFrame->PresContext();
9426 nsITheme* theme = presContext->Theme();
9427 if (theme->ThemeSupportsWidget(presContext, aFrame,
9428 StyleAppearance::FocusOutline)) {
9429 outerRect.Inflate(offset);
9430 theme->GetWidgetOverflow(presContext->DeviceContext(), aFrame,
9431 StyleAppearance::FocusOutline, &outerRect);
9432 } else {
9433 useOutlineAuto = false;
9437 if (MOZ_LIKELY(!useOutlineAuto)) {
9438 nscoord width = outline->GetOutlineWidth();
9439 outerRect.Inflate(width + offset);
9442 nsRect& vo = aOverflowAreas.InkOverflow();
9443 vo.UnionRectEdges(vo, innerRect.Union(outerRect));
9446 bool nsIFrame::FinishAndStoreOverflow(OverflowAreas& aOverflowAreas,
9447 nsSize aNewSize, nsSize* aOldSize,
9448 const nsStyleDisplay* aStyleDisplay) {
9449 MOZ_ASSERT(FrameMaintainsOverflow(),
9450 "Don't call - overflow rects not maintained on these SVG frames");
9452 const nsStyleDisplay* disp = StyleDisplayWithOptionalParam(aStyleDisplay);
9453 bool hasTransform = IsTransformed(disp);
9455 nsRect bounds(nsPoint(0, 0), aNewSize);
9456 // Store the passed in overflow area if we are a preserve-3d frame or we have
9457 // a transform, and it's not just the frame bounds.
9458 if (hasTransform || Combines3DTransformWithAncestors(disp)) {
9459 if (!aOverflowAreas.InkOverflow().IsEqualEdges(bounds) ||
9460 !aOverflowAreas.ScrollableOverflow().IsEqualEdges(bounds)) {
9461 OverflowAreas* initial = GetProperty(nsIFrame::InitialOverflowProperty());
9462 if (!initial) {
9463 AddProperty(nsIFrame::InitialOverflowProperty(),
9464 new OverflowAreas(aOverflowAreas));
9465 } else if (initial != &aOverflowAreas) {
9466 *initial = aOverflowAreas;
9468 } else {
9469 RemoveProperty(nsIFrame::InitialOverflowProperty());
9471 #ifdef DEBUG
9472 SetProperty(nsIFrame::DebugInitialOverflowPropertyApplied(), true);
9473 #endif
9474 } else {
9475 #ifdef DEBUG
9476 RemoveProperty(nsIFrame::DebugInitialOverflowPropertyApplied());
9477 #endif
9480 nsSize oldSize = mRect.Size();
9481 bool sizeChanged = ((aOldSize ? *aOldSize : oldSize) != aNewSize);
9483 // Our frame size may not have been computed and set yet, but code under
9484 // functions such as ComputeEffectsRect (which we're about to call) use the
9485 // values that are stored in our frame rect to compute their results. We
9486 // need the results from those functions to be based on the frame size that
9487 // we *will* have, so we temporarily set our frame size here before calling
9488 // those functions.
9490 // XXX Someone should document here why we revert the frame size before we
9491 // return rather than just leaving it set.
9493 // We pass false here to avoid invalidating display items for this temporary
9494 // change. We sometimes reflow frames multiple times, with the final size
9495 // being the same as the initial. The single call to SetSize after reflow is
9496 // done will take care of invalidating display items if the size has actually
9497 // changed.
9498 SetSize(aNewSize, false);
9500 const auto overflowClipAxes = ShouldApplyOverflowClipping(disp);
9502 if (ChildrenHavePerspective(disp) && sizeChanged) {
9503 RecomputePerspectiveChildrenOverflow(this);
9505 if (overflowClipAxes != PhysicalAxes::Both) {
9506 aOverflowAreas.SetAllTo(bounds);
9507 DebugOnly<bool> ok = ComputeCustomOverflow(aOverflowAreas);
9509 // ComputeCustomOverflow() should not return false, when
9510 // FrameMaintainsOverflow() returns true.
9511 MOZ_ASSERT(ok, "FrameMaintainsOverflow() != ComputeCustomOverflow()");
9513 UnionChildOverflow(aOverflowAreas);
9517 // This is now called FinishAndStoreOverflow() instead of
9518 // StoreOverflow() because frame-generic ways of adding overflow
9519 // can happen here, e.g. CSS2 outline and native theme.
9520 // If the overflow area width or height is nscoord_MAX, then a
9521 // saturating union may have encounted an overflow, so the overflow may not
9522 // contain the frame border-box. Don't warn in that case.
9523 // Don't warn for SVG either, since SVG doesn't need the overflow area
9524 // to contain the frame bounds.
9525 for (const auto otype : AllOverflowTypes()) {
9526 DebugOnly<nsRect*> r = &aOverflowAreas.Overflow(otype);
9527 NS_ASSERTION(aNewSize.width == 0 || aNewSize.height == 0 ||
9528 r->width == nscoord_MAX || r->height == nscoord_MAX ||
9529 (mState & NS_FRAME_SVG_LAYOUT) ||
9530 r->Contains(nsRect(nsPoint(0, 0), aNewSize)),
9531 "Computed overflow area must contain frame bounds");
9534 // If we clip our children, clear accumulated overflow area in the affected
9535 // dimension(s). The children are actually clipped to the padding-box, but
9536 // since the overflow area should include the entire border-box, just set it
9537 // to the border-box size here.
9538 if (overflowClipAxes != PhysicalAxes::None) {
9539 nsRect& ink = aOverflowAreas.InkOverflow();
9540 nsRect& scrollable = aOverflowAreas.ScrollableOverflow();
9541 if (overflowClipAxes & PhysicalAxes::Vertical) {
9542 ink.y = bounds.y;
9543 scrollable.y = bounds.y;
9544 ink.height = bounds.height;
9545 scrollable.height = bounds.height;
9547 if (overflowClipAxes & PhysicalAxes::Horizontal) {
9548 ink.x = bounds.x;
9549 scrollable.x = bounds.x;
9550 ink.width = bounds.width;
9551 scrollable.width = bounds.width;
9555 // Overflow area must always include the frame's top-left and bottom-right,
9556 // even if the frame rect is empty (so we can scroll to those positions).
9557 // Pending a real fix for bug 426879, don't do this for inline frames
9558 // with zero width.
9559 // Do not do this for SVG either, since it will usually massively increase
9560 // the area unnecessarily.
9561 if ((aNewSize.width != 0 || !IsInlineFrame()) &&
9562 !HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
9563 for (const auto otype : AllOverflowTypes()) {
9564 nsRect& o = aOverflowAreas.Overflow(otype);
9565 o.UnionRectEdges(o, bounds);
9569 // Note that StyleOverflow::Clip doesn't clip the frame
9570 // background, so we add theme background overflow here so it's not clipped.
9571 if (!::IsXULBoxWrapped(this) && IsThemed(disp)) {
9572 nsRect r(bounds);
9573 nsPresContext* presContext = PresContext();
9574 if (presContext->Theme()->GetWidgetOverflow(
9575 presContext->DeviceContext(), this, disp->EffectiveAppearance(),
9576 &r)) {
9577 nsRect& vo = aOverflowAreas.InkOverflow();
9578 vo.UnionRectEdges(vo, r);
9582 ComputeAndIncludeOutlineArea(this, aOverflowAreas, aNewSize);
9584 // Nothing in here should affect scrollable overflow.
9585 aOverflowAreas.InkOverflow() =
9586 ComputeEffectsRect(this, aOverflowAreas.InkOverflow(), aNewSize);
9588 // Absolute position clipping
9589 const nsStyleEffects* effects = StyleEffects();
9590 Maybe<nsRect> clipPropClipRect = GetClipPropClipRect(disp, effects, aNewSize);
9591 if (clipPropClipRect) {
9592 for (const auto otype : AllOverflowTypes()) {
9593 nsRect& o = aOverflowAreas.Overflow(otype);
9594 o.IntersectRect(o, *clipPropClipRect);
9598 /* If we're transformed, transform the overflow rect by the current
9599 * transformation. */
9600 if (hasTransform) {
9601 SetProperty(nsIFrame::PreTransformOverflowAreasProperty(),
9602 new OverflowAreas(aOverflowAreas));
9604 if (Combines3DTransformWithAncestors(disp)) {
9605 /* If we're a preserve-3d leaf frame, then our pre-transform overflow
9606 * should be correct. Our post-transform overflow is empty though, because
9607 * we only contribute to the overflow area of the preserve-3d root frame.
9608 * If we're an intermediate frame then the pre-transform overflow should
9609 * contain all our non-preserve-3d children, which is what we want. Again
9610 * we have no post-transform overflow.
9612 aOverflowAreas.SetAllTo(nsRect());
9613 } else {
9614 TransformReferenceBox refBox(this);
9615 for (const auto otype : AllOverflowTypes()) {
9616 nsRect& o = aOverflowAreas.Overflow(otype);
9617 o = nsDisplayTransform::TransformRect(o, this, refBox);
9620 /* If we're the root of the 3d context, then we want to include the
9621 * overflow areas of all the participants. This won't have happened yet as
9622 * the code above set their overflow area to empty. Manually collect these
9623 * overflow areas now.
9625 if (Extend3DContext(disp, effects)) {
9626 ComputePreserve3DChildrenOverflow(aOverflowAreas);
9629 } else {
9630 RemoveProperty(nsIFrame::PreTransformOverflowAreasProperty());
9633 /* Revert the size change in case some caller is depending on this. */
9634 SetSize(oldSize, false);
9636 bool anyOverflowChanged;
9637 if (aOverflowAreas != OverflowAreas(bounds, bounds)) {
9638 anyOverflowChanged = SetOverflowAreas(aOverflowAreas);
9639 } else {
9640 anyOverflowChanged = ClearOverflowRects();
9643 if (anyOverflowChanged) {
9644 SVGObserverUtils::InvalidateDirectRenderingObservers(this);
9645 if (IsBlockFrameOrSubclass() &&
9646 TextOverflow::CanHaveOverflowMarkers(this)) {
9647 DiscardDisplayItems(this, [](nsDisplayItemBase* aItem) {
9648 return aItem->GetType() == DisplayItemType::TYPE_TEXT_OVERFLOW;
9650 SchedulePaint(PAINT_DEFAULT);
9653 return anyOverflowChanged;
9656 void nsIFrame::RecomputePerspectiveChildrenOverflow(
9657 const nsIFrame* aStartFrame) {
9658 for (const auto& childList : ChildLists()) {
9659 for (nsIFrame* child : childList.mList) {
9660 if (!child->FrameMaintainsOverflow()) {
9661 continue; // frame does not maintain overflow rects
9663 if (child->HasPerspective()) {
9664 OverflowAreas* overflow =
9665 child->GetProperty(nsIFrame::InitialOverflowProperty());
9666 nsRect bounds(nsPoint(0, 0), child->GetSize());
9667 if (overflow) {
9668 OverflowAreas overflowCopy = *overflow;
9669 child->FinishAndStoreOverflow(overflowCopy, bounds.Size());
9670 } else {
9671 OverflowAreas boundsOverflow;
9672 boundsOverflow.SetAllTo(bounds);
9673 child->FinishAndStoreOverflow(boundsOverflow, bounds.Size());
9675 } else if (child->GetContainingBlock(SKIP_SCROLLED_FRAME) ==
9676 aStartFrame) {
9677 // If a frame is using perspective, then the size used to compute
9678 // perspective-origin is the size of the frame belonging to its parent
9679 // style. We must find any descendant frames using our size
9680 // (by recursing into frames that have the same containing block)
9681 // to update their overflow rects too.
9682 child->RecomputePerspectiveChildrenOverflow(aStartFrame);
9688 void nsIFrame::ComputePreserve3DChildrenOverflow(
9689 OverflowAreas& aOverflowAreas) {
9690 // Find all descendants that participate in the 3d context, and include their
9691 // overflow. These descendants have an empty overflow, so won't have been
9692 // included in the normal overflow calculation. Any children that don't
9693 // participate have normal overflow, so will have been included already.
9695 nsRect childVisual;
9696 nsRect childScrollable;
9697 for (const auto& childList : ChildLists()) {
9698 for (nsIFrame* child : childList.mList) {
9699 // If this child participates in the 3d context, then take the
9700 // pre-transform region (which contains all descendants that aren't
9701 // participating in the 3d context) and transform it into the 3d context
9702 // root coordinate space.
9703 const nsStyleDisplay* childDisp = child->StyleDisplay();
9704 if (child->Combines3DTransformWithAncestors(childDisp)) {
9705 OverflowAreas childOverflow = child->GetOverflowAreasRelativeToSelf();
9706 TransformReferenceBox refBox(child);
9707 for (const auto otype : AllOverflowTypes()) {
9708 nsRect& o = childOverflow.Overflow(otype);
9709 o = nsDisplayTransform::TransformRect(o, child, refBox);
9712 aOverflowAreas.UnionWith(childOverflow);
9714 // If this child also extends the 3d context, then recurse into it
9715 // looking for more participants.
9716 if (child->Extend3DContext(childDisp, child->StyleEffects())) {
9717 child->ComputePreserve3DChildrenOverflow(aOverflowAreas);
9724 Maybe<int32_t> nsIFrame::ZIndex() const {
9725 if (!StyleDisplay()->IsPositionedStyle() && !IsFlexOrGridItem()) {
9726 return Nothing(); // z-index doesn't apply.
9728 const auto& zIndex = StylePosition()->mZIndex;
9729 if (zIndex.IsAuto()) {
9730 return Nothing();
9732 return Some(zIndex.AsInteger());
9735 bool nsIFrame::IsScrollAnchor(ScrollAnchorContainer** aOutContainer) {
9736 if (!mInScrollAnchorChain) {
9737 return false;
9740 nsIFrame* f = this;
9742 // FIXME(emilio, bug 1629280): We should find a non-null anchor if we have the
9743 // flag set, but bug 1629280 makes it so that we cannot really assert it /
9744 // make this just a `while (true)`, and uncomment the below assertion.
9745 while (auto* container = ScrollAnchorContainer::FindFor(f)) {
9746 // MOZ_ASSERT(f->IsInScrollAnchorChain());
9747 if (nsIFrame* anchor = container->AnchorNode()) {
9748 if (anchor != this) {
9749 return false;
9751 if (aOutContainer) {
9752 *aOutContainer = container;
9754 return true;
9757 f = container->Frame();
9760 return false;
9763 bool nsIFrame::IsInScrollAnchorChain() const { return mInScrollAnchorChain; }
9765 void nsIFrame::SetInScrollAnchorChain(bool aInChain) {
9766 mInScrollAnchorChain = aInChain;
9769 uint32_t nsIFrame::GetDepthInFrameTree() const {
9770 uint32_t result = 0;
9771 for (nsContainerFrame* ancestor = GetParent(); ancestor;
9772 ancestor = ancestor->GetParent()) {
9773 result++;
9775 return result;
9779 * This function takes a frame that is part of a block-in-inline split,
9780 * and _if_ that frame is an anonymous block created by an ib split it
9781 * returns the block's preceding inline. This is needed because the
9782 * split inline's style is the parent of the anonymous block's style.
9784 * If aFrame is not an anonymous block, null is returned.
9786 static nsIFrame* GetIBSplitSiblingForAnonymousBlock(const nsIFrame* aFrame) {
9787 MOZ_ASSERT(aFrame, "Must have a non-null frame!");
9788 NS_ASSERTION(aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT),
9789 "GetIBSplitSibling should only be called on ib-split frames");
9791 if (aFrame->Style()->GetPseudoType() !=
9792 PseudoStyleType::mozBlockInsideInlineWrapper) {
9793 // it's not an anonymous block
9794 return nullptr;
9797 // Find the first continuation of the frame. (Ugh. This ends up
9798 // being O(N^2) when it is called O(N) times.)
9799 aFrame = aFrame->FirstContinuation();
9802 * Now look up the nsGkAtoms::IBSplitPrevSibling
9803 * property.
9805 nsIFrame* ibSplitSibling =
9806 aFrame->GetProperty(nsIFrame::IBSplitPrevSibling());
9807 NS_ASSERTION(ibSplitSibling, "Broken frame tree?");
9808 return ibSplitSibling;
9812 * Get the parent, corrected for the mangled frame tree resulting from
9813 * having a block within an inline. The result only differs from the
9814 * result of |GetParent| when |GetParent| returns an anonymous block
9815 * that was created for an element that was 'display: inline' because
9816 * that element contained a block.
9818 * Also skip anonymous scrolled-content parents; inherit directly from the
9819 * outer scroll frame.
9821 * Also skip NAC parents if the child frame is NAC.
9823 static nsIFrame* GetCorrectedParent(const nsIFrame* aFrame) {
9824 nsIFrame* parent = aFrame->GetParent();
9825 if (!parent) {
9826 return nullptr;
9829 // For a table caption we want the _inner_ table frame (unless it's anonymous)
9830 // as the style parent.
9831 if (aFrame->IsTableCaption()) {
9832 nsIFrame* innerTable = parent->PrincipalChildList().FirstChild();
9833 if (!innerTable->Style()->IsAnonBox()) {
9834 return innerTable;
9838 // Table wrappers are always anon boxes; if we're in here for an outer
9839 // table, that actually means its the _inner_ table that wants to
9840 // know its parent. So get the pseudo of the inner in that case.
9841 auto pseudo = aFrame->Style()->GetPseudoType();
9842 if (pseudo == PseudoStyleType::tableWrapper) {
9843 pseudo =
9844 aFrame->PrincipalChildList().FirstChild()->Style()->GetPseudoType();
9847 // Prevent a NAC pseudo-element from inheriting from its NAC parent, and
9848 // inherit from the NAC generator element instead.
9849 if (pseudo != PseudoStyleType::NotPseudo) {
9850 MOZ_ASSERT(aFrame->GetContent());
9851 Element* element = Element::FromNode(aFrame->GetContent());
9852 // Make sure to avoid doing the fixup for non-element-backed pseudos like
9853 // ::first-line and such.
9854 if (element && !element->IsRootOfNativeAnonymousSubtree() &&
9855 element->GetPseudoElementType() == aFrame->Style()->GetPseudoType()) {
9856 while (parent->GetContent() &&
9857 !parent->GetContent()->IsRootOfNativeAnonymousSubtree()) {
9858 parent = parent->GetInFlowParent();
9860 parent = parent->GetInFlowParent();
9864 return nsIFrame::CorrectStyleParentFrame(parent, pseudo);
9867 /* static */
9868 nsIFrame* nsIFrame::CorrectStyleParentFrame(nsIFrame* aProspectiveParent,
9869 PseudoStyleType aChildPseudo) {
9870 MOZ_ASSERT(aProspectiveParent, "Must have a prospective parent");
9872 if (aChildPseudo != PseudoStyleType::NotPseudo) {
9873 // Non-inheriting anon boxes have no style parent frame at all.
9874 if (PseudoStyle::IsNonInheritingAnonBox(aChildPseudo)) {
9875 return nullptr;
9878 // Other anon boxes are parented to their actual parent already, except
9879 // for non-elements. Those should not be treated as an anon box.
9880 if (PseudoStyle::IsAnonBox(aChildPseudo) &&
9881 !nsCSSAnonBoxes::IsNonElement(aChildPseudo)) {
9882 NS_ASSERTION(aChildPseudo != PseudoStyleType::mozBlockInsideInlineWrapper,
9883 "Should have dealt with kids that have "
9884 "NS_FRAME_PART_OF_IBSPLIT elsewhere");
9885 return aProspectiveParent;
9889 // Otherwise, walk up out of all anon boxes. For placeholder frames, walk out
9890 // of all pseudo-elements as well. Otherwise ReparentComputedStyle could
9891 // cause style data to be out of sync with the frame tree.
9892 nsIFrame* parent = aProspectiveParent;
9893 do {
9894 if (parent->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) {
9895 nsIFrame* sibling = GetIBSplitSiblingForAnonymousBlock(parent);
9897 if (sibling) {
9898 // |parent| was a block in an {ib} split; use the inline as
9899 // |the style parent.
9900 parent = sibling;
9904 if (!parent->Style()->IsPseudoOrAnonBox()) {
9905 return parent;
9908 if (!parent->Style()->IsAnonBox() && aChildPseudo != PseudoStyleType::MAX) {
9909 // nsPlaceholderFrame passes in PseudoStyleType::MAX for
9910 // aChildPseudo (even though that's not a valid pseudo-type) just to
9911 // trigger this behavior of walking up to the nearest non-pseudo
9912 // ancestor.
9913 return parent;
9916 parent = parent->GetInFlowParent();
9917 } while (parent);
9919 if (aProspectiveParent->Style()->GetPseudoType() ==
9920 PseudoStyleType::viewportScroll) {
9921 // aProspectiveParent is the scrollframe for a viewport
9922 // and the kids are the anonymous scrollbars
9923 return aProspectiveParent;
9926 // We can get here if the root element is absolutely positioned.
9927 // We can't test for this very accurately, but it can only happen
9928 // when the prospective parent is a canvas frame.
9929 NS_ASSERTION(aProspectiveParent->IsCanvasFrame(),
9930 "Should have found a parent before this");
9931 return nullptr;
9934 ComputedStyle* nsIFrame::DoGetParentComputedStyle(
9935 nsIFrame** aProviderFrame) const {
9936 *aProviderFrame = nullptr;
9938 // Handle display:contents and the root frame, when there's no parent frame
9939 // to inherit from.
9940 if (MOZ_LIKELY(mContent)) {
9941 Element* parentElement = mContent->GetFlattenedTreeParentElement();
9942 if (MOZ_LIKELY(parentElement)) {
9943 auto pseudo = Style()->GetPseudoType();
9944 if (pseudo == PseudoStyleType::NotPseudo || !mContent->IsElement() ||
9945 (!PseudoStyle::IsAnonBox(pseudo) &&
9946 // Ensure that we don't return the display:contents style
9947 // of the parent content for pseudos that have the same content
9948 // as their primary frame (like -moz-list-bullets do):
9949 IsPrimaryFrame()) ||
9950 /* if next is true then it's really a request for the table frame's
9951 parent context, see nsTable[Outer]Frame::GetParentComputedStyle. */
9952 pseudo == PseudoStyleType::tableWrapper) {
9953 // In some edge cases involving display: contents, we may end up here
9954 // for something that's pending to be reframed. In this case we return
9955 // the wrong style from here (because we've already lost track of it!),
9956 // but it's not a big deal as we're going to be reframed anyway.
9957 if (MOZ_LIKELY(parentElement->HasServoData()) &&
9958 Servo_Element_IsDisplayContents(parentElement)) {
9959 RefPtr<ComputedStyle> style =
9960 ServoStyleSet::ResolveServoStyle(*parentElement);
9961 // NOTE(emilio): we return a weak reference because the element also
9962 // holds the style context alive. This is a bit silly (we could've
9963 // returned a weak ref directly), but it's probably not worth
9964 // optimizing, given this function has just one caller which is rare,
9965 // and this path is rare itself.
9966 return style;
9969 } else {
9970 if (Style()->GetPseudoType() == PseudoStyleType::NotPseudo) {
9971 // We're a frame for the root. We have no style parent.
9972 return nullptr;
9977 if (!(mState & NS_FRAME_OUT_OF_FLOW)) {
9979 * If this frame is an anonymous block created when an inline with a block
9980 * inside it got split, then the parent style is on its preceding inline. We
9981 * can get to it using GetIBSplitSiblingForAnonymousBlock.
9983 if (mState & NS_FRAME_PART_OF_IBSPLIT) {
9984 nsIFrame* ibSplitSibling = GetIBSplitSiblingForAnonymousBlock(this);
9985 if (ibSplitSibling) {
9986 return (*aProviderFrame = ibSplitSibling)->Style();
9990 // If this frame is one of the blocks that split an inline, we must
9991 // return the "special" inline parent, i.e., the parent that this
9992 // frame would have if we didn't mangle the frame structure.
9993 *aProviderFrame = GetCorrectedParent(this);
9994 return *aProviderFrame ? (*aProviderFrame)->Style() : nullptr;
9997 // We're an out-of-flow frame. For out-of-flow frames, we must
9998 // resolve underneath the placeholder's parent. The placeholder is
9999 // reached from the first-in-flow.
10000 nsPlaceholderFrame* placeholder = FirstInFlow()->GetPlaceholderFrame();
10001 if (!placeholder) {
10002 MOZ_ASSERT_UNREACHABLE("no placeholder frame for out-of-flow frame");
10003 *aProviderFrame = GetCorrectedParent(this);
10004 return *aProviderFrame ? (*aProviderFrame)->Style() : nullptr;
10006 return placeholder->GetParentComputedStyleForOutOfFlow(aProviderFrame);
10009 void nsIFrame::GetLastLeaf(nsIFrame** aFrame) {
10010 if (!aFrame || !*aFrame) return;
10011 nsIFrame* child = *aFrame;
10012 // if we are a block frame then go for the last line of 'this'
10013 while (1) {
10014 child = child->PrincipalChildList().FirstChild();
10015 if (!child) return; // nothing to do
10016 nsIFrame* siblingFrame;
10017 nsIContent* content;
10018 // ignore anonymous elements, e.g. mozTableAdd* mozTableRemove*
10019 // see bug 278197 comment #12 #13 for details
10020 while ((siblingFrame = child->GetNextSibling()) &&
10021 (content = siblingFrame->GetContent()) &&
10022 !content->IsRootOfNativeAnonymousSubtree())
10023 child = siblingFrame;
10024 *aFrame = child;
10028 void nsIFrame::GetFirstLeaf(nsIFrame** aFrame) {
10029 if (!aFrame || !*aFrame) return;
10030 nsIFrame* child = *aFrame;
10031 while (1) {
10032 child = child->PrincipalChildList().FirstChild();
10033 if (!child) return; // nothing to do
10034 *aFrame = child;
10038 bool nsIFrame::IsFocusableDueToScrollFrame() {
10039 if (!IsScrollFrame()) {
10040 if (nsFieldSetFrame* fieldset = do_QueryFrame(this)) {
10041 // TODO: Do we have similar special-cases like this where we can have
10042 // anonymous scrollable boxes hanging off a primary frame?
10043 if (nsIFrame* inner = fieldset->GetInner()) {
10044 return inner->IsFocusableDueToScrollFrame();
10047 return false;
10049 if (!mContent->IsHTMLElement()) {
10050 return false;
10052 if (mContent->IsRootOfNativeAnonymousSubtree()) {
10053 return false;
10055 if (!mContent->GetParent()) {
10056 return false;
10058 if (mContent->AsElement()->HasAttr(nsGkAtoms::tabindex)) {
10059 return false;
10061 // Elements with scrollable view are focusable with script & tabbable
10062 // Otherwise you couldn't scroll them with keyboard, which is an accessibility
10063 // issue (e.g. Section 508 rules) However, we don't make them to be focusable
10064 // with the mouse, because the extra focus outlines are considered
10065 // unnecessarily ugly. When clicked on, the selection position within the
10066 // element will be enough to make them keyboard scrollable.
10067 nsIScrollableFrame* scrollFrame = do_QueryFrame(this);
10068 if (!scrollFrame) {
10069 return false;
10071 if (scrollFrame->IsForTextControlWithNoScrollbars()) {
10072 return false;
10074 if (scrollFrame->GetScrollStyles().IsHiddenInBothDirections()) {
10075 return false;
10077 if (scrollFrame->GetScrollRange().IsEqualEdges(nsRect(0, 0, 0, 0))) {
10078 return false;
10080 return true;
10083 nsIFrame::Focusable nsIFrame::IsFocusable(bool aWithMouse) {
10084 // cannot focus content in print preview mode. Only the root can be focused,
10085 // but that's handled elsewhere.
10086 if (PresContext()->Type() == nsPresContext::eContext_PrintPreview) {
10087 return {};
10090 if (!mContent || !mContent->IsElement()) {
10091 return {};
10094 if (!IsVisibleConsideringAncestors()) {
10095 return {};
10098 const nsStyleUI* ui = StyleUI();
10099 if (ui->mInert == StyleInert::Inert) {
10100 return {};
10103 PseudoStyleType pseudo = Style()->GetPseudoType();
10104 if (pseudo == PseudoStyleType::anonymousFlexItem ||
10105 pseudo == PseudoStyleType::anonymousGridItem) {
10106 return {};
10109 int32_t tabIndex = -1;
10110 if (ui->mUserFocus != StyleUserFocus::Ignore &&
10111 ui->mUserFocus != StyleUserFocus::None) {
10112 // Pass in default tabindex of -1 for nonfocusable and 0 for focusable
10113 tabIndex = 0;
10116 if (mContent->IsFocusable(&tabIndex, aWithMouse)) {
10117 // If the content is focusable, then we're done.
10118 return {true, tabIndex};
10121 // If we're focusing with the mouse we never focus scroll areas.
10122 if (!aWithMouse && IsFocusableDueToScrollFrame()) {
10123 return {true, 0};
10126 return {false, tabIndex};
10130 * @return true if this text frame ends with a newline character which is
10131 * treated as preformatted. It should return false if this is not a text frame.
10133 bool nsIFrame::HasSignificantTerminalNewline() const { return false; }
10135 static StyleVerticalAlignKeyword ConvertSVGDominantBaselineToVerticalAlign(
10136 StyleDominantBaseline aDominantBaseline) {
10137 // Most of these are approximate mappings.
10138 switch (aDominantBaseline) {
10139 case StyleDominantBaseline::Hanging:
10140 case StyleDominantBaseline::TextBeforeEdge:
10141 return StyleVerticalAlignKeyword::TextTop;
10142 case StyleDominantBaseline::TextAfterEdge:
10143 case StyleDominantBaseline::Ideographic:
10144 return StyleVerticalAlignKeyword::TextBottom;
10145 case StyleDominantBaseline::Central:
10146 case StyleDominantBaseline::Middle:
10147 case StyleDominantBaseline::Mathematical:
10148 return StyleVerticalAlignKeyword::Middle;
10149 case StyleDominantBaseline::Auto:
10150 case StyleDominantBaseline::Alphabetic:
10151 return StyleVerticalAlignKeyword::Baseline;
10152 default:
10153 MOZ_ASSERT_UNREACHABLE("unexpected aDominantBaseline value");
10154 return StyleVerticalAlignKeyword::Baseline;
10158 Maybe<StyleVerticalAlignKeyword> nsIFrame::VerticalAlignEnum() const {
10159 if (SVGUtils::IsInSVGTextSubtree(this)) {
10160 StyleDominantBaseline dominantBaseline = StyleSVG()->mDominantBaseline;
10161 return Some(ConvertSVGDominantBaselineToVerticalAlign(dominantBaseline));
10164 const auto& verticalAlign = StyleDisplay()->mVerticalAlign;
10165 if (verticalAlign.IsKeyword()) {
10166 return Some(verticalAlign.AsKeyword());
10169 return Nothing();
10172 NS_IMETHODIMP
10173 nsIFrame::RefreshSizeCache(nsBoxLayoutState& aState) {
10174 // XXXbz this comment needs some rewriting to make sense in the
10175 // post-reflow-branch world.
10177 // Ok we need to compute our minimum, preferred, and maximum sizes.
10178 // 1) Maximum size. This is easy. Its infinite unless it is overloaded by CSS.
10179 // 2) Preferred size. This is a little harder. This is the size the
10180 // block would be if it were laid out on an infinite canvas. So we can
10181 // get this by reflowing the block with and INTRINSIC width and height. We
10182 // can also do a nice optimization for incremental reflow. If the reflow is
10183 // incremental then we can pass a flag to have the block compute the
10184 // preferred width for us! Preferred height can just be the minimum height;
10185 // 3) Minimum size. This is a toughy. We can pass the block a flag asking for
10186 // the max element size. That would give us the width. Unfortunately you
10187 // can only ask for a maxElementSize during an incremental reflow. So on
10188 // other reflows we will just have to use 0. The min height on the other
10189 // hand is fairly easy we need to get the largest line height. This can be
10190 // done with the line iterator.
10192 // if we do have a rendering context
10193 gfxContext* rendContext = aState.GetRenderingContext();
10194 if (rendContext) {
10195 nsPresContext* presContext = aState.PresContext();
10197 // If we don't have any HTML constraints and it's a resize, then nothing in
10198 // the block could have changed, so no refresh is necessary.
10199 nsBoxLayoutMetrics* metrics = BoxMetrics();
10200 if (!XULNeedsRecalc(metrics->mBlockPrefSize)) {
10201 return NS_OK;
10204 // the rect we plan to size to.
10205 nsRect rect = GetRect();
10207 nsMargin bp(0, 0, 0, 0);
10208 GetXULBorderAndPadding(bp);
10211 // If we're a container for font size inflation, then shrink
10212 // wrapping inside of us should not apply font size inflation.
10213 AutoMaybeDisableFontInflation an(this);
10215 metrics->mBlockPrefSize.width =
10216 GetPrefISize(rendContext) + bp.LeftRight();
10217 metrics->mBlockMinSize.width = GetMinISize(rendContext) + bp.LeftRight();
10220 // do the nasty.
10221 const WritingMode wm = aState.OuterReflowInput()
10222 ? aState.OuterReflowInput()->GetWritingMode()
10223 : GetWritingMode();
10224 ReflowOutput desiredSize(wm);
10225 BoxReflow(aState, presContext, desiredSize, rendContext, rect.x, rect.y,
10226 metrics->mBlockPrefSize.width, NS_UNCONSTRAINEDSIZE);
10228 metrics->mBlockMinSize.height = 0;
10229 // ok we need the max ascent of the items on the line. So to do this
10230 // ask the block for its line iterator. Get the max ascent.
10231 nsAutoLineIterator lines = GetLineIterator();
10232 if (lines) {
10233 metrics->mBlockMinSize.height = 0;
10234 int32_t lineCount = lines->GetNumLines();
10235 for (int32_t i = 0; i < lineCount; ++i) {
10236 auto line = lines->GetLine(i).unwrap();
10238 if (line.mLineBounds.height > metrics->mBlockMinSize.height) {
10239 metrics->mBlockMinSize.height = line.mLineBounds.height;
10242 } else {
10243 metrics->mBlockMinSize.height = desiredSize.Height();
10246 metrics->mBlockPrefSize.height = metrics->mBlockMinSize.height;
10248 if (desiredSize.BlockStartAscent() == ReflowOutput::ASK_FOR_BASELINE) {
10249 if (!nsLayoutUtils::GetFirstLineBaseline(wm, this,
10250 &metrics->mBlockAscent))
10251 metrics->mBlockAscent = GetLogicalBaseline(wm);
10252 } else {
10253 metrics->mBlockAscent = desiredSize.BlockStartAscent();
10256 #ifdef DEBUG_adaptor
10257 printf("min=(%d,%d), pref=(%d,%d), ascent=%d\n",
10258 metrics->mBlockMinSize.width, metrics->mBlockMinSize.height,
10259 metrics->mBlockPrefSize.width, metrics->mBlockPrefSize.height,
10260 metrics->mBlockAscent);
10261 #endif
10264 return NS_OK;
10267 nsSize nsIFrame::GetXULPrefSize(nsBoxLayoutState& aState) {
10268 nsSize size(0, 0);
10269 DISPLAY_PREF_SIZE(this, size);
10270 // If the size is cached, and there are no HTML constraints that we might
10271 // be depending on, then we just return the cached size.
10272 nsBoxLayoutMetrics* metrics = BoxMetrics();
10273 if (!XULNeedsRecalc(metrics->mPrefSize)) {
10274 size = metrics->mPrefSize;
10275 return size;
10278 if (IsXULCollapsed()) return size;
10280 // get our size in CSS.
10281 bool widthSet, heightSet;
10282 bool completelyRedefined =
10283 nsIFrame::AddXULPrefSize(this, size, widthSet, heightSet);
10285 // Refresh our caches with new sizes.
10286 if (!completelyRedefined) {
10287 RefreshSizeCache(aState);
10288 nsSize blockSize = metrics->mBlockPrefSize;
10290 // notice we don't need to add our borders or padding
10291 // in. That's because the block did it for us.
10292 if (!widthSet) size.width = blockSize.width;
10293 if (!heightSet) size.height = blockSize.height;
10296 metrics->mPrefSize = size;
10297 return size;
10300 nsSize nsIFrame::GetXULMinSize(nsBoxLayoutState& aState) {
10301 nsSize size(0, 0);
10302 DISPLAY_MIN_SIZE(this, size);
10303 // Don't use the cache if we have HTMLReflowInput constraints --- they might
10304 // have changed
10305 nsBoxLayoutMetrics* metrics = BoxMetrics();
10306 if (!XULNeedsRecalc(metrics->mMinSize)) {
10307 size = metrics->mMinSize;
10308 return size;
10311 if (IsXULCollapsed()) return size;
10313 // get our size in CSS.
10314 bool widthSet, heightSet;
10315 bool completelyRedefined =
10316 nsIFrame::AddXULMinSize(this, size, widthSet, heightSet);
10318 // Refresh our caches with new sizes.
10319 if (!completelyRedefined) {
10320 RefreshSizeCache(aState);
10321 nsSize blockSize = metrics->mBlockMinSize;
10323 if (!widthSet) size.width = blockSize.width;
10324 if (!heightSet) size.height = blockSize.height;
10327 metrics->mMinSize = size;
10328 return size;
10331 nsSize nsIFrame::GetXULMaxSize(nsBoxLayoutState& aState) {
10332 nsSize size(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
10333 DISPLAY_MAX_SIZE(this, size);
10334 // Don't use the cache if we have HTMLReflowInput constraints --- they might
10335 // have changed
10336 nsBoxLayoutMetrics* metrics = BoxMetrics();
10337 if (!XULNeedsRecalc(metrics->mMaxSize)) {
10338 size = metrics->mMaxSize;
10339 return size;
10342 if (IsXULCollapsed()) return size;
10344 size = nsIFrame::GetUncachedXULMaxSize(aState);
10345 metrics->mMaxSize = size;
10347 return size;
10350 nscoord nsIFrame::GetXULFlex() {
10351 nsBoxLayoutMetrics* metrics = BoxMetrics();
10352 if (XULNeedsRecalc(metrics->mFlex)) {
10353 nsIFrame::AddXULFlex(this, metrics->mFlex);
10356 return metrics->mFlex;
10359 nscoord nsIFrame::GetXULBoxAscent(nsBoxLayoutState& aState) {
10360 nsBoxLayoutMetrics* metrics = BoxMetrics();
10361 if (!XULNeedsRecalc(metrics->mAscent)) {
10362 return metrics->mAscent;
10365 if (IsXULCollapsed()) {
10366 metrics->mAscent = 0;
10367 } else {
10368 // Refresh our caches with new sizes.
10369 RefreshSizeCache(aState);
10370 metrics->mAscent = metrics->mBlockAscent;
10373 return metrics->mAscent;
10376 nsresult nsIFrame::DoXULLayout(nsBoxLayoutState& aState) {
10377 nsRect ourRect(mRect);
10379 gfxContext* rendContext = aState.GetRenderingContext();
10380 nsPresContext* presContext = aState.PresContext();
10381 WritingMode ourWM = GetWritingMode();
10382 const WritingMode outerWM = aState.OuterReflowInput()
10383 ? aState.OuterReflowInput()->GetWritingMode()
10384 : ourWM;
10385 ReflowOutput desiredSize(outerWM);
10386 LogicalSize ourSize = GetLogicalSize(outerWM);
10388 if (rendContext) {
10389 BoxReflow(aState, presContext, desiredSize, rendContext, ourRect.x,
10390 ourRect.y, ourRect.width, ourRect.height);
10392 if (IsXULCollapsed()) {
10393 SetSize(nsSize(0, 0));
10394 } else {
10395 // if our child needs to be bigger. This might happend with
10396 // wrapping text. There is no way to predict its height until we
10397 // reflow it. Now that we know the height reshuffle upward.
10398 if (desiredSize.ISize(outerWM) > ourSize.ISize(outerWM) ||
10399 desiredSize.BSize(outerWM) > ourSize.BSize(outerWM)) {
10400 #ifdef DEBUG_GROW
10401 XULDumpBox(stdout);
10402 printf(" GREW from (%d,%d) -> (%d,%d)\n", ourSize.ISize(outerWM),
10403 ourSize.BSize(outerWM), desiredSize.ISize(outerWM),
10404 desiredSize.BSize(outerWM));
10405 #endif
10407 if (desiredSize.ISize(outerWM) > ourSize.ISize(outerWM)) {
10408 ourSize.ISize(outerWM) = desiredSize.ISize(outerWM);
10411 if (desiredSize.BSize(outerWM) > ourSize.BSize(outerWM)) {
10412 ourSize.BSize(outerWM) = desiredSize.BSize(outerWM);
10416 // ensure our size is what we think is should be. Someone could have
10417 // reset the frame to be smaller or something dumb like that.
10418 SetSize(ourSize.ConvertTo(ourWM, outerWM));
10422 // Should we do this if IsXULCollapsed() is true?
10423 LogicalSize size(GetLogicalSize(outerWM));
10424 desiredSize.ISize(outerWM) = size.ISize(outerWM);
10425 desiredSize.BSize(outerWM) = size.BSize(outerWM);
10426 desiredSize.UnionOverflowAreasWithDesiredBounds();
10428 if (HasAbsolutelyPositionedChildren()) {
10429 // Set up a |reflowInput| to pass into ReflowAbsoluteFrames
10430 ReflowInput reflowInput(aState.PresContext(), this,
10431 aState.GetRenderingContext(),
10432 LogicalSize(ourWM, ISize(), NS_UNCONSTRAINEDSIZE),
10433 ReflowInput::InitFlag::DummyParentReflowInput);
10435 AddStateBits(NS_FRAME_IN_REFLOW);
10436 // Set up a |reflowStatus| to pass into ReflowAbsoluteFrames
10437 // (just a dummy value; hopefully that's OK)
10438 nsReflowStatus reflowStatus;
10439 ReflowAbsoluteFrames(aState.PresContext(), desiredSize, reflowInput,
10440 reflowStatus);
10441 RemoveStateBits(NS_FRAME_IN_REFLOW);
10444 nsSize oldSize(ourRect.Size());
10445 FinishAndStoreOverflow(desiredSize.mOverflowAreas,
10446 size.GetPhysicalSize(outerWM), &oldSize);
10448 SyncXULLayout(aState);
10450 return NS_OK;
10453 void nsIFrame::BoxReflow(nsBoxLayoutState& aState, nsPresContext* aPresContext,
10454 ReflowOutput& aDesiredSize,
10455 gfxContext* aRenderingContext, nscoord aX, nscoord aY,
10456 nscoord aWidth, nscoord aHeight, bool aMoveFrame) {
10457 DO_GLOBAL_REFLOW_COUNT("nsBoxToBlockAdaptor");
10459 nsBoxLayoutMetrics* metrics = BoxMetrics();
10460 if (MOZ_UNLIKELY(!metrics)) {
10461 // Can't proceed without BoxMetrics. This should only happen if something
10462 // is seriously broken, e.g. if we try to do XUL layout on a non-XUL frame.
10463 // (If this is a content process, we'll abort even in release builds,
10464 // because XUL layout mixup is extra surprising in content, and aborts are
10465 // less catastrophic in content vs. in chrome.)
10466 MOZ_RELEASE_ASSERT(!XRE_IsContentProcess(),
10467 "Starting XUL BoxReflow w/o BoxMetrics (in content)?");
10468 MOZ_ASSERT_UNREACHABLE("Starting XUL BoxReflow w/o BoxMetrics?");
10469 return;
10472 nsReflowStatus status;
10474 bool needsReflow = IsSubtreeDirty();
10476 // if we don't need a reflow then
10477 // lets see if we are already that size. Yes? then don't even reflow. We are
10478 // done.
10479 if (!needsReflow) {
10480 if (aWidth != NS_UNCONSTRAINEDSIZE && aHeight != NS_UNCONSTRAINEDSIZE) {
10481 // if the new calculated size has a 0 width or a 0 height
10482 if ((metrics->mLastSize.width == 0 || metrics->mLastSize.height == 0) &&
10483 (aWidth == 0 || aHeight == 0)) {
10484 needsReflow = false;
10485 aDesiredSize.Width() = aWidth;
10486 aDesiredSize.Height() = aHeight;
10487 SetSize(aDesiredSize.Size(GetWritingMode()));
10488 } else {
10489 aDesiredSize.Width() = metrics->mLastSize.width;
10490 aDesiredSize.Height() = metrics->mLastSize.height;
10492 // remove the margin. The rect of our child does not include it but our
10493 // calculated size does. don't reflow if we are already the right size
10494 if (metrics->mLastSize.width == aWidth &&
10495 metrics->mLastSize.height == aHeight)
10496 needsReflow = false;
10497 else
10498 needsReflow = true;
10500 } else {
10501 // if the width or height are intrinsic alway reflow because
10502 // we don't know what it should be.
10503 needsReflow = true;
10507 // ok now reflow the child into the spacers calculated space
10508 if (needsReflow) {
10509 aDesiredSize.ClearSize();
10511 // create a reflow input to tell our child to flow at the given size.
10513 // Construct a bogus parent reflow input so that there's a usable
10514 // containing block reflow input.
10515 nsMargin margin(0, 0, 0, 0);
10516 GetXULMargin(margin);
10518 nsSize parentSize(aWidth, aHeight);
10519 if (parentSize.height != NS_UNCONSTRAINEDSIZE)
10520 parentSize.height += margin.TopBottom();
10521 if (parentSize.width != NS_UNCONSTRAINEDSIZE)
10522 parentSize.width += margin.LeftRight();
10524 nsIFrame* parentFrame = GetParent();
10525 WritingMode parentWM = parentFrame->GetWritingMode();
10526 ReflowInput parentReflowInput(
10527 aPresContext, parentFrame, aRenderingContext,
10528 LogicalSize(parentWM, parentSize),
10529 ReflowInput::InitFlag::DummyParentReflowInput);
10531 // This may not do very much useful, but it's probably worth trying.
10532 if (parentSize.width != NS_UNCONSTRAINEDSIZE)
10533 parentReflowInput.SetComputedWidth(std::max(parentSize.width, 0));
10534 if (parentSize.height != NS_UNCONSTRAINEDSIZE)
10535 parentReflowInput.SetComputedHeight(std::max(parentSize.height, 0));
10536 parentReflowInput.SetComputedLogicalMargin(parentWM,
10537 LogicalMargin(parentWM));
10538 // XXX use box methods
10539 nsMargin padding;
10540 parentFrame->GetXULPadding(padding);
10541 parentReflowInput.SetComputedLogicalPadding(
10542 parentWM, LogicalMargin(parentWM, padding));
10543 nsMargin border;
10544 parentFrame->GetXULBorder(border);
10545 parentReflowInput.SetComputedLogicalBorderPadding(
10546 parentWM, LogicalMargin(parentWM, border + padding));
10548 // Construct the parent chain manually since constructing it normally
10549 // messes up dimensions.
10550 const ReflowInput* outerReflowInput = aState.OuterReflowInput();
10551 NS_ASSERTION(!outerReflowInput || outerReflowInput->mFrame != this,
10552 "in and out of XUL on a single frame?");
10553 const ReflowInput* parentRI;
10554 if (outerReflowInput && outerReflowInput->mFrame == parentFrame) {
10555 // We're a frame (such as a text control frame) that jumps into
10556 // box reflow and then straight out of it on the child frame.
10557 // This means we actually have a real parent reflow input.
10558 // nsLayoutUtils::InflationMinFontSizeFor used to need this to be
10559 // linked up correctly for text control frames, so do so here).
10560 parentRI = outerReflowInput;
10561 } else {
10562 parentRI = &parentReflowInput;
10565 // XXX Is it OK that this reflow input has only one ancestor?
10566 // (It used to have a bogus parent, skipping all the boxes).
10567 WritingMode wm = GetWritingMode();
10568 LogicalSize logicalSize(wm, nsSize(aWidth, aHeight));
10569 logicalSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
10570 ReflowInput reflowInput(aPresContext, *parentRI, this, logicalSize,
10571 Nothing(),
10572 ReflowInput::InitFlag::DummyParentReflowInput);
10574 // XXX_jwir3: This is somewhat fishy. If this is actually changing the value
10575 // here (which it might be), then we should make sure that it's
10576 // correct the first time around, rather than changing it later.
10577 reflowInput.mCBReflowInput = parentRI;
10579 reflowInput.mReflowDepth = aState.GetReflowDepth();
10581 // mComputedWidth and mComputedHeight are content-box, not
10582 // border-box
10583 if (aWidth != NS_UNCONSTRAINEDSIZE) {
10584 nscoord computedWidth =
10585 aWidth - reflowInput.ComputedPhysicalBorderPadding().LeftRight();
10586 computedWidth = std::max(computedWidth, 0);
10587 reflowInput.SetComputedWidth(computedWidth);
10590 // Most child frames of box frames (e.g. subdocument or scroll frames)
10591 // need to be constrained to the provided size and overflow as necessary.
10592 // The one exception are block frames, because we need to know their
10593 // natural height excluding any overflow area which may be caused by
10594 // various CSS effects such as shadow or outline.
10595 if (!IsBlockFrameOrSubclass()) {
10596 if (aHeight != NS_UNCONSTRAINEDSIZE) {
10597 nscoord computedHeight =
10598 aHeight - reflowInput.ComputedPhysicalBorderPadding().TopBottom();
10599 computedHeight = std::max(computedHeight, 0);
10600 reflowInput.SetComputedHeight(computedHeight);
10601 } else {
10602 reflowInput.SetComputedHeight(
10603 ComputeSize(
10604 aRenderingContext, wm, logicalSize, logicalSize.ISize(wm),
10605 reflowInput.ComputedLogicalMargin(wm).Size(wm),
10606 reflowInput.ComputedLogicalBorderPadding(wm).Size(wm), {}, {})
10607 .mLogicalSize.Height(wm));
10611 // Box layout calls SetRect before XULLayout, whereas non-box layout
10612 // calls SetRect after Reflow.
10613 // XXX Perhaps we should be doing this by twiddling the rect back to
10614 // mLastSize before calling Reflow and then switching it back, but
10615 // However, mLastSize can also be the size passed to BoxReflow by
10616 // RefreshSizeCache, so that doesn't really make sense.
10617 if (metrics->mLastSize.width != aWidth) {
10618 reflowInput.SetHResize(true);
10620 // When font size inflation is enabled, a horizontal resize
10621 // requires a full reflow. See ReflowInput::InitResizeFlags
10622 // for more details.
10623 if (nsLayoutUtils::FontSizeInflationEnabled(aPresContext)) {
10624 this->MarkSubtreeDirty();
10627 if (metrics->mLastSize.height != aHeight) {
10628 reflowInput.SetVResize(true);
10631 // place the child and reflow
10633 Reflow(aPresContext, aDesiredSize, reflowInput, status);
10635 NS_ASSERTION(status.IsComplete(), "bad status");
10637 ReflowChildFlags layoutFlags = aState.LayoutFlags();
10638 nsContainerFrame::FinishReflowChild(
10639 this, aPresContext, aDesiredSize, &reflowInput, aX, aY,
10640 layoutFlags | ReflowChildFlags::NoMoveFrame);
10642 // Save the ascent. (bug 103925)
10643 if (IsXULCollapsed()) {
10644 metrics->mAscent = 0;
10645 } else {
10646 if (aDesiredSize.BlockStartAscent() == ReflowOutput::ASK_FOR_BASELINE) {
10647 if (!nsLayoutUtils::GetFirstLineBaseline(wm, this, &metrics->mAscent))
10648 metrics->mAscent = GetLogicalBaseline(wm);
10649 } else
10650 metrics->mAscent = aDesiredSize.BlockStartAscent();
10653 } else {
10654 aDesiredSize.SetBlockStartAscent(metrics->mBlockAscent);
10657 metrics->mLastSize.width = aDesiredSize.Width();
10658 metrics->mLastSize.height = aDesiredSize.Height();
10661 nsBoxLayoutMetrics* nsIFrame::BoxMetrics() const {
10662 nsBoxLayoutMetrics* metrics = GetProperty(BoxMetricsProperty());
10663 NS_ASSERTION(
10664 metrics,
10665 "A box layout method was called but InitBoxMetrics was never called");
10666 return metrics;
10669 void nsIFrame::UpdateStyleOfChildAnonBox(nsIFrame* aChildFrame,
10670 ServoRestyleState& aRestyleState) {
10671 #ifdef DEBUG
10672 nsIFrame* parent = aChildFrame->GetInFlowParent();
10673 if (aChildFrame->IsTableFrame()) {
10674 parent = parent->GetParent();
10676 if (parent->IsLineFrame()) {
10677 parent = parent->GetParent();
10679 MOZ_ASSERT(nsLayoutUtils::FirstContinuationOrIBSplitSibling(parent) == this,
10680 "This should only be used for children!");
10681 #endif // DEBUG
10682 MOZ_ASSERT(!GetContent() || !aChildFrame->GetContent() ||
10683 aChildFrame->GetContent() == GetContent(),
10684 "What content node is it a frame for?");
10685 MOZ_ASSERT(!aChildFrame->GetPrevContinuation(),
10686 "Only first continuations should end up here");
10688 // We could force the caller to pass in the pseudo, since some callers know it
10689 // statically... But this API is a bit nicer.
10690 auto pseudo = aChildFrame->Style()->GetPseudoType();
10691 MOZ_ASSERT(PseudoStyle::IsAnonBox(pseudo), "Child is not an anon box?");
10692 MOZ_ASSERT(!PseudoStyle::IsNonInheritingAnonBox(pseudo),
10693 "Why did the caller bother calling us?");
10695 // Anon boxes inherit from their parent; that's us.
10696 RefPtr<ComputedStyle> newContext =
10697 aRestyleState.StyleSet().ResolveInheritingAnonymousBoxStyle(pseudo,
10698 Style());
10700 nsChangeHint childHint =
10701 UpdateStyleOfOwnedChildFrame(aChildFrame, newContext, aRestyleState);
10703 // Now that we've updated the style on aChildFrame, check whether it itself
10704 // has anon boxes to deal with.
10705 ServoRestyleState childrenState(*aChildFrame, aRestyleState, childHint,
10706 ServoRestyleState::Type::InFlow);
10707 aChildFrame->UpdateStyleOfOwnedAnonBoxes(childrenState);
10709 // Assuming anon boxes don't have ::backdrop associated with them... if that
10710 // ever changes, we'd need to handle that here, like we do in
10711 // RestyleManager::ProcessPostTraversal
10713 // We do need to handle block pseudo-elements here, though. Especially list
10714 // bullets.
10715 if (nsBlockFrame* block = do_QueryFrame(aChildFrame)) {
10716 block->UpdatePseudoElementStyles(childrenState);
10720 /* static */
10721 nsChangeHint nsIFrame::UpdateStyleOfOwnedChildFrame(
10722 nsIFrame* aChildFrame, ComputedStyle* aNewComputedStyle,
10723 ServoRestyleState& aRestyleState,
10724 const Maybe<ComputedStyle*>& aContinuationComputedStyle) {
10725 MOZ_ASSERT(!aChildFrame->GetAdditionalComputedStyle(0),
10726 "We don't handle additional styles here");
10728 // Figure out whether we have an actual change. It's important that we do
10729 // this, for several reasons:
10731 // 1) Even if all the child's changes are due to properties it inherits from
10732 // us, it's possible that no one ever asked us for those style structs and
10733 // hence changes to them aren't reflected in the changes handled at all.
10735 // 2) Content can change stylesheets that change the styles of pseudos, and
10736 // extensions can add/remove stylesheets that change the styles of
10737 // anonymous boxes directly.
10738 uint32_t equalStructs; // Not used, actually.
10739 nsChangeHint childHint = aChildFrame->Style()->CalcStyleDifference(
10740 *aNewComputedStyle, &equalStructs);
10742 // If aChildFrame is out of flow, then aRestyleState's "changes handled by the
10743 // parent" doesn't apply to it, because it may have some other parent in the
10744 // frame tree.
10745 if (!aChildFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
10746 childHint = NS_RemoveSubsumedHints(
10747 childHint, aRestyleState.ChangesHandledFor(aChildFrame));
10749 if (childHint) {
10750 if (childHint & nsChangeHint_ReconstructFrame) {
10751 // If we generate a reconstruct here, remove any non-reconstruct hints we
10752 // may have already generated for this content.
10753 aRestyleState.ChangeList().PopChangesForContent(
10754 aChildFrame->GetContent());
10756 aRestyleState.ChangeList().AppendChange(
10757 aChildFrame, aChildFrame->GetContent(), childHint);
10760 aChildFrame->SetComputedStyle(aNewComputedStyle);
10761 ComputedStyle* continuationStyle = aContinuationComputedStyle
10762 ? *aContinuationComputedStyle
10763 : aNewComputedStyle;
10764 for (nsIFrame* kid = aChildFrame->GetNextContinuation(); kid;
10765 kid = kid->GetNextContinuation()) {
10766 MOZ_ASSERT(!kid->GetAdditionalComputedStyle(0));
10767 kid->SetComputedStyle(continuationStyle);
10770 return childHint;
10773 /* static */
10774 void nsIFrame::AddInPopupStateBitToDescendants(nsIFrame* aFrame) {
10775 if (!aFrame->HasAnyStateBits(NS_FRAME_IN_POPUP) &&
10776 aFrame->TrackingVisibility()) {
10777 // Assume all frames in popups are visible.
10778 aFrame->IncApproximateVisibleCount();
10781 aFrame->AddStateBits(NS_FRAME_IN_POPUP);
10783 for (const auto& childList : aFrame->CrossDocChildLists()) {
10784 for (nsIFrame* child : childList.mList) {
10785 AddInPopupStateBitToDescendants(child);
10790 /* static */
10791 void nsIFrame::RemoveInPopupStateBitFromDescendants(nsIFrame* aFrame) {
10792 if (!aFrame->HasAnyStateBits(NS_FRAME_IN_POPUP) ||
10793 nsLayoutUtils::IsPopup(aFrame)) {
10794 return;
10797 aFrame->RemoveStateBits(NS_FRAME_IN_POPUP);
10799 if (aFrame->TrackingVisibility()) {
10800 // We assume all frames in popups are visible, so this decrement balances
10801 // out the increment in AddInPopupStateBitToDescendants above.
10802 aFrame->DecApproximateVisibleCount();
10804 for (const auto& childList : aFrame->CrossDocChildLists()) {
10805 for (nsIFrame* child : childList.mList) {
10806 RemoveInPopupStateBitFromDescendants(child);
10811 void nsIFrame::SetParent(nsContainerFrame* aParent) {
10812 // If our parent is a wrapper anon box, our new parent should be too. We
10813 // _can_ change parent if our parent is a wrapper anon box, because some
10814 // wrapper anon boxes can have continuations.
10815 MOZ_ASSERT_IF(ParentIsWrapperAnonBox(),
10816 aParent->Style()->IsInheritingAnonBox());
10818 // Note that the current mParent may already be destroyed at this point.
10819 mParent = aParent;
10820 MOZ_DIAGNOSTIC_ASSERT(!mParent || PresShell() == mParent->PresShell());
10821 if (::IsXULBoxWrapped(this)) {
10822 ::InitBoxMetrics(this, true);
10823 } else {
10824 // We could call Properties().Delete(BoxMetricsProperty()); here but
10825 // that's kind of slow and re-parenting in such a way that we were
10826 // IsXULBoxWrapped() before but not now should be very rare, so we'll just
10827 // keep this unused frame property until this frame dies instead.
10830 if (HasAnyStateBits(NS_FRAME_HAS_VIEW | NS_FRAME_HAS_CHILD_WITH_VIEW)) {
10831 for (nsIFrame* f = aParent;
10832 f && !f->HasAnyStateBits(NS_FRAME_HAS_CHILD_WITH_VIEW);
10833 f = f->GetParent()) {
10834 f->AddStateBits(NS_FRAME_HAS_CHILD_WITH_VIEW);
10838 if (HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)) {
10839 for (nsIFrame* f = aParent; f; f = f->GetParent()) {
10840 if (f->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)) {
10841 break;
10843 f->AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
10847 if (HasAnyStateBits(NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE)) {
10848 for (nsIFrame* f = aParent; f; f = f->GetParent()) {
10849 if (f->HasAnyStateBits(
10850 NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE)) {
10851 break;
10853 f->AddStateBits(NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE);
10857 if (HasInvalidFrameInSubtree()) {
10858 for (nsIFrame* f = aParent;
10859 f && !f->HasAnyStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT |
10860 NS_FRAME_IS_NONDISPLAY);
10861 f = nsLayoutUtils::GetCrossDocParentFrame(f)) {
10862 f->AddStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT);
10866 if (aParent->HasAnyStateBits(NS_FRAME_IN_POPUP)) {
10867 AddInPopupStateBitToDescendants(this);
10868 } else {
10869 RemoveInPopupStateBitFromDescendants(this);
10872 // If our new parent only has invalid children, then we just invalidate
10873 // ourselves too. This is probably faster than clearing the flag all
10874 // the way up the frame tree.
10875 if (aParent->HasAnyStateBits(NS_FRAME_ALL_DESCENDANTS_NEED_PAINT)) {
10876 InvalidateFrame();
10877 } else {
10878 SchedulePaint();
10882 void nsIFrame::CreateOwnLayerIfNeeded(nsDisplayListBuilder* aBuilder,
10883 nsDisplayList* aList, uint16_t aType,
10884 bool* aCreatedContainerItem) {
10885 if (GetContent() && GetContent()->IsXULElement() &&
10886 GetContent()->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::layer)) {
10887 aList->AppendNewToTopWithIndex<nsDisplayOwnLayer>(
10888 aBuilder, this, /* aIndex = */ aType, aList,
10889 aBuilder->CurrentActiveScrolledRoot(), nsDisplayOwnLayerFlags::None,
10890 ScrollbarData{}, true, false);
10891 if (aCreatedContainerItem) {
10892 *aCreatedContainerItem = true;
10897 bool nsIFrame::IsStackingContext(const nsStyleDisplay* aStyleDisplay,
10898 const nsStyleEffects* aStyleEffects) {
10899 return HasOpacity(aStyleDisplay, aStyleEffects, nullptr) ||
10900 IsTransformed(aStyleDisplay) ||
10901 ((aStyleDisplay->IsContainPaint() ||
10902 aStyleDisplay->IsContainLayout()) &&
10903 IsFrameOfType(eSupportsContainLayoutAndPaint)) ||
10904 // strictly speaking, 'perspective' doesn't require visual atomicity,
10905 // but the spec says it acts like the rest of these
10906 ChildrenHavePerspective(aStyleDisplay) ||
10907 aStyleEffects->mMixBlendMode != StyleBlend::Normal ||
10908 SVGIntegrationUtils::UsingEffectsForFrame(this) ||
10909 aStyleDisplay->IsPositionForcingStackingContext() ||
10910 ZIndex().isSome() ||
10911 (aStyleDisplay->mWillChange.bits &
10912 StyleWillChangeBits::STACKING_CONTEXT) ||
10913 aStyleDisplay->mIsolation != StyleIsolation::Auto ||
10914 aStyleEffects->HasBackdropFilters();
10917 bool nsIFrame::IsStackingContext() {
10918 return IsStackingContext(StyleDisplay(), StyleEffects());
10921 static bool IsFrameScrolledOutOfView(const nsIFrame* aTarget,
10922 const nsRect& aTargetRect,
10923 const nsIFrame* aParent) {
10924 // The ancestor frame we are checking if it clips out aTargetRect relative to
10925 // aTarget.
10926 nsIFrame* clipParent = nullptr;
10928 // find the first scrollable frame or root frame if we are in a fixed pos
10929 // subtree
10930 for (nsIFrame* f = const_cast<nsIFrame*>(aParent); f;
10931 f = nsLayoutUtils::GetCrossDocParentFrame(f)) {
10932 nsIScrollableFrame* scrollableFrame = do_QueryFrame(f);
10933 if (scrollableFrame) {
10934 clipParent = f;
10935 break;
10937 if (f->StyleDisplay()->mPosition == StylePositionProperty::Fixed &&
10938 nsLayoutUtils::IsReallyFixedPos(f)) {
10939 clipParent = f->GetParent();
10940 break;
10944 if (!clipParent) {
10945 // Even if we couldn't find the nearest scrollable frame, it might mean we
10946 // are in an out-of-process iframe, try to see if |aTarget| frame is
10947 // scrolled out of view in an scrollable frame in a cross-process ancestor
10948 // document.
10949 return nsLayoutUtils::FrameIsScrolledOutOfViewInCrossProcess(aTarget);
10952 nsRect clipRect = clipParent->InkOverflowRectRelativeToSelf();
10953 // We consider that the target is scrolled out if the scrollable (or root)
10954 // frame is empty.
10955 if (clipRect.IsEmpty()) {
10956 return true;
10959 nsRect transformedRect = nsLayoutUtils::TransformFrameRectToAncestor(
10960 aTarget, aTargetRect, clipParent);
10962 if (transformedRect.IsEmpty()) {
10963 // If the transformed rect is empty it represents a line or a point that we
10964 // should check is outside the the scrollable rect.
10965 if (transformedRect.x > clipRect.XMost() ||
10966 transformedRect.y > clipRect.YMost() ||
10967 clipRect.x > transformedRect.XMost() ||
10968 clipRect.y > transformedRect.YMost()) {
10969 return true;
10971 } else if (!transformedRect.Intersects(clipRect)) {
10972 return true;
10975 nsIFrame* parent = clipParent->GetParent();
10976 if (!parent) {
10977 return false;
10980 return IsFrameScrolledOutOfView(aTarget, aTargetRect, parent);
10983 bool nsIFrame::IsScrolledOutOfView() const {
10984 nsRect rect = InkOverflowRectRelativeToSelf();
10985 return IsFrameScrolledOutOfView(this, rect, this);
10988 gfx::Matrix nsIFrame::ComputeWidgetTransform() {
10989 const nsStyleUIReset* uiReset = StyleUIReset();
10990 if (uiReset->mMozWindowTransform.IsNone()) {
10991 return gfx::Matrix();
10994 TransformReferenceBox refBox(nullptr, nsRect(nsPoint(), GetSize()));
10996 nsPresContext* presContext = PresContext();
10997 int32_t appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
10998 gfx::Matrix4x4 matrix = nsStyleTransformMatrix::ReadTransforms(
10999 uiReset->mMozWindowTransform, refBox, float(appUnitsPerDevPixel));
11001 // Apply the -moz-window-transform-origin translation to the matrix.
11002 const StyleTransformOrigin& origin = uiReset->mWindowTransformOrigin;
11003 Point transformOrigin = nsStyleTransformMatrix::Convert2DPosition(
11004 origin.horizontal, origin.vertical, refBox, appUnitsPerDevPixel);
11005 matrix.ChangeBasis(Point3D(transformOrigin.x, transformOrigin.y, 0));
11007 gfx::Matrix result2d;
11008 if (!matrix.CanDraw2D(&result2d)) {
11009 // FIXME: It would be preferable to reject non-2D transforms at parse time.
11010 NS_WARNING(
11011 "-moz-window-transform does not describe a 2D transform, "
11012 "but only 2d transforms are supported");
11013 return gfx::Matrix();
11016 return result2d;
11019 void nsIFrame::DoUpdateStyleOfOwnedAnonBoxes(ServoRestyleState& aRestyleState) {
11020 // As a special case, we check for {ib}-split block frames here, rather
11021 // than have an nsInlineFrame::AppendDirectlyOwnedAnonBoxes implementation
11022 // that returns them.
11024 // (If we did handle them in AppendDirectlyOwnedAnonBoxes, we would have to
11025 // return *all* of the in-flow {ib}-split block frames, not just the first
11026 // one. For restyling, we really just need the first in flow, and the other
11027 // user of the AppendOwnedAnonBoxes API, AllChildIterator, doesn't need to
11028 // know about them at all, since these block frames never create NAC. So we
11029 // avoid any unncessary hashtable lookups for the {ib}-split frames by calling
11030 // UpdateStyleOfOwnedAnonBoxesForIBSplit directly here.)
11031 if (IsInlineFrame()) {
11032 if (HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) {
11033 static_cast<nsInlineFrame*>(this)->UpdateStyleOfOwnedAnonBoxesForIBSplit(
11034 aRestyleState);
11036 return;
11039 AutoTArray<OwnedAnonBox, 4> frames;
11040 AppendDirectlyOwnedAnonBoxes(frames);
11041 for (OwnedAnonBox& box : frames) {
11042 if (box.mUpdateStyleFn) {
11043 box.mUpdateStyleFn(this, box.mAnonBoxFrame, aRestyleState);
11044 } else {
11045 UpdateStyleOfChildAnonBox(box.mAnonBoxFrame, aRestyleState);
11050 /* virtual */
11051 void nsIFrame::AppendDirectlyOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult) {
11052 MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_OWNS_ANON_BOXES));
11053 MOZ_ASSERT(false, "Why did this get called?");
11056 void nsIFrame::DoAppendOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult) {
11057 size_t i = aResult.Length();
11058 AppendDirectlyOwnedAnonBoxes(aResult);
11060 // After appending the directly owned anonymous boxes of this frame to
11061 // aResult above, we need to check each of them to see if they own
11062 // any anonymous boxes themselves. Note that we keep progressing
11063 // through aResult, looking for additional entries in aResult from these
11064 // subsequent AppendDirectlyOwnedAnonBoxes calls. (Thus we can't
11065 // use a ranged for loop here.)
11067 while (i < aResult.Length()) {
11068 nsIFrame* f = aResult[i].mAnonBoxFrame;
11069 if (f->HasAnyStateBits(NS_FRAME_OWNS_ANON_BOXES)) {
11070 f->AppendDirectlyOwnedAnonBoxes(aResult);
11072 ++i;
11076 nsIFrame::CaretPosition::CaretPosition() : mContentOffset(0) {}
11078 nsIFrame::CaretPosition::~CaretPosition() = default;
11080 bool nsIFrame::HasCSSAnimations() {
11081 auto collection =
11082 AnimationCollection<CSSAnimation>::GetAnimationCollection(this);
11083 return collection && collection->mAnimations.Length() > 0;
11086 bool nsIFrame::HasCSSTransitions() {
11087 auto collection =
11088 AnimationCollection<CSSTransition>::GetAnimationCollection(this);
11089 return collection && collection->mAnimations.Length() > 0;
11092 void nsIFrame::AddSizeOfExcludingThisForTree(nsWindowSizes& aSizes) const {
11093 aSizes.mLayoutFramePropertiesSize +=
11094 mProperties.SizeOfExcludingThis(aSizes.mState.mMallocSizeOf);
11096 // We don't do this for Gecko because this stuff is stored in the nsPresArena
11097 // and so measured elsewhere.
11098 if (!aSizes.mState.HaveSeenPtr(mComputedStyle)) {
11099 mComputedStyle->AddSizeOfIncludingThis(aSizes,
11100 &aSizes.mLayoutComputedValuesNonDom);
11103 // And our additional styles.
11104 int32_t index = 0;
11105 while (auto* extra = GetAdditionalComputedStyle(index++)) {
11106 if (!aSizes.mState.HaveSeenPtr(extra)) {
11107 extra->AddSizeOfIncludingThis(aSizes,
11108 &aSizes.mLayoutComputedValuesNonDom);
11112 for (const auto& childList : ChildLists()) {
11113 for (const nsIFrame* f : childList.mList) {
11114 f->AddSizeOfExcludingThisForTree(aSizes);
11119 nsRect nsIFrame::GetCompositorHitTestArea(nsDisplayListBuilder* aBuilder) {
11120 nsRect area;
11122 nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetScrollableFrameFor(this);
11123 if (scrollFrame) {
11124 // If the frame is content of a scrollframe, then we need to pick up the
11125 // area corresponding to the overflow rect as well. Otherwise the parts of
11126 // the overflow that are not occupied by descendants get skipped and the
11127 // APZ code sends touch events to the content underneath instead.
11128 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1127773#c15.
11129 area = ScrollableOverflowRect();
11130 } else {
11131 area = nsRect(nsPoint(0, 0), GetSize());
11134 if (!area.IsEmpty()) {
11135 return area + aBuilder->ToReferenceFrame(this);
11138 return area;
11141 CompositorHitTestInfo nsIFrame::GetCompositorHitTestInfo(
11142 nsDisplayListBuilder* aBuilder) {
11143 CompositorHitTestInfo result = CompositorHitTestInvisibleToHit;
11145 if (aBuilder->IsInsidePointerEventsNoneDoc()) {
11146 // Somewhere up the parent document chain is a subdocument with pointer-
11147 // events:none set on it.
11148 return result;
11150 if (!GetParent()) {
11151 MOZ_ASSERT(IsViewportFrame());
11152 // Viewport frames are never event targets, other frames, like canvas
11153 // frames, are the event targets for any regions viewport frames may cover.
11154 return result;
11156 const StylePointerEvents pointerEvents =
11157 StyleUI()->GetEffectivePointerEvents(this);
11158 if (pointerEvents == StylePointerEvents::None) {
11159 return result;
11161 if (!StyleVisibility()->IsVisible()) {
11162 return result;
11165 // Anything that didn't match the above conditions is visible to hit-testing.
11166 result = CompositorHitTestFlags::eVisibleToHitTest;
11167 if (SVGIntegrationUtils::UsingMaskOrClipPathForFrame(this)) {
11168 // If WebRender is enabled, simple clip-paths can be converted into WR
11169 // clips that WR knows how to hit-test against, so we don't need to mark
11170 // it as an irregular area.
11171 if (!gfxVars::UseWebRender() ||
11172 !SVGIntegrationUtils::UsingSimpleClipPathForFrame(this)) {
11173 result += CompositorHitTestFlags::eIrregularArea;
11177 if (aBuilder->IsBuildingNonLayerizedScrollbar()) {
11178 // Scrollbars may be painted into a layer below the actual layer they will
11179 // scroll, and therefore wheel events may be dispatched to the outer frame
11180 // instead of the intended scrollframe. To address this, we force a d-t-c
11181 // region on scrollbar frames that won't be placed in their own layer. See
11182 // bug 1213324 for details.
11183 result += CompositorHitTestFlags::eInactiveScrollframe;
11184 } else if (aBuilder->GetAncestorHasApzAwareEventHandler()) {
11185 result += CompositorHitTestFlags::eApzAwareListeners;
11186 } else if (IsRangeFrame()) {
11187 // Range frames handle touch events directly without having a touch listener
11188 // so we need to let APZ know that this area cares about events.
11189 result += CompositorHitTestFlags::eApzAwareListeners;
11192 if (aBuilder->IsTouchEventPrefEnabledDoc()) {
11193 // Inherit the touch-action flags from the parent, if there is one. We do
11194 // this because of how the touch-action on a frame combines the touch-action
11195 // from ancestor DOM elements. Refer to the documentation in
11196 // TouchActionHelper.cpp for details; this code is meant to be equivalent to
11197 // that code, but woven into the top-down recursive display list building
11198 // process.
11199 CompositorHitTestInfo inheritedTouchAction =
11200 aBuilder->GetCompositorHitTestInfo() & CompositorHitTestTouchActionMask;
11202 nsIFrame* touchActionFrame = this;
11203 if (nsIScrollableFrame* scrollFrame =
11204 nsLayoutUtils::GetScrollableFrameFor(this)) {
11205 ScrollStyles ss = scrollFrame->GetScrollStyles();
11206 if (ss.mVertical != StyleOverflow::Hidden ||
11207 ss.mHorizontal != StyleOverflow::Hidden) {
11208 touchActionFrame = do_QueryFrame(scrollFrame);
11209 // On scrollframes, stop inheriting the pan-x and pan-y flags; instead,
11210 // reset them back to zero to allow panning on the scrollframe unless we
11211 // encounter an element that disables it that's inside the scrollframe.
11212 // This is equivalent to the |considerPanning| variable in
11213 // TouchActionHelper.cpp, but for a top-down traversal.
11214 CompositorHitTestInfo panMask(
11215 CompositorHitTestFlags::eTouchActionPanXDisabled,
11216 CompositorHitTestFlags::eTouchActionPanYDisabled);
11217 inheritedTouchAction -= panMask;
11221 result += inheritedTouchAction;
11223 const StyleTouchAction touchAction =
11224 nsLayoutUtils::GetTouchActionFromFrame(touchActionFrame);
11225 // The CSS allows the syntax auto | none | [pan-x || pan-y] | manipulation
11226 // so we can eliminate some combinations of things.
11227 if (touchAction == StyleTouchAction::AUTO) {
11228 // nothing to do
11229 } else if (touchAction & StyleTouchAction::MANIPULATION) {
11230 result += CompositorHitTestFlags::eTouchActionDoubleTapZoomDisabled;
11231 } else {
11232 // This path handles the cases none | [pan-x || pan-y || pinch-zoom] so
11233 // double-tap is disabled in here.
11234 if (!(touchAction & StyleTouchAction::PINCH_ZOOM)) {
11235 result += CompositorHitTestFlags::eTouchActionPinchZoomDisabled;
11238 result += CompositorHitTestFlags::eTouchActionDoubleTapZoomDisabled;
11240 if (!(touchAction & StyleTouchAction::PAN_X)) {
11241 result += CompositorHitTestFlags::eTouchActionPanXDisabled;
11243 if (!(touchAction & StyleTouchAction::PAN_Y)) {
11244 result += CompositorHitTestFlags::eTouchActionPanYDisabled;
11246 if (touchAction & StyleTouchAction::NONE) {
11247 // all the touch-action disabling flags will already have been set above
11248 MOZ_ASSERT(result.contains(CompositorHitTestTouchActionMask));
11253 const Maybe<ScrollDirection> scrollDirection =
11254 aBuilder->GetCurrentScrollbarDirection();
11255 if (scrollDirection.isSome()) {
11256 if (GetContent()->IsXULElement(nsGkAtoms::thumb)) {
11257 const bool thumbGetsLayer = aBuilder->GetCurrentScrollbarTarget() !=
11258 layers::ScrollableLayerGuid::NULL_SCROLL_ID;
11259 if (thumbGetsLayer) {
11260 result += CompositorHitTestFlags::eScrollbarThumb;
11261 } else {
11262 result += CompositorHitTestFlags::eInactiveScrollframe;
11266 if (*scrollDirection == ScrollDirection::eVertical) {
11267 result += CompositorHitTestFlags::eScrollbarVertical;
11270 // includes the ScrollbarFrame, SliderFrame, anything else that
11271 // might be inside the xul:scrollbar
11272 result += CompositorHitTestFlags::eScrollbar;
11275 return result;
11278 // Returns true if we can guarantee there is no visible descendants.
11279 static bool HasNoVisibleDescendants(const nsIFrame* aFrame) {
11280 for (const auto& childList : aFrame->ChildLists()) {
11281 for (nsIFrame* f : childList.mList) {
11282 if (nsPlaceholderFrame::GetRealFrameFor(f)
11283 ->IsVisibleOrMayHaveVisibleDescendants()) {
11284 return false;
11288 return true;
11291 void nsIFrame::UpdateVisibleDescendantsState() {
11292 if (StyleVisibility()->IsVisible()) {
11293 // Notify invisible ancestors that a visible descendant exists now.
11294 nsIFrame* ancestor;
11295 for (ancestor = GetInFlowParent();
11296 ancestor && !ancestor->StyleVisibility()->IsVisible();
11297 ancestor = ancestor->GetInFlowParent()) {
11298 ancestor->mAllDescendantsAreInvisible = false;
11300 } else {
11301 mAllDescendantsAreInvisible = HasNoVisibleDescendants(this);
11305 nsIFrame::PhysicalAxes nsIFrame::ShouldApplyOverflowClipping(
11306 const nsStyleDisplay* aDisp) const {
11307 MOZ_ASSERT(aDisp == StyleDisplay(), "Wrong display struct");
11309 // 'contain:paint', which we handle as 'overflow:clip' here. Except for
11310 // scrollframes we don't need contain:paint to add any clipping, because
11311 // the scrollable frame will already clip overflowing content, and because
11312 // 'contain:paint' should prevent all means of escaping that clipping
11313 // (e.g. because it forms a fixed-pos containing block).
11314 if (aDisp->IsContainPaint() && !IsScrollFrame() &&
11315 IsFrameOfType(eSupportsContainLayoutAndPaint)) {
11316 return PhysicalAxes::Both;
11319 // and overflow:hidden that we should interpret as clip
11320 if (aDisp->mOverflowX == StyleOverflow::Hidden &&
11321 aDisp->mOverflowY == StyleOverflow::Hidden) {
11322 // REVIEW: these are the frame types that set up clipping.
11323 LayoutFrameType type = Type();
11324 switch (type) {
11325 case LayoutFrameType::Table:
11326 case LayoutFrameType::TableCell:
11327 case LayoutFrameType::SVGOuterSVG:
11328 case LayoutFrameType::SVGInnerSVG:
11329 case LayoutFrameType::SVGSymbol:
11330 case LayoutFrameType::SVGForeignObject:
11331 return PhysicalAxes::Both;
11332 default:
11333 if (IsFrameOfType(nsIFrame::eReplacedContainsBlock)) {
11334 if (type == mozilla::LayoutFrameType::TextInput) {
11335 // It has an anonymous scroll frame that handles any overflow.
11336 return PhysicalAxes::None;
11338 return PhysicalAxes::Both;
11343 // clip overflow:clip, except for nsListControlFrame which is
11344 // an nsHTMLScrollFrame sub-class.
11345 if (MOZ_UNLIKELY((aDisp->mOverflowX == mozilla::StyleOverflow::Clip ||
11346 aDisp->mOverflowY == mozilla::StyleOverflow::Clip) &&
11347 !IsListControlFrame())) {
11348 // FIXME: we could use GetViewportScrollStylesOverrideElement() here instead
11349 // if that worked correctly in a print context. (see bug 1654667)
11350 const auto* element = Element::FromNodeOrNull(GetContent());
11351 if (!element ||
11352 !PresContext()->ElementWouldPropagateScrollStyles(*element)) {
11353 uint8_t axes = uint8_t(PhysicalAxes::None);
11354 if (aDisp->mOverflowX == mozilla::StyleOverflow::Clip) {
11355 axes |= uint8_t(PhysicalAxes::Horizontal);
11357 if (aDisp->mOverflowY == mozilla::StyleOverflow::Clip) {
11358 axes |= uint8_t(PhysicalAxes::Vertical);
11360 return PhysicalAxes(axes);
11364 if (HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
11365 return PhysicalAxes::None;
11368 // If we're paginated and a block, and have NS_BLOCK_CLIP_PAGINATED_OVERFLOW
11369 // set, then we want to clip our overflow.
11370 bool clip = HasAnyStateBits(NS_BLOCK_CLIP_PAGINATED_OVERFLOW) &&
11371 PresContext()->IsPaginated() && IsBlockFrame();
11372 return clip ? PhysicalAxes::Both : PhysicalAxes::None;
11375 void nsIFrame::AddPaintedPresShell(mozilla::PresShell* aPresShell) {
11376 PaintedPresShellList()->AppendElement(do_GetWeakReference(aPresShell));
11379 void nsIFrame::UpdatePaintCountForPaintedPresShells() {
11380 for (nsWeakPtr& item : *PaintedPresShellList()) {
11381 if (RefPtr<mozilla::PresShell> presShell = do_QueryReferent(item)) {
11382 presShell->IncrementPaintCount();
11387 bool nsIFrame::DidPaintPresShell(mozilla::PresShell* aPresShell) {
11388 for (nsWeakPtr& item : *PaintedPresShellList()) {
11389 RefPtr<mozilla::PresShell> presShell = do_QueryReferent(item);
11390 if (presShell == aPresShell) {
11391 return true;
11394 return false;
11397 #ifdef DEBUG
11398 static void GetTagName(nsIFrame* aFrame, nsIContent* aContent, int aResultSize,
11399 char* aResult) {
11400 if (aContent) {
11401 snprintf(aResult, aResultSize, "%s@%p",
11402 nsAtomCString(aContent->NodeInfo()->NameAtom()).get(), aFrame);
11403 } else {
11404 snprintf(aResult, aResultSize, "@%p", aFrame);
11408 void nsIFrame::Trace(const char* aMethod, bool aEnter) {
11409 if (NS_FRAME_LOG_TEST(sFrameLogModule, NS_FRAME_TRACE_CALLS)) {
11410 char tagbuf[40];
11411 GetTagName(this, mContent, sizeof(tagbuf), tagbuf);
11412 printf_stderr("%s: %s %s", tagbuf, aEnter ? "enter" : "exit", aMethod);
11416 void nsIFrame::Trace(const char* aMethod, bool aEnter,
11417 const nsReflowStatus& aStatus) {
11418 if (NS_FRAME_LOG_TEST(sFrameLogModule, NS_FRAME_TRACE_CALLS)) {
11419 char tagbuf[40];
11420 GetTagName(this, mContent, sizeof(tagbuf), tagbuf);
11421 printf_stderr("%s: %s %s, status=%scomplete%s", tagbuf,
11422 aEnter ? "enter" : "exit", aMethod,
11423 aStatus.IsIncomplete() ? "not" : "",
11424 (aStatus.NextInFlowNeedsReflow()) ? "+reflow" : "");
11428 void nsIFrame::TraceMsg(const char* aFormatString, ...) {
11429 if (NS_FRAME_LOG_TEST(sFrameLogModule, NS_FRAME_TRACE_CALLS)) {
11430 // Format arguments into a buffer
11431 char argbuf[200];
11432 va_list ap;
11433 va_start(ap, aFormatString);
11434 VsprintfLiteral(argbuf, aFormatString, ap);
11435 va_end(ap);
11437 char tagbuf[40];
11438 GetTagName(this, mContent, sizeof(tagbuf), tagbuf);
11439 printf_stderr("%s: %s", tagbuf, argbuf);
11443 void nsIFrame::VerifyDirtyBitSet(const nsFrameList& aFrameList) {
11444 for (nsFrameList::Enumerator e(aFrameList); !e.AtEnd(); e.Next()) {
11445 NS_ASSERTION(e.get()->HasAnyStateBits(NS_FRAME_IS_DIRTY),
11446 "dirty bit not set");
11450 // Start Display Reflow
11451 DR_cookie::DR_cookie(nsPresContext* aPresContext, nsIFrame* aFrame,
11452 const ReflowInput& aReflowInput, ReflowOutput& aMetrics,
11453 nsReflowStatus& aStatus)
11454 : mPresContext(aPresContext),
11455 mFrame(aFrame),
11456 mReflowInput(aReflowInput),
11457 mMetrics(aMetrics),
11458 mStatus(aStatus) {
11459 MOZ_COUNT_CTOR(DR_cookie);
11460 mValue = nsIFrame::DisplayReflowEnter(aPresContext, mFrame, mReflowInput);
11463 DR_cookie::~DR_cookie() {
11464 MOZ_COUNT_DTOR(DR_cookie);
11465 nsIFrame::DisplayReflowExit(mPresContext, mFrame, mMetrics, mStatus, mValue);
11468 DR_layout_cookie::DR_layout_cookie(nsIFrame* aFrame) : mFrame(aFrame) {
11469 MOZ_COUNT_CTOR(DR_layout_cookie);
11470 mValue = nsIFrame::DisplayLayoutEnter(mFrame);
11473 DR_layout_cookie::~DR_layout_cookie() {
11474 MOZ_COUNT_DTOR(DR_layout_cookie);
11475 nsIFrame::DisplayLayoutExit(mFrame, mValue);
11478 DR_intrinsic_inline_size_cookie::DR_intrinsic_inline_size_cookie(
11479 nsIFrame* aFrame, const char* aType, nscoord& aResult)
11480 : mFrame(aFrame), mType(aType), mResult(aResult) {
11481 MOZ_COUNT_CTOR(DR_intrinsic_inline_size_cookie);
11482 mValue = nsIFrame::DisplayIntrinsicISizeEnter(mFrame, mType);
11485 DR_intrinsic_inline_size_cookie::~DR_intrinsic_inline_size_cookie() {
11486 MOZ_COUNT_DTOR(DR_intrinsic_inline_size_cookie);
11487 nsIFrame::DisplayIntrinsicISizeExit(mFrame, mType, mResult, mValue);
11490 DR_intrinsic_size_cookie::DR_intrinsic_size_cookie(nsIFrame* aFrame,
11491 const char* aType,
11492 nsSize& aResult)
11493 : mFrame(aFrame), mType(aType), mResult(aResult) {
11494 MOZ_COUNT_CTOR(DR_intrinsic_size_cookie);
11495 mValue = nsIFrame::DisplayIntrinsicSizeEnter(mFrame, mType);
11498 DR_intrinsic_size_cookie::~DR_intrinsic_size_cookie() {
11499 MOZ_COUNT_DTOR(DR_intrinsic_size_cookie);
11500 nsIFrame::DisplayIntrinsicSizeExit(mFrame, mType, mResult, mValue);
11503 DR_init_constraints_cookie::DR_init_constraints_cookie(
11504 nsIFrame* aFrame, ReflowInput* aState, nscoord aCBWidth, nscoord aCBHeight,
11505 const mozilla::Maybe<mozilla::LogicalMargin> aBorder,
11506 const mozilla::Maybe<mozilla::LogicalMargin> aPadding)
11507 : mFrame(aFrame), mState(aState) {
11508 MOZ_COUNT_CTOR(DR_init_constraints_cookie);
11509 nsMargin border;
11510 if (aBorder) {
11511 border = aBorder->GetPhysicalMargin(aFrame->GetWritingMode());
11513 nsMargin padding;
11514 if (aPadding) {
11515 padding = aPadding->GetPhysicalMargin(aFrame->GetWritingMode());
11517 mValue = ReflowInput::DisplayInitConstraintsEnter(
11518 mFrame, mState, aCBWidth, aCBHeight, aBorder ? &border : nullptr,
11519 aPadding ? &padding : nullptr);
11522 DR_init_constraints_cookie::~DR_init_constraints_cookie() {
11523 MOZ_COUNT_DTOR(DR_init_constraints_cookie);
11524 ReflowInput::DisplayInitConstraintsExit(mFrame, mState, mValue);
11527 DR_init_offsets_cookie::DR_init_offsets_cookie(
11528 nsIFrame* aFrame, SizeComputationInput* aState, nscoord aPercentBasis,
11529 WritingMode aCBWritingMode,
11530 const mozilla::Maybe<mozilla::LogicalMargin> aBorder,
11531 const mozilla::Maybe<mozilla::LogicalMargin> aPadding)
11532 : mFrame(aFrame), mState(aState) {
11533 MOZ_COUNT_CTOR(DR_init_offsets_cookie);
11534 nsMargin border;
11535 if (aBorder) {
11536 border = aBorder->GetPhysicalMargin(aFrame->GetWritingMode());
11538 nsMargin padding;
11539 if (aPadding) {
11540 padding = aPadding->GetPhysicalMargin(aFrame->GetWritingMode());
11542 mValue = SizeComputationInput::DisplayInitOffsetsEnter(
11543 mFrame, mState, aPercentBasis, aCBWritingMode,
11544 aBorder ? &border : nullptr, aPadding ? &padding : nullptr);
11547 DR_init_offsets_cookie::~DR_init_offsets_cookie() {
11548 MOZ_COUNT_DTOR(DR_init_offsets_cookie);
11549 SizeComputationInput::DisplayInitOffsetsExit(mFrame, mState, mValue);
11552 struct DR_Rule;
11554 struct DR_FrameTypeInfo {
11555 DR_FrameTypeInfo(LayoutFrameType aFrameType, const char* aFrameNameAbbrev,
11556 const char* aFrameName);
11557 ~DR_FrameTypeInfo();
11559 LayoutFrameType mType;
11560 char mNameAbbrev[16];
11561 char mName[32];
11562 nsTArray<DR_Rule*> mRules;
11564 private:
11565 DR_FrameTypeInfo& operator=(const DR_FrameTypeInfo&) = delete;
11568 struct DR_FrameTreeNode;
11569 struct DR_Rule;
11571 struct DR_State {
11572 DR_State();
11573 ~DR_State();
11574 void Init();
11575 void AddFrameTypeInfo(LayoutFrameType aFrameType,
11576 const char* aFrameNameAbbrev, const char* aFrameName);
11577 DR_FrameTypeInfo* GetFrameTypeInfo(LayoutFrameType aFrameType);
11578 DR_FrameTypeInfo* GetFrameTypeInfo(char* aFrameName);
11579 void InitFrameTypeTable();
11580 DR_FrameTreeNode* CreateTreeNode(nsIFrame* aFrame,
11581 const ReflowInput* aReflowInput);
11582 void FindMatchingRule(DR_FrameTreeNode& aNode);
11583 bool RuleMatches(DR_Rule& aRule, DR_FrameTreeNode& aNode);
11584 bool GetToken(FILE* aFile, char* aBuf, size_t aBufSize);
11585 DR_Rule* ParseRule(FILE* aFile);
11586 void ParseRulesFile();
11587 void AddRule(nsTArray<DR_Rule*>& aRules, DR_Rule& aRule);
11588 bool IsWhiteSpace(int c);
11589 bool GetNumber(char* aBuf, int32_t& aNumber);
11590 void PrettyUC(nscoord aSize, char* aBuf, int aBufSize);
11591 void PrintMargin(const char* tag, const nsMargin* aMargin);
11592 void DisplayFrameTypeInfo(nsIFrame* aFrame, int32_t aIndent);
11593 void DeleteTreeNode(DR_FrameTreeNode& aNode);
11595 bool mInited;
11596 bool mActive;
11597 int32_t mCount;
11598 int32_t mAssert;
11599 int32_t mIndent;
11600 bool mIndentUndisplayedFrames;
11601 bool mDisplayPixelErrors;
11602 nsTArray<DR_Rule*> mWildRules;
11603 nsTArray<DR_FrameTypeInfo> mFrameTypeTable;
11604 // reflow specific state
11605 nsTArray<DR_FrameTreeNode*> mFrameTreeLeaves;
11608 static DR_State* DR_state; // the one and only DR_State
11610 struct DR_RulePart {
11611 explicit DR_RulePart(LayoutFrameType aFrameType)
11612 : mFrameType(aFrameType), mNext(0) {}
11614 void Destroy();
11616 LayoutFrameType mFrameType;
11617 DR_RulePart* mNext;
11620 void DR_RulePart::Destroy() {
11621 if (mNext) {
11622 mNext->Destroy();
11624 delete this;
11627 struct DR_Rule {
11628 DR_Rule() : mLength(0), mTarget(nullptr), mDisplay(false) {
11629 MOZ_COUNT_CTOR(DR_Rule);
11631 ~DR_Rule() {
11632 if (mTarget) mTarget->Destroy();
11633 MOZ_COUNT_DTOR(DR_Rule);
11635 void AddPart(LayoutFrameType aFrameType);
11637 uint32_t mLength;
11638 DR_RulePart* mTarget;
11639 bool mDisplay;
11642 void DR_Rule::AddPart(LayoutFrameType aFrameType) {
11643 DR_RulePart* newPart = new DR_RulePart(aFrameType);
11644 newPart->mNext = mTarget;
11645 mTarget = newPart;
11646 mLength++;
11649 DR_FrameTypeInfo::~DR_FrameTypeInfo() {
11650 int32_t numElements;
11651 numElements = mRules.Length();
11652 for (int32_t i = numElements - 1; i >= 0; i--) {
11653 delete mRules.ElementAt(i);
11657 DR_FrameTypeInfo::DR_FrameTypeInfo(LayoutFrameType aFrameType,
11658 const char* aFrameNameAbbrev,
11659 const char* aFrameName) {
11660 mType = aFrameType;
11661 PL_strncpyz(mNameAbbrev, aFrameNameAbbrev, sizeof(mNameAbbrev));
11662 PL_strncpyz(mName, aFrameName, sizeof(mName));
11665 struct DR_FrameTreeNode {
11666 DR_FrameTreeNode(nsIFrame* aFrame, DR_FrameTreeNode* aParent)
11667 : mFrame(aFrame), mParent(aParent), mDisplay(0), mIndent(0) {
11668 MOZ_COUNT_CTOR(DR_FrameTreeNode);
11671 MOZ_COUNTED_DTOR(DR_FrameTreeNode)
11673 nsIFrame* mFrame;
11674 DR_FrameTreeNode* mParent;
11675 bool mDisplay;
11676 uint32_t mIndent;
11679 // DR_State implementation
11681 DR_State::DR_State()
11682 : mInited(false),
11683 mActive(false),
11684 mCount(0),
11685 mAssert(-1),
11686 mIndent(0),
11687 mIndentUndisplayedFrames(false),
11688 mDisplayPixelErrors(false) {
11689 MOZ_COUNT_CTOR(DR_State);
11692 void DR_State::Init() {
11693 char* env = PR_GetEnv("GECKO_DISPLAY_REFLOW_ASSERT");
11694 int32_t num;
11695 if (env) {
11696 if (GetNumber(env, num))
11697 mAssert = num;
11698 else
11699 printf("GECKO_DISPLAY_REFLOW_ASSERT - invalid value = %s", env);
11702 env = PR_GetEnv("GECKO_DISPLAY_REFLOW_INDENT_START");
11703 if (env) {
11704 if (GetNumber(env, num))
11705 mIndent = num;
11706 else
11707 printf("GECKO_DISPLAY_REFLOW_INDENT_START - invalid value = %s", env);
11710 env = PR_GetEnv("GECKO_DISPLAY_REFLOW_INDENT_UNDISPLAYED_FRAMES");
11711 if (env) {
11712 if (GetNumber(env, num))
11713 mIndentUndisplayedFrames = num;
11714 else
11715 printf(
11716 "GECKO_DISPLAY_REFLOW_INDENT_UNDISPLAYED_FRAMES - invalid value = %s",
11717 env);
11720 env = PR_GetEnv("GECKO_DISPLAY_REFLOW_FLAG_PIXEL_ERRORS");
11721 if (env) {
11722 if (GetNumber(env, num))
11723 mDisplayPixelErrors = num;
11724 else
11725 printf("GECKO_DISPLAY_REFLOW_FLAG_PIXEL_ERRORS - invalid value = %s",
11726 env);
11729 InitFrameTypeTable();
11730 ParseRulesFile();
11731 mInited = true;
11734 DR_State::~DR_State() {
11735 MOZ_COUNT_DTOR(DR_State);
11736 int32_t numElements, i;
11737 numElements = mWildRules.Length();
11738 for (i = numElements - 1; i >= 0; i--) {
11739 delete mWildRules.ElementAt(i);
11741 numElements = mFrameTreeLeaves.Length();
11742 for (i = numElements - 1; i >= 0; i--) {
11743 delete mFrameTreeLeaves.ElementAt(i);
11747 bool DR_State::GetNumber(char* aBuf, int32_t& aNumber) {
11748 if (sscanf(aBuf, "%d", &aNumber) > 0)
11749 return true;
11750 else
11751 return false;
11754 bool DR_State::IsWhiteSpace(int c) {
11755 return (c == ' ') || (c == '\t') || (c == '\n') || (c == '\r');
11758 bool DR_State::GetToken(FILE* aFile, char* aBuf, size_t aBufSize) {
11759 bool haveToken = false;
11760 aBuf[0] = 0;
11761 // get the 1st non whitespace char
11762 int c = -1;
11763 for (c = getc(aFile); (c > 0) && IsWhiteSpace(c); c = getc(aFile)) {
11766 if (c > 0) {
11767 haveToken = true;
11768 aBuf[0] = c;
11769 // get everything up to the next whitespace char
11770 size_t cX;
11771 for (cX = 1; cX + 1 < aBufSize; cX++) {
11772 c = getc(aFile);
11773 if (c < 0) { // EOF
11774 ungetc(' ', aFile);
11775 break;
11776 } else {
11777 if (IsWhiteSpace(c)) {
11778 break;
11779 } else {
11780 aBuf[cX] = c;
11784 aBuf[cX] = 0;
11786 return haveToken;
11789 DR_Rule* DR_State::ParseRule(FILE* aFile) {
11790 char buf[128];
11791 int32_t doDisplay;
11792 DR_Rule* rule = nullptr;
11793 while (GetToken(aFile, buf, sizeof(buf))) {
11794 if (GetNumber(buf, doDisplay)) {
11795 if (rule) {
11796 rule->mDisplay = !!doDisplay;
11797 break;
11798 } else {
11799 printf("unexpected token - %s \n", buf);
11801 } else {
11802 if (!rule) {
11803 rule = new DR_Rule;
11805 if (strcmp(buf, "*") == 0) {
11806 rule->AddPart(LayoutFrameType::None);
11807 } else {
11808 DR_FrameTypeInfo* info = GetFrameTypeInfo(buf);
11809 if (info) {
11810 rule->AddPart(info->mType);
11811 } else {
11812 printf("invalid frame type - %s \n", buf);
11817 return rule;
11820 void DR_State::AddRule(nsTArray<DR_Rule*>& aRules, DR_Rule& aRule) {
11821 int32_t numRules = aRules.Length();
11822 for (int32_t ruleX = 0; ruleX < numRules; ruleX++) {
11823 DR_Rule* rule = aRules.ElementAt(ruleX);
11824 NS_ASSERTION(rule, "program error");
11825 if (aRule.mLength > rule->mLength) {
11826 aRules.InsertElementAt(ruleX, &aRule);
11827 return;
11830 aRules.AppendElement(&aRule);
11833 static Maybe<bool> ShouldLogReflow(const char* processes) {
11834 switch (processes[0]) {
11835 case 'A':
11836 case 'a':
11837 return Some(true);
11838 case 'P':
11839 case 'p':
11840 return Some(XRE_IsParentProcess());
11841 case 'C':
11842 case 'c':
11843 return Some(XRE_IsContentProcess());
11844 default:
11845 return Nothing{};
11849 void DR_State::ParseRulesFile() {
11850 char* processes = PR_GetEnv("GECKO_DISPLAY_REFLOW_PROCESSES");
11851 if (processes) {
11852 Maybe<bool> enableLog = ShouldLogReflow(processes);
11853 if (enableLog.isNothing()) {
11854 MOZ_CRASH("GECKO_DISPLAY_REFLOW_PROCESSES: [a]ll [p]arent [c]ontent");
11855 } else if (enableLog.value()) {
11856 DR_Rule* rule = new DR_Rule;
11857 rule->AddPart(LayoutFrameType::None);
11858 rule->mDisplay = true;
11859 AddRule(mWildRules, *rule);
11860 mActive = true;
11862 return;
11865 char* path = PR_GetEnv("GECKO_DISPLAY_REFLOW_RULES_FILE");
11866 if (path) {
11867 FILE* inFile = fopen(path, "r");
11868 if (!inFile) {
11869 MOZ_CRASH(
11870 "Failed to open the specified rules file; Try `--setpref "
11871 "security.sandbox.content.level=2` if the sandbox is at cause");
11873 for (DR_Rule* rule = ParseRule(inFile); rule; rule = ParseRule(inFile)) {
11874 if (rule->mTarget) {
11875 LayoutFrameType fType = rule->mTarget->mFrameType;
11876 if (fType != LayoutFrameType::None) {
11877 DR_FrameTypeInfo* info = GetFrameTypeInfo(fType);
11878 AddRule(info->mRules, *rule);
11879 } else {
11880 AddRule(mWildRules, *rule);
11882 mActive = true;
11886 fclose(inFile);
11890 void DR_State::AddFrameTypeInfo(LayoutFrameType aFrameType,
11891 const char* aFrameNameAbbrev,
11892 const char* aFrameName) {
11893 mFrameTypeTable.EmplaceBack(aFrameType, aFrameNameAbbrev, aFrameName);
11896 DR_FrameTypeInfo* DR_State::GetFrameTypeInfo(LayoutFrameType aFrameType) {
11897 int32_t numEntries = mFrameTypeTable.Length();
11898 NS_ASSERTION(numEntries != 0, "empty FrameTypeTable");
11899 for (int32_t i = 0; i < numEntries; i++) {
11900 DR_FrameTypeInfo& info = mFrameTypeTable.ElementAt(i);
11901 if (info.mType == aFrameType) {
11902 return &info;
11905 return &mFrameTypeTable.ElementAt(numEntries -
11906 1); // return unknown frame type
11909 DR_FrameTypeInfo* DR_State::GetFrameTypeInfo(char* aFrameName) {
11910 int32_t numEntries = mFrameTypeTable.Length();
11911 NS_ASSERTION(numEntries != 0, "empty FrameTypeTable");
11912 for (int32_t i = 0; i < numEntries; i++) {
11913 DR_FrameTypeInfo& info = mFrameTypeTable.ElementAt(i);
11914 if ((strcmp(aFrameName, info.mName) == 0) ||
11915 (strcmp(aFrameName, info.mNameAbbrev) == 0)) {
11916 return &info;
11919 return &mFrameTypeTable.ElementAt(numEntries -
11920 1); // return unknown frame type
11923 void DR_State::InitFrameTypeTable() {
11924 AddFrameTypeInfo(LayoutFrameType::Block, "block", "block");
11925 AddFrameTypeInfo(LayoutFrameType::Br, "br", "br");
11926 AddFrameTypeInfo(LayoutFrameType::Bullet, "bullet", "bullet");
11927 AddFrameTypeInfo(LayoutFrameType::ColorControl, "color", "colorControl");
11928 AddFrameTypeInfo(LayoutFrameType::GfxButtonControl, "button",
11929 "gfxButtonControl");
11930 AddFrameTypeInfo(LayoutFrameType::HTMLButtonControl, "HTMLbutton",
11931 "HTMLButtonControl");
11932 AddFrameTypeInfo(LayoutFrameType::HTMLCanvas, "HTMLCanvas", "HTMLCanvas");
11933 AddFrameTypeInfo(LayoutFrameType::SubDocument, "subdoc", "subDocument");
11934 AddFrameTypeInfo(LayoutFrameType::Image, "img", "image");
11935 AddFrameTypeInfo(LayoutFrameType::Inline, "inline", "inline");
11936 AddFrameTypeInfo(LayoutFrameType::Letter, "letter", "letter");
11937 AddFrameTypeInfo(LayoutFrameType::Line, "line", "line");
11938 AddFrameTypeInfo(LayoutFrameType::ListControl, "select", "select");
11939 AddFrameTypeInfo(LayoutFrameType::Page, "page", "page");
11940 AddFrameTypeInfo(LayoutFrameType::Placeholder, "place", "placeholder");
11941 AddFrameTypeInfo(LayoutFrameType::Canvas, "canvas", "canvas");
11942 AddFrameTypeInfo(LayoutFrameType::XULRoot, "xulroot", "xulroot");
11943 AddFrameTypeInfo(LayoutFrameType::Scroll, "scroll", "scroll");
11944 AddFrameTypeInfo(LayoutFrameType::TableCell, "cell", "tableCell");
11945 AddFrameTypeInfo(LayoutFrameType::TableCol, "col", "tableCol");
11946 AddFrameTypeInfo(LayoutFrameType::TableColGroup, "colG", "tableColGroup");
11947 AddFrameTypeInfo(LayoutFrameType::Table, "tbl", "table");
11948 AddFrameTypeInfo(LayoutFrameType::TableWrapper, "tblW", "tableWrapper");
11949 AddFrameTypeInfo(LayoutFrameType::TableRowGroup, "rowG", "tableRowGroup");
11950 AddFrameTypeInfo(LayoutFrameType::TableRow, "row", "tableRow");
11951 AddFrameTypeInfo(LayoutFrameType::TextInput, "textCtl", "textInput");
11952 AddFrameTypeInfo(LayoutFrameType::Text, "text", "text");
11953 AddFrameTypeInfo(LayoutFrameType::Viewport, "VP", "viewport");
11954 # ifdef MOZ_XUL
11955 AddFrameTypeInfo(LayoutFrameType::XULLabel, "XULLabel", "XULLabel");
11956 AddFrameTypeInfo(LayoutFrameType::Box, "Box", "Box");
11957 AddFrameTypeInfo(LayoutFrameType::Slider, "Slider", "Slider");
11958 AddFrameTypeInfo(LayoutFrameType::PopupSet, "PopupSet", "PopupSet");
11959 # endif
11960 AddFrameTypeInfo(LayoutFrameType::None, "unknown", "unknown");
11963 void DR_State::DisplayFrameTypeInfo(nsIFrame* aFrame, int32_t aIndent) {
11964 DR_FrameTypeInfo* frameTypeInfo = GetFrameTypeInfo(aFrame->Type());
11965 if (frameTypeInfo) {
11966 for (int32_t i = 0; i < aIndent; i++) {
11967 printf(" ");
11969 if (!strcmp(frameTypeInfo->mNameAbbrev, "unknown")) {
11970 if (aFrame) {
11971 nsAutoString name;
11972 aFrame->GetFrameName(name);
11973 printf("%s %p ", NS_LossyConvertUTF16toASCII(name).get(),
11974 (void*)aFrame);
11975 } else {
11976 printf("%s %p ", frameTypeInfo->mNameAbbrev, (void*)aFrame);
11978 } else {
11979 printf("%s %p ", frameTypeInfo->mNameAbbrev, (void*)aFrame);
11984 bool DR_State::RuleMatches(DR_Rule& aRule, DR_FrameTreeNode& aNode) {
11985 NS_ASSERTION(aRule.mTarget, "program error");
11987 DR_RulePart* rulePart;
11988 DR_FrameTreeNode* parentNode;
11989 for (rulePart = aRule.mTarget->mNext, parentNode = aNode.mParent;
11990 rulePart && parentNode;
11991 rulePart = rulePart->mNext, parentNode = parentNode->mParent) {
11992 if (rulePart->mFrameType != LayoutFrameType::None) {
11993 if (parentNode->mFrame) {
11994 if (rulePart->mFrameType != parentNode->mFrame->Type()) {
11995 return false;
11997 } else
11998 NS_ASSERTION(false, "program error");
12000 // else wild card match
12002 return true;
12005 void DR_State::FindMatchingRule(DR_FrameTreeNode& aNode) {
12006 if (!aNode.mFrame) {
12007 NS_ASSERTION(false, "invalid DR_FrameTreeNode \n");
12008 return;
12011 bool matchingRule = false;
12013 DR_FrameTypeInfo* info = GetFrameTypeInfo(aNode.mFrame->Type());
12014 NS_ASSERTION(info, "program error");
12015 int32_t numRules = info->mRules.Length();
12016 for (int32_t ruleX = 0; ruleX < numRules; ruleX++) {
12017 DR_Rule* rule = info->mRules.ElementAt(ruleX);
12018 if (rule && RuleMatches(*rule, aNode)) {
12019 aNode.mDisplay = rule->mDisplay;
12020 matchingRule = true;
12021 break;
12024 if (!matchingRule) {
12025 int32_t numWildRules = mWildRules.Length();
12026 for (int32_t ruleX = 0; ruleX < numWildRules; ruleX++) {
12027 DR_Rule* rule = mWildRules.ElementAt(ruleX);
12028 if (rule && RuleMatches(*rule, aNode)) {
12029 aNode.mDisplay = rule->mDisplay;
12030 break;
12036 DR_FrameTreeNode* DR_State::CreateTreeNode(nsIFrame* aFrame,
12037 const ReflowInput* aReflowInput) {
12038 // find the frame of the parent reflow input (usually just the parent of
12039 // aFrame)
12040 nsIFrame* parentFrame;
12041 if (aReflowInput) {
12042 const ReflowInput* parentRI = aReflowInput->mParentReflowInput;
12043 parentFrame = (parentRI) ? parentRI->mFrame : nullptr;
12044 } else {
12045 parentFrame = aFrame->GetParent();
12048 // find the parent tree node leaf
12049 DR_FrameTreeNode* parentNode = nullptr;
12051 DR_FrameTreeNode* lastLeaf = nullptr;
12052 if (mFrameTreeLeaves.Length())
12053 lastLeaf = mFrameTreeLeaves.ElementAt(mFrameTreeLeaves.Length() - 1);
12054 if (lastLeaf) {
12055 for (parentNode = lastLeaf;
12056 parentNode && (parentNode->mFrame != parentFrame);
12057 parentNode = parentNode->mParent) {
12060 DR_FrameTreeNode* newNode = new DR_FrameTreeNode(aFrame, parentNode);
12061 FindMatchingRule(*newNode);
12063 newNode->mIndent = mIndent;
12064 if (newNode->mDisplay || mIndentUndisplayedFrames) {
12065 ++mIndent;
12068 if (lastLeaf && (lastLeaf == parentNode)) {
12069 mFrameTreeLeaves.RemoveLastElement();
12071 mFrameTreeLeaves.AppendElement(newNode);
12072 mCount++;
12074 return newNode;
12077 void DR_State::PrettyUC(nscoord aSize, char* aBuf, int aBufSize) {
12078 if (NS_UNCONSTRAINEDSIZE == aSize) {
12079 strcpy(aBuf, "UC");
12080 } else {
12081 if ((nscoord)0xdeadbeefU == aSize) {
12082 strcpy(aBuf, "deadbeef");
12083 } else {
12084 snprintf(aBuf, aBufSize, "%d", aSize);
12089 void DR_State::PrintMargin(const char* tag, const nsMargin* aMargin) {
12090 if (aMargin) {
12091 char t[16], r[16], b[16], l[16];
12092 PrettyUC(aMargin->top, t, 16);
12093 PrettyUC(aMargin->right, r, 16);
12094 PrettyUC(aMargin->bottom, b, 16);
12095 PrettyUC(aMargin->left, l, 16);
12096 printf(" %s=%s,%s,%s,%s", tag, t, r, b, l);
12097 } else {
12098 // use %p here for consistency with other null-pointer printouts
12099 printf(" %s=%p", tag, (void*)aMargin);
12103 void DR_State::DeleteTreeNode(DR_FrameTreeNode& aNode) {
12104 mFrameTreeLeaves.RemoveElement(&aNode);
12105 int32_t numLeaves = mFrameTreeLeaves.Length();
12106 if ((0 == numLeaves) ||
12107 (aNode.mParent != mFrameTreeLeaves.ElementAt(numLeaves - 1))) {
12108 mFrameTreeLeaves.AppendElement(aNode.mParent);
12111 if (aNode.mDisplay || mIndentUndisplayedFrames) {
12112 --mIndent;
12114 // delete the tree node
12115 delete &aNode;
12118 static void CheckPixelError(nscoord aSize, int32_t aPixelToTwips) {
12119 if (NS_UNCONSTRAINEDSIZE != aSize) {
12120 if ((aSize % aPixelToTwips) > 0) {
12121 printf("VALUE %d is not a whole pixel \n", aSize);
12126 static void DisplayReflowEnterPrint(nsPresContext* aPresContext,
12127 nsIFrame* aFrame,
12128 const ReflowInput& aReflowInput,
12129 DR_FrameTreeNode& aTreeNode,
12130 bool aChanged) {
12131 if (aTreeNode.mDisplay) {
12132 DR_state->DisplayFrameTypeInfo(aFrame, aTreeNode.mIndent);
12134 char width[16];
12135 char height[16];
12137 DR_state->PrettyUC(aReflowInput.AvailableWidth(), width, 16);
12138 DR_state->PrettyUC(aReflowInput.AvailableHeight(), height, 16);
12139 printf("Reflow a=%s,%s ", width, height);
12141 DR_state->PrettyUC(aReflowInput.ComputedWidth(), width, 16);
12142 DR_state->PrettyUC(aReflowInput.ComputedHeight(), height, 16);
12143 printf("c=%s,%s ", width, height);
12145 if (aFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY)) printf("dirty ");
12147 if (aFrame->HasAnyStateBits(NS_FRAME_HAS_DIRTY_CHILDREN))
12148 printf("dirty-children ");
12150 if (aReflowInput.mFlags.mSpecialBSizeReflow) printf("special-bsize ");
12152 if (aReflowInput.IsHResize()) printf("h-resize ");
12154 if (aReflowInput.IsVResize()) printf("v-resize ");
12156 nsIFrame* inFlow = aFrame->GetPrevInFlow();
12157 if (inFlow) {
12158 printf("pif=%p ", (void*)inFlow);
12160 inFlow = aFrame->GetNextInFlow();
12161 if (inFlow) {
12162 printf("nif=%p ", (void*)inFlow);
12164 if (aChanged)
12165 printf("CHANGED \n");
12166 else
12167 printf("cnt=%d \n", DR_state->mCount);
12168 if (DR_state->mDisplayPixelErrors) {
12169 int32_t d2a = aPresContext->AppUnitsPerDevPixel();
12170 CheckPixelError(aReflowInput.AvailableWidth(), d2a);
12171 CheckPixelError(aReflowInput.AvailableHeight(), d2a);
12172 CheckPixelError(aReflowInput.ComputedWidth(), d2a);
12173 CheckPixelError(aReflowInput.ComputedHeight(), d2a);
12178 void* nsIFrame::DisplayReflowEnter(nsPresContext* aPresContext,
12179 nsIFrame* aFrame,
12180 const ReflowInput& aReflowInput) {
12181 if (!DR_state->mInited) DR_state->Init();
12182 if (!DR_state->mActive) return nullptr;
12184 NS_ASSERTION(aFrame, "invalid call");
12186 DR_FrameTreeNode* treeNode = DR_state->CreateTreeNode(aFrame, &aReflowInput);
12187 if (treeNode) {
12188 DisplayReflowEnterPrint(aPresContext, aFrame, aReflowInput, *treeNode,
12189 false);
12191 return treeNode;
12194 void* nsIFrame::DisplayLayoutEnter(nsIFrame* aFrame) {
12195 if (!DR_state->mInited) DR_state->Init();
12196 if (!DR_state->mActive) return nullptr;
12198 NS_ASSERTION(aFrame, "invalid call");
12200 DR_FrameTreeNode* treeNode = DR_state->CreateTreeNode(aFrame, nullptr);
12201 if (treeNode && treeNode->mDisplay) {
12202 DR_state->DisplayFrameTypeInfo(aFrame, treeNode->mIndent);
12203 printf("XULLayout\n");
12205 return treeNode;
12208 void* nsIFrame::DisplayIntrinsicISizeEnter(nsIFrame* aFrame,
12209 const char* aType) {
12210 if (!DR_state->mInited) DR_state->Init();
12211 if (!DR_state->mActive) return nullptr;
12213 NS_ASSERTION(aFrame, "invalid call");
12215 DR_FrameTreeNode* treeNode = DR_state->CreateTreeNode(aFrame, nullptr);
12216 if (treeNode && treeNode->mDisplay) {
12217 DR_state->DisplayFrameTypeInfo(aFrame, treeNode->mIndent);
12218 printf("Get%sISize\n", aType);
12220 return treeNode;
12223 void* nsIFrame::DisplayIntrinsicSizeEnter(nsIFrame* aFrame, const char* aType) {
12224 if (!DR_state->mInited) DR_state->Init();
12225 if (!DR_state->mActive) return nullptr;
12227 NS_ASSERTION(aFrame, "invalid call");
12229 DR_FrameTreeNode* treeNode = DR_state->CreateTreeNode(aFrame, nullptr);
12230 if (treeNode && treeNode->mDisplay) {
12231 DR_state->DisplayFrameTypeInfo(aFrame, treeNode->mIndent);
12232 printf("Get%sSize\n", aType);
12234 return treeNode;
12237 void nsIFrame::DisplayReflowExit(nsPresContext* aPresContext, nsIFrame* aFrame,
12238 ReflowOutput& aMetrics,
12239 const nsReflowStatus& aStatus,
12240 void* aFrameTreeNode) {
12241 if (!DR_state->mActive) return;
12243 NS_ASSERTION(aFrame, "DisplayReflowExit - invalid call");
12244 if (!aFrameTreeNode) return;
12246 DR_FrameTreeNode* treeNode = (DR_FrameTreeNode*)aFrameTreeNode;
12247 if (treeNode->mDisplay) {
12248 DR_state->DisplayFrameTypeInfo(aFrame, treeNode->mIndent);
12250 char width[16];
12251 char height[16];
12252 char x[16];
12253 char y[16];
12254 DR_state->PrettyUC(aMetrics.Width(), width, 16);
12255 DR_state->PrettyUC(aMetrics.Height(), height, 16);
12256 printf("Reflow d=%s,%s", width, height);
12258 if (!aStatus.IsEmpty()) {
12259 printf(" status=%s", ToString(aStatus).c_str());
12261 if (aFrame->HasOverflowAreas()) {
12262 DR_state->PrettyUC(aMetrics.InkOverflow().x, x, 16);
12263 DR_state->PrettyUC(aMetrics.InkOverflow().y, y, 16);
12264 DR_state->PrettyUC(aMetrics.InkOverflow().width, width, 16);
12265 DR_state->PrettyUC(aMetrics.InkOverflow().height, height, 16);
12266 printf(" vis-o=(%s,%s) %s x %s", x, y, width, height);
12268 nsRect storedOverflow = aFrame->InkOverflowRect();
12269 DR_state->PrettyUC(storedOverflow.x, x, 16);
12270 DR_state->PrettyUC(storedOverflow.y, y, 16);
12271 DR_state->PrettyUC(storedOverflow.width, width, 16);
12272 DR_state->PrettyUC(storedOverflow.height, height, 16);
12273 printf(" vis-sto=(%s,%s) %s x %s", x, y, width, height);
12275 DR_state->PrettyUC(aMetrics.ScrollableOverflow().x, x, 16);
12276 DR_state->PrettyUC(aMetrics.ScrollableOverflow().y, y, 16);
12277 DR_state->PrettyUC(aMetrics.ScrollableOverflow().width, width, 16);
12278 DR_state->PrettyUC(aMetrics.ScrollableOverflow().height, height, 16);
12279 printf(" scr-o=(%s,%s) %s x %s", x, y, width, height);
12281 storedOverflow = aFrame->ScrollableOverflowRect();
12282 DR_state->PrettyUC(storedOverflow.x, x, 16);
12283 DR_state->PrettyUC(storedOverflow.y, y, 16);
12284 DR_state->PrettyUC(storedOverflow.width, width, 16);
12285 DR_state->PrettyUC(storedOverflow.height, height, 16);
12286 printf(" scr-sto=(%s,%s) %s x %s", x, y, width, height);
12288 printf("\n");
12289 if (DR_state->mDisplayPixelErrors) {
12290 int32_t d2a = aPresContext->AppUnitsPerDevPixel();
12291 CheckPixelError(aMetrics.Width(), d2a);
12292 CheckPixelError(aMetrics.Height(), d2a);
12295 DR_state->DeleteTreeNode(*treeNode);
12298 void nsIFrame::DisplayLayoutExit(nsIFrame* aFrame, void* aFrameTreeNode) {
12299 if (!DR_state->mActive) return;
12301 NS_ASSERTION(aFrame, "non-null frame required");
12302 if (!aFrameTreeNode) return;
12304 DR_FrameTreeNode* treeNode = (DR_FrameTreeNode*)aFrameTreeNode;
12305 if (treeNode->mDisplay) {
12306 DR_state->DisplayFrameTypeInfo(aFrame, treeNode->mIndent);
12307 nsRect rect = aFrame->GetRect();
12308 printf("XULLayout=%d,%d,%d,%d\n", rect.x, rect.y, rect.width, rect.height);
12310 DR_state->DeleteTreeNode(*treeNode);
12313 void nsIFrame::DisplayIntrinsicISizeExit(nsIFrame* aFrame, const char* aType,
12314 nscoord aResult,
12315 void* aFrameTreeNode) {
12316 if (!DR_state->mActive) return;
12318 NS_ASSERTION(aFrame, "non-null frame required");
12319 if (!aFrameTreeNode) return;
12321 DR_FrameTreeNode* treeNode = (DR_FrameTreeNode*)aFrameTreeNode;
12322 if (treeNode->mDisplay) {
12323 DR_state->DisplayFrameTypeInfo(aFrame, treeNode->mIndent);
12324 char iSize[16];
12325 DR_state->PrettyUC(aResult, iSize, 16);
12326 printf("Get%sISize=%s\n", aType, iSize);
12328 DR_state->DeleteTreeNode(*treeNode);
12331 void nsIFrame::DisplayIntrinsicSizeExit(nsIFrame* aFrame, const char* aType,
12332 nsSize aResult, void* aFrameTreeNode) {
12333 if (!DR_state->mActive) return;
12335 NS_ASSERTION(aFrame, "non-null frame required");
12336 if (!aFrameTreeNode) return;
12338 DR_FrameTreeNode* treeNode = (DR_FrameTreeNode*)aFrameTreeNode;
12339 if (treeNode->mDisplay) {
12340 DR_state->DisplayFrameTypeInfo(aFrame, treeNode->mIndent);
12342 char width[16];
12343 char height[16];
12344 DR_state->PrettyUC(aResult.width, width, 16);
12345 DR_state->PrettyUC(aResult.height, height, 16);
12346 printf("Get%sSize=%s,%s\n", aType, width, height);
12348 DR_state->DeleteTreeNode(*treeNode);
12351 /* static */
12352 void nsIFrame::DisplayReflowStartup() { DR_state = new DR_State(); }
12354 /* static */
12355 void nsIFrame::DisplayReflowShutdown() {
12356 delete DR_state;
12357 DR_state = nullptr;
12360 void DR_cookie::Change() const {
12361 DR_FrameTreeNode* treeNode = (DR_FrameTreeNode*)mValue;
12362 if (treeNode && treeNode->mDisplay) {
12363 DisplayReflowEnterPrint(mPresContext, mFrame, mReflowInput, *treeNode,
12364 true);
12368 /* static */
12369 void* ReflowInput::DisplayInitConstraintsEnter(nsIFrame* aFrame,
12370 ReflowInput* aState,
12371 nscoord aContainingBlockWidth,
12372 nscoord aContainingBlockHeight,
12373 const nsMargin* aBorder,
12374 const nsMargin* aPadding) {
12375 MOZ_ASSERT(aFrame, "non-null frame required");
12376 MOZ_ASSERT(aState, "non-null state required");
12378 if (!DR_state->mInited) DR_state->Init();
12379 if (!DR_state->mActive) return nullptr;
12381 DR_FrameTreeNode* treeNode = DR_state->CreateTreeNode(aFrame, aState);
12382 if (treeNode && treeNode->mDisplay) {
12383 DR_state->DisplayFrameTypeInfo(aFrame, treeNode->mIndent);
12385 printf("InitConstraints parent=%p", (void*)aState->mParentReflowInput);
12387 char width[16];
12388 char height[16];
12390 DR_state->PrettyUC(aContainingBlockWidth, width, 16);
12391 DR_state->PrettyUC(aContainingBlockHeight, height, 16);
12392 printf(" cb=%s,%s", width, height);
12394 DR_state->PrettyUC(aState->AvailableWidth(), width, 16);
12395 DR_state->PrettyUC(aState->AvailableHeight(), height, 16);
12396 printf(" as=%s,%s", width, height);
12398 DR_state->PrintMargin("b", aBorder);
12399 DR_state->PrintMargin("p", aPadding);
12400 putchar('\n');
12402 return treeNode;
12405 /* static */
12406 void ReflowInput::DisplayInitConstraintsExit(nsIFrame* aFrame,
12407 ReflowInput* aState,
12408 void* aValue) {
12409 MOZ_ASSERT(aFrame, "non-null frame required");
12410 MOZ_ASSERT(aState, "non-null state required");
12412 if (!DR_state->mActive) return;
12413 if (!aValue) return;
12415 DR_FrameTreeNode* treeNode = (DR_FrameTreeNode*)aValue;
12416 if (treeNode->mDisplay) {
12417 DR_state->DisplayFrameTypeInfo(aFrame, treeNode->mIndent);
12418 char cmiw[16], cw[16], cmxw[16], cmih[16], ch[16], cmxh[16];
12419 DR_state->PrettyUC(aState->ComputedMinWidth(), cmiw, 16);
12420 DR_state->PrettyUC(aState->ComputedWidth(), cw, 16);
12421 DR_state->PrettyUC(aState->ComputedMaxWidth(), cmxw, 16);
12422 DR_state->PrettyUC(aState->ComputedMinHeight(), cmih, 16);
12423 DR_state->PrettyUC(aState->ComputedHeight(), ch, 16);
12424 DR_state->PrettyUC(aState->ComputedMaxHeight(), cmxh, 16);
12425 printf("InitConstraints= cw=(%s <= %s <= %s) ch=(%s <= %s <= %s)", cmiw, cw,
12426 cmxw, cmih, ch, cmxh);
12427 const nsMargin m = aState->ComputedPhysicalOffsets();
12428 DR_state->PrintMargin("co", &m);
12429 putchar('\n');
12431 DR_state->DeleteTreeNode(*treeNode);
12434 /* static */
12435 void* SizeComputationInput::DisplayInitOffsetsEnter(
12436 nsIFrame* aFrame, SizeComputationInput* aState, nscoord aPercentBasis,
12437 WritingMode aCBWritingMode, const nsMargin* aBorder,
12438 const nsMargin* aPadding) {
12439 MOZ_ASSERT(aFrame, "non-null frame required");
12440 MOZ_ASSERT(aState, "non-null state required");
12442 if (!DR_state->mInited) DR_state->Init();
12443 if (!DR_state->mActive) return nullptr;
12445 // aState is not necessarily a ReflowInput
12446 DR_FrameTreeNode* treeNode = DR_state->CreateTreeNode(aFrame, nullptr);
12447 if (treeNode && treeNode->mDisplay) {
12448 DR_state->DisplayFrameTypeInfo(aFrame, treeNode->mIndent);
12450 char pctBasisStr[16];
12451 DR_state->PrettyUC(aPercentBasis, pctBasisStr, 16);
12452 printf("InitOffsets pct_basis=%s", pctBasisStr);
12454 DR_state->PrintMargin("b", aBorder);
12455 DR_state->PrintMargin("p", aPadding);
12456 putchar('\n');
12458 return treeNode;
12461 /* static */
12462 void SizeComputationInput::DisplayInitOffsetsExit(nsIFrame* aFrame,
12463 SizeComputationInput* aState,
12464 void* aValue) {
12465 MOZ_ASSERT(aFrame, "non-null frame required");
12466 MOZ_ASSERT(aState, "non-null state required");
12468 if (!DR_state->mActive) return;
12469 if (!aValue) return;
12471 DR_FrameTreeNode* treeNode = (DR_FrameTreeNode*)aValue;
12472 if (treeNode->mDisplay) {
12473 DR_state->DisplayFrameTypeInfo(aFrame, treeNode->mIndent);
12474 printf("InitOffsets=");
12475 const auto m = aState->ComputedPhysicalMargin();
12476 DR_state->PrintMargin("m", &m);
12477 const auto p = aState->ComputedPhysicalPadding();
12478 DR_state->PrintMargin("p", &p);
12479 const auto bp = aState->ComputedPhysicalBorderPadding();
12480 DR_state->PrintMargin("b+p", &bp);
12481 putchar('\n');
12483 DR_state->DeleteTreeNode(*treeNode);
12486 // End Display Reflow
12488 // Validation of SideIsVertical.
12489 # define CASE(side, result) \
12490 static_assert(SideIsVertical(side) == result, "SideIsVertical is wrong")
12491 CASE(eSideTop, false);
12492 CASE(eSideRight, true);
12493 CASE(eSideBottom, false);
12494 CASE(eSideLeft, true);
12495 # undef CASE
12497 // Validation of HalfCornerIsX.
12498 # define CASE(corner, result) \
12499 static_assert(HalfCornerIsX(corner) == result, "HalfCornerIsX is wrong")
12500 CASE(eCornerTopLeftX, true);
12501 CASE(eCornerTopLeftY, false);
12502 CASE(eCornerTopRightX, true);
12503 CASE(eCornerTopRightY, false);
12504 CASE(eCornerBottomRightX, true);
12505 CASE(eCornerBottomRightY, false);
12506 CASE(eCornerBottomLeftX, true);
12507 CASE(eCornerBottomLeftY, false);
12508 # undef CASE
12510 // Validation of HalfToFullCorner.
12511 # define CASE(corner, result) \
12512 static_assert(HalfToFullCorner(corner) == result, \
12513 "HalfToFullCorner is " \
12514 "wrong")
12515 CASE(eCornerTopLeftX, eCornerTopLeft);
12516 CASE(eCornerTopLeftY, eCornerTopLeft);
12517 CASE(eCornerTopRightX, eCornerTopRight);
12518 CASE(eCornerTopRightY, eCornerTopRight);
12519 CASE(eCornerBottomRightX, eCornerBottomRight);
12520 CASE(eCornerBottomRightY, eCornerBottomRight);
12521 CASE(eCornerBottomLeftX, eCornerBottomLeft);
12522 CASE(eCornerBottomLeftY, eCornerBottomLeft);
12523 # undef CASE
12525 // Validation of FullToHalfCorner.
12526 # define CASE(corner, vert, result) \
12527 static_assert(FullToHalfCorner(corner, vert) == result, \
12528 "FullToHalfCorner is wrong")
12529 CASE(eCornerTopLeft, false, eCornerTopLeftX);
12530 CASE(eCornerTopLeft, true, eCornerTopLeftY);
12531 CASE(eCornerTopRight, false, eCornerTopRightX);
12532 CASE(eCornerTopRight, true, eCornerTopRightY);
12533 CASE(eCornerBottomRight, false, eCornerBottomRightX);
12534 CASE(eCornerBottomRight, true, eCornerBottomRightY);
12535 CASE(eCornerBottomLeft, false, eCornerBottomLeftX);
12536 CASE(eCornerBottomLeft, true, eCornerBottomLeftY);
12537 # undef CASE
12539 // Validation of SideToFullCorner.
12540 # define CASE(side, second, result) \
12541 static_assert(SideToFullCorner(side, second) == result, \
12542 "SideToFullCorner is wrong")
12543 CASE(eSideTop, false, eCornerTopLeft);
12544 CASE(eSideTop, true, eCornerTopRight);
12546 CASE(eSideRight, false, eCornerTopRight);
12547 CASE(eSideRight, true, eCornerBottomRight);
12549 CASE(eSideBottom, false, eCornerBottomRight);
12550 CASE(eSideBottom, true, eCornerBottomLeft);
12552 CASE(eSideLeft, false, eCornerBottomLeft);
12553 CASE(eSideLeft, true, eCornerTopLeft);
12554 # undef CASE
12556 // Validation of SideToHalfCorner.
12557 # define CASE(side, second, parallel, result) \
12558 static_assert(SideToHalfCorner(side, second, parallel) == result, \
12559 "SideToHalfCorner is wrong")
12560 CASE(eSideTop, false, true, eCornerTopLeftX);
12561 CASE(eSideTop, false, false, eCornerTopLeftY);
12562 CASE(eSideTop, true, true, eCornerTopRightX);
12563 CASE(eSideTop, true, false, eCornerTopRightY);
12565 CASE(eSideRight, false, false, eCornerTopRightX);
12566 CASE(eSideRight, false, true, eCornerTopRightY);
12567 CASE(eSideRight, true, false, eCornerBottomRightX);
12568 CASE(eSideRight, true, true, eCornerBottomRightY);
12570 CASE(eSideBottom, false, true, eCornerBottomRightX);
12571 CASE(eSideBottom, false, false, eCornerBottomRightY);
12572 CASE(eSideBottom, true, true, eCornerBottomLeftX);
12573 CASE(eSideBottom, true, false, eCornerBottomLeftY);
12575 CASE(eSideLeft, false, false, eCornerBottomLeftX);
12576 CASE(eSideLeft, false, true, eCornerBottomLeftY);
12577 CASE(eSideLeft, true, false, eCornerTopLeftX);
12578 CASE(eSideLeft, true, true, eCornerTopLeftY);
12579 # undef CASE
12581 #endif