forked from mozilla/gecko-dev
-
Notifications
You must be signed in to change notification settings - Fork 1
/
nsAbsoluteContainingBlock.cpp
886 lines (793 loc) · 38 KB
/
nsAbsoluteContainingBlock.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
* code for managing absolutely positioned children of a rendering
* object that is a containing block for them
*/
#include "nsAbsoluteContainingBlock.h"
#include "nsAtomicContainerFrame.h"
#include "nsContainerFrame.h"
#include "nsGkAtoms.h"
#include "mozilla/CSSAlignUtils.h"
#include "mozilla/PresShell.h"
#include "mozilla/ReflowInput.h"
#include "nsPlaceholderFrame.h"
#include "nsPresContext.h"
#include "nsCSSFrameConstructor.h"
#include "nsGridContainerFrame.h"
#include "mozilla/Sprintf.h"
#ifdef DEBUG
# include "nsBlockFrame.h"
static void PrettyUC(nscoord aSize, char* aBuf, int aBufSize) {
if (NS_UNCONSTRAINEDSIZE == aSize) {
strcpy(aBuf, "UC");
} else {
if ((int32_t)0xdeadbeef == aSize) {
strcpy(aBuf, "deadbeef");
} else {
snprintf(aBuf, aBufSize, "%d", aSize);
}
}
}
#endif
using namespace mozilla;
typedef mozilla::CSSAlignUtils::AlignJustifyFlags AlignJustifyFlags;
void nsAbsoluteContainingBlock::SetInitialChildList(nsIFrame* aDelegatingFrame,
FrameChildListID aListID,
nsFrameList&& aChildList) {
MOZ_ASSERT(mChildListID == aListID, "unexpected child list name");
#ifdef DEBUG
nsIFrame::VerifyDirtyBitSet(aChildList);
for (nsIFrame* f : aChildList) {
MOZ_ASSERT(f->GetParent() == aDelegatingFrame, "Unexpected parent");
}
#endif
mAbsoluteFrames = std::move(aChildList);
}
void nsAbsoluteContainingBlock::AppendFrames(nsIFrame* aDelegatingFrame,
FrameChildListID aListID,
nsFrameList&& aFrameList) {
NS_ASSERTION(mChildListID == aListID, "unexpected child list");
// Append the frames to our list of absolutely positioned frames
#ifdef DEBUG
nsIFrame::VerifyDirtyBitSet(aFrameList);
#endif
mAbsoluteFrames.AppendFrames(nullptr, std::move(aFrameList));
// no damage to intrinsic widths, since absolutely positioned frames can't
// change them
aDelegatingFrame->PresShell()->FrameNeedsReflow(
aDelegatingFrame, IntrinsicDirty::None, NS_FRAME_HAS_DIRTY_CHILDREN);
}
void nsAbsoluteContainingBlock::InsertFrames(nsIFrame* aDelegatingFrame,
FrameChildListID aListID,
nsIFrame* aPrevFrame,
nsFrameList&& aFrameList) {
NS_ASSERTION(mChildListID == aListID, "unexpected child list");
NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == aDelegatingFrame,
"inserting after sibling frame with different parent");
#ifdef DEBUG
nsIFrame::VerifyDirtyBitSet(aFrameList);
#endif
mAbsoluteFrames.InsertFrames(nullptr, aPrevFrame, std::move(aFrameList));
// no damage to intrinsic widths, since absolutely positioned frames can't
// change them
aDelegatingFrame->PresShell()->FrameNeedsReflow(
aDelegatingFrame, IntrinsicDirty::None, NS_FRAME_HAS_DIRTY_CHILDREN);
}
void nsAbsoluteContainingBlock::RemoveFrame(FrameDestroyContext& aContext,
FrameChildListID aListID,
nsIFrame* aOldFrame) {
NS_ASSERTION(mChildListID == aListID, "unexpected child list");
if (nsIFrame* nif = aOldFrame->GetNextInFlow()) {
nif->GetParent()->DeleteNextInFlowChild(aContext, nif, false);
}
mAbsoluteFrames.DestroyFrame(aContext, aOldFrame);
}
static void MaybeMarkAncestorsAsHavingDescendantDependentOnItsStaticPos(
nsIFrame* aFrame, nsIFrame* aContainingBlockFrame) {
MOZ_ASSERT(aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW));
if (!aFrame->StylePosition()->NeedsHypotheticalPositionIfAbsPos()) {
return;
}
// We should have set the bit when reflowing the previous continuations
// already.
if (aFrame->GetPrevContinuation()) {
return;
}
auto* placeholder = aFrame->GetPlaceholderFrame();
MOZ_ASSERT(placeholder);
// Only fixed-pos frames can escape their containing block.
if (!placeholder->HasAnyStateBits(PLACEHOLDER_FOR_FIXEDPOS)) {
return;
}
for (nsIFrame* ancestor = placeholder->GetParent(); ancestor;
ancestor = ancestor->GetParent()) {
// Walk towards the ancestor's first continuation. That's the only one that
// really matters, since it's the only one restyling will look at. We also
// flag the following continuations just so it's caught on the first
// early-return ones just to avoid walking them over and over.
do {
if (ancestor->DescendantMayDependOnItsStaticPosition()) {
return;
}
// Moving the containing block or anything above it would move our static
// position as well, so no need to flag it or any of its ancestors.
if (aFrame == aContainingBlockFrame) {
return;
}
ancestor->SetDescendantMayDependOnItsStaticPosition(true);
nsIFrame* prev = ancestor->GetPrevContinuation();
if (!prev) {
break;
}
ancestor = prev;
} while (true);
}
}
void nsAbsoluteContainingBlock::Reflow(nsContainerFrame* aDelegatingFrame,
nsPresContext* aPresContext,
const ReflowInput& aReflowInput,
nsReflowStatus& aReflowStatus,
const nsRect& aContainingBlock,
AbsPosReflowFlags aFlags,
OverflowAreas* aOverflowAreas) {
// PageContentFrame replicates fixed pos children so we really don't want
// them contributing to overflow areas because that means we'll create new
// pages ad infinitum if one of them overflows the page.
if (aDelegatingFrame->IsPageContentFrame()) {
MOZ_ASSERT(mChildListID == FrameChildListID::Fixed);
aOverflowAreas = nullptr;
}
nsReflowStatus reflowStatus;
const bool reflowAll = aReflowInput.ShouldReflowAllKids();
const bool isGrid = !!(aFlags & AbsPosReflowFlags::IsGridContainerCB);
nsIFrame* kidFrame;
nsOverflowContinuationTracker tracker(aDelegatingFrame, true);
for (kidFrame = mAbsoluteFrames.FirstChild(); kidFrame;
kidFrame = kidFrame->GetNextSibling()) {
bool kidNeedsReflow =
reflowAll || kidFrame->IsSubtreeDirty() ||
FrameDependsOnContainer(
kidFrame, !!(aFlags & AbsPosReflowFlags::CBWidthChanged),
!!(aFlags & AbsPosReflowFlags::CBHeightChanged));
if (kidFrame->IsSubtreeDirty()) {
MaybeMarkAncestorsAsHavingDescendantDependentOnItsStaticPos(
kidFrame, aDelegatingFrame);
}
nscoord availBSize = aReflowInput.AvailableBSize();
const nsRect& cb =
isGrid ? nsGridContainerFrame::GridItemCB(kidFrame) : aContainingBlock;
WritingMode containerWM = aReflowInput.GetWritingMode();
if (!kidNeedsReflow && availBSize != NS_UNCONSTRAINEDSIZE) {
// If we need to redo pagination on the kid, we need to reflow it.
// This can happen either if the available height shrunk and the
// kid (or its overflow that creates overflow containers) is now
// too large to fit in the available height, or if the available
// height has increased and the kid has a next-in-flow that we
// might need to pull from.
WritingMode kidWM = kidFrame->GetWritingMode();
if (containerWM.GetBlockDir() != kidWM.GetBlockDir()) {
// Not sure what the right test would be here.
kidNeedsReflow = true;
} else {
nscoord kidBEnd = kidFrame->GetLogicalRect(cb.Size()).BEnd(kidWM);
nscoord kidOverflowBEnd =
LogicalRect(containerWM,
// Use ...RelativeToSelf to ignore transforms
kidFrame->ScrollableOverflowRectRelativeToSelf() +
kidFrame->GetPosition(),
aContainingBlock.Size())
.BEnd(containerWM);
NS_ASSERTION(kidOverflowBEnd >= kidBEnd,
"overflow area should be at least as large as frame rect");
if (kidOverflowBEnd > availBSize ||
(kidBEnd < availBSize && kidFrame->GetNextInFlow())) {
kidNeedsReflow = true;
}
}
}
if (kidNeedsReflow && !aPresContext->HasPendingInterrupt()) {
// Reflow the frame
nsReflowStatus kidStatus;
ReflowAbsoluteFrame(aDelegatingFrame, aPresContext, aReflowInput, cb,
aFlags, kidFrame, kidStatus, aOverflowAreas);
MOZ_ASSERT(!kidStatus.IsInlineBreakBefore(),
"ShouldAvoidBreakInside should prevent this from happening");
nsIFrame* nextFrame = kidFrame->GetNextInFlow();
if (!kidStatus.IsFullyComplete() &&
aDelegatingFrame->CanContainOverflowContainers()) {
// Need a continuation
if (!nextFrame) {
nextFrame = aPresContext->PresShell()
->FrameConstructor()
->CreateContinuingFrame(kidFrame, aDelegatingFrame);
}
// Add it as an overflow container.
// XXXfr This is a hack to fix some of our printing dataloss.
// See bug 154892. Not sure how to do it "right" yet; probably want
// to keep continuations within an nsAbsoluteContainingBlock eventually.
tracker.Insert(nextFrame, kidStatus);
reflowStatus.MergeCompletionStatusFrom(kidStatus);
} else if (nextFrame) {
// Delete any continuations
nsOverflowContinuationTracker::AutoFinish fini(&tracker, kidFrame);
FrameDestroyContext context(aPresContext->PresShell());
nextFrame->GetParent()->DeleteNextInFlowChild(context, nextFrame, true);
}
} else {
tracker.Skip(kidFrame, reflowStatus);
if (aOverflowAreas) {
aDelegatingFrame->ConsiderChildOverflow(*aOverflowAreas, kidFrame);
}
}
// Make a CheckForInterrupt call, here, not just HasPendingInterrupt. That
// will make sure that we end up reflowing aDelegatingFrame in cases when
// one of our kids interrupted. Otherwise we'd set the dirty or
// dirty-children bit on the kid in the condition below, and then when
// reflow completes and we go to mark dirty bits on all ancestors of that
// kid we'll immediately bail out, because the kid already has a dirty bit.
// In particular, we won't set any dirty bits on aDelegatingFrame, so when
// the following reflow happens we won't reflow the kid in question. This
// might be slightly suboptimal in cases where |kidFrame| itself did not
// interrupt, since we'll trigger a reflow of it too when it's not strictly
// needed. But the logic to not do that is enough more complicated, and
// the case enough of an edge case, that this is probably better.
if (kidNeedsReflow && aPresContext->CheckForInterrupt(aDelegatingFrame)) {
if (aDelegatingFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
kidFrame->MarkSubtreeDirty();
} else {
kidFrame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
}
}
}
// Abspos frames can't cause their parent to be incomplete,
// only overflow incomplete.
if (reflowStatus.IsIncomplete()) {
reflowStatus.SetOverflowIncomplete();
reflowStatus.SetNextInFlowNeedsReflow();
}
aReflowStatus.MergeCompletionStatusFrom(reflowStatus);
}
static inline bool IsFixedPaddingSize(const LengthPercentage& aCoord) {
return aCoord.ConvertsToLength();
}
static inline bool IsFixedMarginSize(const LengthPercentageOrAuto& aCoord) {
return aCoord.ConvertsToLength();
}
static inline bool IsFixedOffset(const LengthPercentageOrAuto& aCoord) {
return aCoord.ConvertsToLength();
}
bool nsAbsoluteContainingBlock::FrameDependsOnContainer(nsIFrame* f,
bool aCBWidthChanged,
bool aCBHeightChanged) {
const nsStylePosition* pos = f->StylePosition();
// See if f's position might have changed because it depends on a
// placeholder's position.
if (pos->NeedsHypotheticalPositionIfAbsPos()) {
return true;
}
if (!aCBWidthChanged && !aCBHeightChanged) {
// skip getting style data
return false;
}
const nsStylePadding* padding = f->StylePadding();
const nsStyleMargin* margin = f->StyleMargin();
WritingMode wm = f->GetWritingMode();
if (wm.IsVertical() ? aCBHeightChanged : aCBWidthChanged) {
// See if f's inline-size might have changed.
// If margin-inline-start/end, padding-inline-start/end,
// inline-size, min/max-inline-size are all lengths, 'none', or enumerated,
// then our frame isize does not depend on the parent isize.
// Note that borders never depend on the parent isize.
// XXX All of the enumerated values except -moz-available are ok too.
if (pos->ISizeDependsOnContainer(wm) ||
pos->MinISizeDependsOnContainer(wm) ||
pos->MaxISizeDependsOnContainer(wm) ||
!IsFixedPaddingSize(padding->mPadding.GetIStart(wm)) ||
!IsFixedPaddingSize(padding->mPadding.GetIEnd(wm))) {
return true;
}
// See if f's position might have changed. If we're RTL then the
// rules are slightly different. We'll assume percentage or auto
// margins will always induce a dependency on the size
if (!IsFixedMarginSize(margin->mMargin.GetIStart(wm)) ||
!IsFixedMarginSize(margin->mMargin.GetIEnd(wm))) {
return true;
}
}
if (wm.IsVertical() ? aCBWidthChanged : aCBHeightChanged) {
// See if f's block-size might have changed.
// If margin-block-start/end, padding-block-start/end,
// min-block-size, and max-block-size are all lengths or 'none',
// and bsize is a length or bsize and bend are auto and bstart is not auto,
// then our frame bsize does not depend on the parent bsize.
// Note that borders never depend on the parent bsize.
//
// FIXME(emilio): Should the BSize(wm).IsAuto() check also for the extremum
// lengths?
if ((pos->BSizeDependsOnContainer(wm) &&
!(pos->BSize(wm).IsAuto() && pos->mOffset.GetBEnd(wm).IsAuto() &&
!pos->mOffset.GetBStart(wm).IsAuto())) ||
pos->MinBSizeDependsOnContainer(wm) ||
pos->MaxBSizeDependsOnContainer(wm) ||
!IsFixedPaddingSize(padding->mPadding.GetBStart(wm)) ||
!IsFixedPaddingSize(padding->mPadding.GetBEnd(wm))) {
return true;
}
// See if f's position might have changed.
if (!IsFixedMarginSize(margin->mMargin.GetBStart(wm)) ||
!IsFixedMarginSize(margin->mMargin.GetBEnd(wm))) {
return true;
}
}
// Since we store coordinates relative to top and left, the position
// of a frame depends on that of its container if it is fixed relative
// to the right or bottom, or if it is positioned using percentages
// relative to the left or top. Because of the dependency on the
// sides (left and top) that we use to store coordinates, these tests
// are easier to do using physical coordinates rather than logical.
if (aCBWidthChanged) {
if (!IsFixedOffset(pos->mOffset.Get(eSideLeft))) {
return true;
}
// Note that even if 'left' is a length, our position can still
// depend on the containing block width, because if our direction or
// writing-mode moves from right to left (in either block or inline
// progression) and 'right' is not 'auto', we will discard 'left'
// and be positioned relative to the containing block right edge.
// 'left' length and 'right' auto is the only combination we can be
// sure of.
if ((wm.GetInlineDir() == WritingMode::eInlineRTL ||
wm.GetBlockDir() == WritingMode::eBlockRL) &&
!pos->mOffset.Get(eSideRight).IsAuto()) {
return true;
}
}
if (aCBHeightChanged) {
if (!IsFixedOffset(pos->mOffset.Get(eSideTop))) {
return true;
}
// See comment above for width changes.
if (wm.GetInlineDir() == WritingMode::eInlineBTT &&
!pos->mOffset.Get(eSideBottom).IsAuto()) {
return true;
}
}
return false;
}
void nsAbsoluteContainingBlock::DestroyFrames(DestroyContext& aContext) {
mAbsoluteFrames.DestroyFrames(aContext);
}
void nsAbsoluteContainingBlock::MarkSizeDependentFramesDirty() {
DoMarkFramesDirty(false);
}
void nsAbsoluteContainingBlock::MarkAllFramesDirty() {
DoMarkFramesDirty(true);
}
void nsAbsoluteContainingBlock::DoMarkFramesDirty(bool aMarkAllDirty) {
for (nsIFrame* kidFrame : mAbsoluteFrames) {
if (aMarkAllDirty) {
kidFrame->MarkSubtreeDirty();
} else if (FrameDependsOnContainer(kidFrame, true, true)) {
// Add the weakest flags that will make sure we reflow this frame later
kidFrame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
}
}
}
// Given an out-of-flow frame, this method returns the parent frame of its
// placeholder frame or null if it doesn't have a placeholder for some reason.
static nsContainerFrame* GetPlaceholderContainer(nsIFrame* aPositionedFrame) {
nsIFrame* placeholder = aPositionedFrame->GetPlaceholderFrame();
return placeholder ? placeholder->GetParent() : nullptr;
}
/**
* This function returns the offset of an abs/fixed-pos child's static
* position, with respect to the "start" corner of its alignment container,
* according to CSS Box Alignment. This function only operates in a single
* axis at a time -- callers can choose which axis via the |aAbsPosCBAxis|
* parameter.
*
* @param aKidReflowInput The ReflowInput for the to-be-aligned abspos child.
* @param aKidSizeInAbsPosCBWM The child frame's size (after it's been given
* the opportunity to reflow), in terms of
* aAbsPosCBWM.
* @param aAbsPosCBSize The abspos CB size, in terms of aAbsPosCBWM.
* @param aPlaceholderContainer The parent of the child frame's corresponding
* placeholder frame, cast to a nsContainerFrame.
* (This will help us choose which alignment enum
* we should use for the child.)
* @param aAbsPosCBWM The child frame's containing block's WritingMode.
* @param aAbsPosCBAxis The axis (of the containing block) that we should
* be doing this computation for.
*/
static nscoord OffsetToAlignedStaticPos(const ReflowInput& aKidReflowInput,
const LogicalSize& aKidSizeInAbsPosCBWM,
const LogicalSize& aAbsPosCBSize,
nsContainerFrame* aPlaceholderContainer,
WritingMode aAbsPosCBWM,
LogicalAxis aAbsPosCBAxis) {
if (!aPlaceholderContainer) {
// (The placeholder container should be the thing that kicks this whole
// process off, by setting PLACEHOLDER_STATICPOS_NEEDS_CSSALIGN. So it
// should exist... but bail gracefully if it doesn't.)
NS_ERROR(
"Missing placeholder-container when computing a "
"CSS Box Alignment static position");
return 0;
}
// (Most of this function is simply preparing args that we'll pass to
// AlignJustifySelf at the end.)
// NOTE: Our alignment container is aPlaceholderContainer's content-box
// (or an area within it, if aPlaceholderContainer is a grid). So, we'll
// perform most of our arithmetic/alignment in aPlaceholderContainer's
// WritingMode. For brevity, we use the abbreviation "pc" for "placeholder
// container" in variables below.
WritingMode pcWM = aPlaceholderContainer->GetWritingMode();
// Find what axis aAbsPosCBAxis corresponds to, in placeholder's parent's
// writing-mode.
LogicalAxis pcAxis =
(pcWM.IsOrthogonalTo(aAbsPosCBWM) ? GetOrthogonalAxis(aAbsPosCBAxis)
: aAbsPosCBAxis);
const bool placeholderContainerIsContainingBlock =
aPlaceholderContainer == aKidReflowInput.mCBReflowInput->mFrame;
LayoutFrameType parentType = aPlaceholderContainer->Type();
LogicalSize alignAreaSize(pcWM);
if (parentType == LayoutFrameType::FlexContainer) {
// We store the frame rect in FinishAndStoreOverflow, which runs _after_
// reflowing the absolute frames, so handle the special case of the frame
// being the actual containing block here, by getting the size from
// aAbsPosCBSize.
//
// The alignment container is the flex container's content box.
if (placeholderContainerIsContainingBlock) {
alignAreaSize = aAbsPosCBSize.ConvertTo(pcWM, aAbsPosCBWM);
// aAbsPosCBSize is the padding-box, so substract the padding to get the
// content box.
alignAreaSize -=
aPlaceholderContainer->GetLogicalUsedPadding(pcWM).Size(pcWM);
} else {
alignAreaSize = aPlaceholderContainer->GetLogicalSize(pcWM);
LogicalMargin pcBorderPadding =
aPlaceholderContainer->GetLogicalUsedBorderAndPadding(pcWM);
alignAreaSize -= pcBorderPadding.Size(pcWM);
}
} else if (parentType == LayoutFrameType::GridContainer) {
// This abspos elem's parent is a grid container. Per CSS Grid 10.1 & 10.2:
// - If the grid container *also* generates the abspos containing block (a
// grid area) for this abspos child, we use that abspos containing block as
// the alignment container, too. (And its size is aAbsPosCBSize.)
// - Otherwise, we use the grid's padding box as the alignment container.
// https://drafts.csswg.org/css-grid/#static-position
if (placeholderContainerIsContainingBlock) {
// The alignment container is the grid area that we're using as the
// absolute containing block.
alignAreaSize = aAbsPosCBSize.ConvertTo(pcWM, aAbsPosCBWM);
} else {
// The alignment container is a the grid container's content box (which
// we can get by subtracting away its border & padding from frame's size):
alignAreaSize = aPlaceholderContainer->GetLogicalSize(pcWM);
LogicalMargin pcBorderPadding =
aPlaceholderContainer->GetLogicalUsedBorderAndPadding(pcWM);
alignAreaSize -= pcBorderPadding.Size(pcWM);
}
} else {
NS_ERROR("Unsupported container for abpsos CSS Box Alignment");
return 0; // (leave the child at the start of its alignment container)
}
nscoord alignAreaSizeInAxis = (pcAxis == eLogicalAxisInline)
? alignAreaSize.ISize(pcWM)
: alignAreaSize.BSize(pcWM);
AlignJustifyFlags flags = AlignJustifyFlags::IgnoreAutoMargins;
StyleAlignFlags alignConst =
aPlaceholderContainer->CSSAlignmentForAbsPosChild(aKidReflowInput,
pcAxis);
// If the safe bit in alignConst is set, set the safe flag in |flags|.
// Note: If no <overflow-position> is specified, we behave as 'unsafe'.
// This doesn't quite match the css-align spec, which has an [at-risk]
// "smart default" behavior with some extra nuance about scroll containers.
if (alignConst & StyleAlignFlags::SAFE) {
flags |= AlignJustifyFlags::OverflowSafe;
}
alignConst &= ~StyleAlignFlags::FLAG_BITS;
// Find out if placeholder-container & the OOF child have the same start-sides
// in the placeholder-container's pcAxis.
WritingMode kidWM = aKidReflowInput.GetWritingMode();
if (pcWM.ParallelAxisStartsOnSameSide(pcAxis, kidWM)) {
flags |= AlignJustifyFlags::SameSide;
}
// (baselineAdjust is unused. CSSAlignmentForAbsPosChild() should've
// converted 'baseline'/'last baseline' enums to their fallback values.)
const nscoord baselineAdjust = nscoord(0);
// AlignJustifySelf operates in the kid's writing mode, so we need to
// represent the child's size and the desired axis in that writing mode:
LogicalSize kidSizeInOwnWM =
aKidSizeInAbsPosCBWM.ConvertTo(kidWM, aAbsPosCBWM);
LogicalAxis kidAxis =
(kidWM.IsOrthogonalTo(aAbsPosCBWM) ? GetOrthogonalAxis(aAbsPosCBAxis)
: aAbsPosCBAxis);
nscoord offset = CSSAlignUtils::AlignJustifySelf(
alignConst, kidAxis, flags, baselineAdjust, alignAreaSizeInAxis,
aKidReflowInput, kidSizeInOwnWM);
// "offset" is in terms of the CSS Box Alignment container (i.e. it's in
// terms of pcWM). But our return value needs to in terms of the containing
// block's writing mode, which might have the opposite directionality in the
// given axis. In that case, we just need to negate "offset" when returning,
// to make it have the right effect as an offset for coordinates in the
// containing block's writing mode.
if (!pcWM.ParallelAxisStartsOnSameSide(pcAxis, aAbsPosCBWM)) {
return -offset;
}
return offset;
}
void nsAbsoluteContainingBlock::ResolveSizeDependentOffsets(
nsPresContext* aPresContext, ReflowInput& aKidReflowInput,
const LogicalSize& aKidSize, const LogicalMargin& aMargin,
LogicalMargin* aOffsets, LogicalSize* aLogicalCBSize) {
WritingMode wm = aKidReflowInput.GetWritingMode();
WritingMode outerWM = aKidReflowInput.mParentReflowInput->GetWritingMode();
// Now that we know the child's size, we resolve any sentinel values in its
// IStart/BStart offset coordinates that depend on that size.
// * NS_AUTOOFFSET indicates that the child's position in the given axis
// is determined by its end-wards offset property, combined with its size and
// available space. e.g.: "top: auto; height: auto; bottom: 50px"
// * m{I,B}OffsetsResolvedAfterSize indicate that the child is using its
// static position in that axis, *and* its static position is determined by
// the axis-appropriate css-align property (which may require the child's
// size, e.g. to center it within the parent).
if ((NS_AUTOOFFSET == aOffsets->IStart(outerWM)) ||
(NS_AUTOOFFSET == aOffsets->BStart(outerWM)) ||
aKidReflowInput.mFlags.mIOffsetsNeedCSSAlign ||
aKidReflowInput.mFlags.mBOffsetsNeedCSSAlign) {
if (-1 == aLogicalCBSize->ISize(wm)) {
// Get the containing block width/height
const ReflowInput* parentRI = aKidReflowInput.mParentReflowInput;
*aLogicalCBSize = aKidReflowInput.ComputeContainingBlockRectangle(
aPresContext, parentRI);
}
const LogicalSize logicalCBSizeOuterWM =
aLogicalCBSize->ConvertTo(outerWM, wm);
// placeholderContainer is used in each of the m{I,B}OffsetsNeedCSSAlign
// clauses. We declare it at this scope so we can avoid having to look
// it up twice (and only look it up if it's needed).
nsContainerFrame* placeholderContainer = nullptr;
if (NS_AUTOOFFSET == aOffsets->IStart(outerWM)) {
NS_ASSERTION(NS_AUTOOFFSET != aOffsets->IEnd(outerWM),
"Can't solve for both start and end");
aOffsets->IStart(outerWM) =
logicalCBSizeOuterWM.ISize(outerWM) - aOffsets->IEnd(outerWM) -
aMargin.IStartEnd(outerWM) - aKidSize.ISize(outerWM);
} else if (aKidReflowInput.mFlags.mIOffsetsNeedCSSAlign) {
placeholderContainer = GetPlaceholderContainer(aKidReflowInput.mFrame);
nscoord offset = OffsetToAlignedStaticPos(
aKidReflowInput, aKidSize, logicalCBSizeOuterWM, placeholderContainer,
outerWM, eLogicalAxisInline);
// Shift IStart from its current position (at start corner of the
// alignment container) by the returned offset. And set IEnd to the
// distance between the kid's end edge to containing block's end edge.
aOffsets->IStart(outerWM) += offset;
aOffsets->IEnd(outerWM) =
logicalCBSizeOuterWM.ISize(outerWM) -
(aOffsets->IStart(outerWM) + aKidSize.ISize(outerWM));
}
if (NS_AUTOOFFSET == aOffsets->BStart(outerWM)) {
aOffsets->BStart(outerWM) =
logicalCBSizeOuterWM.BSize(outerWM) - aOffsets->BEnd(outerWM) -
aMargin.BStartEnd(outerWM) - aKidSize.BSize(outerWM);
} else if (aKidReflowInput.mFlags.mBOffsetsNeedCSSAlign) {
if (!placeholderContainer) {
placeholderContainer = GetPlaceholderContainer(aKidReflowInput.mFrame);
}
nscoord offset = OffsetToAlignedStaticPos(
aKidReflowInput, aKidSize, logicalCBSizeOuterWM, placeholderContainer,
outerWM, eLogicalAxisBlock);
// Shift BStart from its current position (at start corner of the
// alignment container) by the returned offset. And set BEnd to the
// distance between the kid's end edge to containing block's end edge.
aOffsets->BStart(outerWM) += offset;
aOffsets->BEnd(outerWM) =
logicalCBSizeOuterWM.BSize(outerWM) -
(aOffsets->BStart(outerWM) + aKidSize.BSize(outerWM));
}
aKidReflowInput.SetComputedLogicalOffsets(outerWM, *aOffsets);
}
}
void nsAbsoluteContainingBlock::ResolveAutoMarginsAfterLayout(
ReflowInput& aKidReflowInput, const LogicalSize* aLogicalCBSize,
const LogicalSize& aKidSize, LogicalMargin& aMargin,
LogicalMargin& aOffsets) {
MOZ_ASSERT(aKidReflowInput.mFrame->HasIntrinsicKeywordForBSize());
WritingMode wm = aKidReflowInput.GetWritingMode();
WritingMode outerWM = aKidReflowInput.mParentReflowInput->GetWritingMode();
const LogicalSize kidSizeInWM = aKidSize.ConvertTo(wm, outerWM);
LogicalMargin marginInWM = aMargin.ConvertTo(wm, outerWM);
LogicalMargin offsetsInWM = aOffsets.ConvertTo(wm, outerWM);
// No need to substract border sizes because aKidSize has it included
// already. Also, if any offset is auto, the auto margin resolves to zero.
// https://drafts.csswg.org/css-position-3/#abspos-margins
const bool autoOffset = offsetsInWM.BEnd(wm) == NS_AUTOOFFSET ||
offsetsInWM.BStart(wm) == NS_AUTOOFFSET;
nscoord availMarginSpace =
autoOffset ? 0
: aLogicalCBSize->BSize(wm) - kidSizeInWM.BSize(wm) -
offsetsInWM.BStartEnd(wm) - marginInWM.BStartEnd(wm);
const auto& styleMargin = aKidReflowInput.mStyleMargin;
if (wm.IsOrthogonalTo(outerWM)) {
ReflowInput::ComputeAbsPosInlineAutoMargin(
availMarginSpace, outerWM,
styleMargin->mMargin.GetIStart(outerWM).IsAuto(),
styleMargin->mMargin.GetIEnd(outerWM).IsAuto(), aMargin, aOffsets);
} else {
ReflowInput::ComputeAbsPosBlockAutoMargin(
availMarginSpace, outerWM,
styleMargin->mMargin.GetBStart(outerWM).IsAuto(),
styleMargin->mMargin.GetBEnd(outerWM).IsAuto(), aMargin, aOffsets);
}
aKidReflowInput.SetComputedLogicalMargin(outerWM, aMargin);
aKidReflowInput.SetComputedLogicalOffsets(outerWM, aOffsets);
nsMargin* propValue =
aKidReflowInput.mFrame->GetProperty(nsIFrame::UsedMarginProperty());
// InitOffsets should've created a UsedMarginProperty for us, if any margin is
// auto.
MOZ_ASSERT_IF(styleMargin->HasInlineAxisAuto(outerWM) ||
styleMargin->HasBlockAxisAuto(outerWM),
propValue);
if (propValue) {
*propValue = aMargin.GetPhysicalMargin(outerWM);
}
}
// XXX Optimize the case where it's a resize reflow and the absolutely
// positioned child has the exact same size and position and skip the
// reflow...
// When bug 154892 is checked in, make sure that when
// mChildListID == FrameChildListID::Fixed, the height is unconstrained.
// since we don't allow replicated frames to split.
void nsAbsoluteContainingBlock::ReflowAbsoluteFrame(
nsIFrame* aDelegatingFrame, nsPresContext* aPresContext,
const ReflowInput& aReflowInput, const nsRect& aContainingBlock,
AbsPosReflowFlags aFlags, nsIFrame* aKidFrame, nsReflowStatus& aStatus,
OverflowAreas* aOverflowAreas) {
MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
#ifdef DEBUG
if (nsBlockFrame::gNoisyReflow) {
nsIFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent);
printf("abs pos ");
nsAutoString name;
aKidFrame->GetFrameName(name);
printf("%s ", NS_LossyConvertUTF16toASCII(name).get());
char width[16];
char height[16];
PrettyUC(aReflowInput.AvailableWidth(), width, 16);
PrettyUC(aReflowInput.AvailableHeight(), height, 16);
printf(" a=%s,%s ", width, height);
PrettyUC(aReflowInput.ComputedWidth(), width, 16);
PrettyUC(aReflowInput.ComputedHeight(), height, 16);
printf("c=%s,%s \n", width, height);
}
AutoNoisyIndenter indent(nsBlockFrame::gNoisy);
#endif // DEBUG
WritingMode wm = aKidFrame->GetWritingMode();
LogicalSize logicalCBSize(wm, aContainingBlock.Size());
nscoord availISize = logicalCBSize.ISize(wm);
if (availISize == -1) {
NS_ASSERTION(
aReflowInput.ComputedSize(wm).ISize(wm) != NS_UNCONSTRAINEDSIZE,
"Must have a useful inline-size _somewhere_");
availISize = aReflowInput.ComputedSizeWithPadding(wm).ISize(wm);
}
ReflowInput::InitFlags initFlags;
if (aFlags & AbsPosReflowFlags::IsGridContainerCB) {
// When a grid container generates the abs.pos. CB for a *child* then
// the static position is determined via CSS Box Alignment within the
// abs.pos. CB (a grid area, i.e. a piece of the grid). In this scenario,
// due to the multiple coordinate spaces in play, we use a convenience flag
// to simply have the child's ReflowInput give it a static position at its
// abs.pos. CB origin, and then we'll align & offset it from there.
nsIFrame* placeholder = aKidFrame->GetPlaceholderFrame();
if (placeholder && placeholder->GetParent() == aDelegatingFrame) {
initFlags += ReflowInput::InitFlag::StaticPosIsCBOrigin;
}
}
bool constrainBSize =
(aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE) &&
// Don't split if told not to (e.g. for fixed frames)
(aFlags & AbsPosReflowFlags::ConstrainHeight) &&
// XXX we don't handle splitting frames for inline absolute containing
// blocks yet
!aDelegatingFrame->IsInlineFrame() &&
// Bug 1588623: Support splitting absolute positioned multicol containers.
!aKidFrame->IsColumnSetWrapperFrame() &&
// Don't split things below the fold. (Ideally we shouldn't *have*
// anything totally below the fold, but we can't position frames
// across next-in-flow breaks yet.
(aKidFrame->GetLogicalRect(aContainingBlock.Size()).BStart(wm) <=
aReflowInput.AvailableBSize());
// Get the border values
const WritingMode outerWM = aReflowInput.GetWritingMode();
const LogicalMargin border = aDelegatingFrame->GetLogicalUsedBorder(outerWM);
const nscoord availBSize = constrainBSize
? aReflowInput.AvailableBSize() -
border.ConvertTo(wm, outerWM).BStart(wm)
: NS_UNCONSTRAINEDSIZE;
ReflowInput kidReflowInput(aPresContext, aReflowInput, aKidFrame,
LogicalSize(wm, availISize, availBSize),
Some(logicalCBSize), initFlags);
if (nscoord kidAvailBSize = kidReflowInput.AvailableBSize();
kidAvailBSize != NS_UNCONSTRAINEDSIZE) {
// Shrink available block-size if it's constrained.
kidAvailBSize -= kidReflowInput.ComputedLogicalMargin(wm).BStart(wm);
const nscoord kidOffsetBStart =
kidReflowInput.ComputedLogicalOffsets(wm).BStart(wm);
if (NS_AUTOOFFSET != kidOffsetBStart) {
kidAvailBSize -= kidOffsetBStart;
}
kidReflowInput.SetAvailableBSize(kidAvailBSize);
}
// Do the reflow
ReflowOutput kidDesiredSize(kidReflowInput);
aKidFrame->Reflow(aPresContext, kidDesiredSize, kidReflowInput, aStatus);
// Position the child relative to our padding edge. Don't do this for popups,
// which handle their own positioning.
if (!aKidFrame->IsMenuPopupFrame()) {
const LogicalSize kidSize = kidDesiredSize.Size(outerWM);
LogicalMargin offsets = kidReflowInput.ComputedLogicalOffsets(outerWM);
LogicalMargin margin = kidReflowInput.ComputedLogicalMargin(outerWM);
// If we're doing CSS Box Alignment in either axis, that will apply the
// margin for us in that axis (since the thing that's aligned is the margin
// box). So, we clear out the margin here to avoid applying it twice.
if (kidReflowInput.mFlags.mIOffsetsNeedCSSAlign) {
margin.IStart(outerWM) = margin.IEnd(outerWM) = 0;
}
if (kidReflowInput.mFlags.mBOffsetsNeedCSSAlign) {
margin.BStart(outerWM) = margin.BEnd(outerWM) = 0;
}
// If we're solving for start in either inline or block direction,
// then compute it now that we know the dimensions.
ResolveSizeDependentOffsets(aPresContext, kidReflowInput, kidSize, margin,
&offsets, &logicalCBSize);
if (kidReflowInput.mFrame->HasIntrinsicKeywordForBSize()) {
ResolveAutoMarginsAfterLayout(kidReflowInput, &logicalCBSize, kidSize,
margin, offsets);
}
LogicalRect rect(outerWM,
border.StartOffset(outerWM) +
offsets.StartOffset(outerWM) +
margin.StartOffset(outerWM),
kidSize);
nsRect r = rect.GetPhysicalRect(
outerWM, logicalCBSize.GetPhysicalSize(wm) +
border.Size(outerWM).GetPhysicalSize(outerWM));
// Offset the frame rect by the given origin of the absolute containing
// block.
r.x += aContainingBlock.x;
r.y += aContainingBlock.y;
aKidFrame->SetRect(r);
nsView* view = aKidFrame->GetView();
if (view) {
// Size and position the view and set its opacity, visibility, content
// transparency, and clip
nsContainerFrame::SyncFrameViewAfterReflow(aPresContext, aKidFrame, view,
kidDesiredSize.InkOverflow());
} else {
nsContainerFrame::PositionChildViews(aKidFrame);
}
}
aKidFrame->DidReflow(aPresContext, &kidReflowInput);
const nsRect r = aKidFrame->GetRect();
#ifdef DEBUG
if (nsBlockFrame::gNoisyReflow) {
nsIFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent - 1);
printf("abs pos ");
nsAutoString name;
aKidFrame->GetFrameName(name);
printf("%s ", NS_LossyConvertUTF16toASCII(name).get());
printf("%p rect=%d,%d,%d,%d\n", static_cast<void*>(aKidFrame), r.x, r.y,
r.width, r.height);
}
#endif
if (aOverflowAreas) {
aOverflowAreas->UnionWith(kidDesiredSize.mOverflowAreas + r.TopLeft());
}
}