/
ng_box_fragment_painter.cc
2632 lines (2345 loc) · 106 KB
/
ng_box_fragment_painter.cc
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
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.h"
#include "base/containers/adapters.h"
#include "third_party/blink/renderer/core/css/style_engine.h"
#include "third_party/blink/renderer/core/editing/drag_caret.h"
#include "third_party/blink/renderer/core/editing/frame_selection.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/layout/background_bleed_avoidance.h"
#include "third_party/blink/renderer/core/layout/hit_test_location.h"
#include "third_party/blink/renderer/core/layout/hit_test_result.h"
#include "third_party/blink/renderer/core/layout/layout_inline.h"
#include "third_party/blink/renderer/core/layout/layout_table_cell.h"
#include "third_party/blink/renderer/core/layout/layout_view.h"
#include "third_party/blink/renderer/core/layout/ng/geometry/ng_box_strut.h"
#include "third_party/blink/renderer/core/layout/ng/inline/layout_ng_text_combine.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/layout_ng_mixin.h"
#include "third_party/blink/renderer/core/layout/ng/ng_block_break_token.h"
#include "third_party/blink/renderer/core/layout/ng/ng_outline_utils.h"
#include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h"
#include "third_party/blink/renderer/core/layout/pointer_events_hit_rules.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/paint/background_image_geometry.h"
#include "third_party/blink/renderer/core/paint/box_border_painter.h"
#include "third_party/blink/renderer/core/paint/box_decoration_data.h"
#include "third_party/blink/renderer/core/paint/box_painter.h"
#include "third_party/blink/renderer/core/paint/ng/ng_fieldset_painter.h"
#include "third_party/blink/renderer/core/paint/ng/ng_fragment_painter.h"
#include "third_party/blink/renderer/core/paint/ng/ng_inline_box_fragment_painter.h"
#include "third_party/blink/renderer/core/paint/ng/ng_mathml_painter.h"
#include "third_party/blink/renderer/core/paint/ng/ng_table_painters.h"
#include "third_party/blink/renderer/core/paint/ng/ng_text_combine_painter.h"
#include "third_party/blink/renderer/core/paint/ng/ng_text_fragment_painter.h"
#include "third_party/blink/renderer/core/paint/object_painter.h"
#include "third_party/blink/renderer/core/paint/paint_auto_dark_mode.h"
#include "third_party/blink/renderer/core/paint/paint_info.h"
#include "third_party/blink/renderer/core/paint/paint_layer.h"
#include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
#include "third_party/blink/renderer/core/paint/paint_phase.h"
#include "third_party/blink/renderer/core/paint/paint_timing_detector.h"
#include "third_party/blink/renderer/core/paint/rounded_border_geometry.h"
#include "third_party/blink/renderer/core/paint/scoped_paint_state.h"
#include "third_party/blink/renderer/core/paint/scoped_svg_paint_state.h"
#include "third_party/blink/renderer/core/paint/scrollable_area_painter.h"
#include "third_party/blink/renderer/core/paint/theme_painter.h"
#include "third_party/blink/renderer/core/paint/url_metadata_utils.h"
#include "third_party/blink/renderer/core/paint/view_painter.h"
#include "third_party/blink/renderer/core/scroll/scroll_types.h"
#include "third_party/blink/renderer/platform/geometry/layout_rect_outsets.h"
#include "third_party/blink/renderer/platform/graphics/graphics_context_state_saver.h"
#include "third_party/blink/renderer/platform/graphics/paint/display_item_cache_skipper.h"
#include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h"
#include "third_party/blink/renderer/platform/graphics/paint/scoped_display_item_fragment.h"
namespace blink {
namespace {
inline bool HasSelection(const LayoutObject* layout_object) {
return layout_object->GetSelectionState() != SelectionState::kNone;
}
inline bool IsVisibleToPaint(const NGPhysicalFragment& fragment,
const ComputedStyle& style) {
if (fragment.IsHiddenForPaint())
return false;
if (style.Visibility() != EVisibility::kVisible) {
auto display = style.Display();
// Hidden section/row backgrounds still paint into cells.
if (display != EDisplay::kTableRowGroup && display != EDisplay::kTableRow &&
display != EDisplay::kTableColumn &&
display != EDisplay::kTableColumnGroup) {
return false;
}
}
// When |NGLineTruncator| sets |IsHiddenForPaint|, it sets to the fragment in
// the line. However, when it has self-painting layer, the fragment stored in
// |LayoutBlockFlow| will be painted. Check |IsHiddenForPaint| of the fragment
// in the inline formatting context.
if (UNLIKELY(fragment.IsAtomicInline() && fragment.HasSelfPaintingLayer())) {
const LayoutObject* layout_object = fragment.GetLayoutObject();
if (layout_object->IsInLayoutNGInlineFormattingContext()) {
NGInlineCursor cursor;
cursor.MoveTo(*layout_object);
if (cursor && cursor.Current().IsHiddenForPaint())
return false;
}
}
return true;
}
inline bool IsVisibleToPaint(const NGFragmentItem& item,
const ComputedStyle& style) {
return !item.IsHiddenForPaint() &&
style.Visibility() == EVisibility::kVisible;
}
inline bool IsVisibleToHitTest(const ComputedStyle& style,
const HitTestRequest& request) {
return request.IgnorePointerEventsNone() ||
style.UsedPointerEvents() != EPointerEvents::kNone;
}
inline bool IsVisibleToHitTest(const NGFragmentItem& item,
const HitTestRequest& request) {
const ComputedStyle& style = item.Style();
if (item.Type() != NGFragmentItem::kSvgText)
return IsVisibleToPaint(item, style) && IsVisibleToHitTest(style, request);
if (item.IsHiddenForPaint())
return false;
PointerEventsHitRules hit_rules(PointerEventsHitRules::kSvgTextHitTesting,
request, style.UsedPointerEvents());
if (hit_rules.require_visible && style.Visibility() != EVisibility::kVisible)
return false;
if (hit_rules.can_hit_bounding_box ||
(hit_rules.can_hit_stroke &&
(style.HasStroke() || !hit_rules.require_stroke)) ||
(hit_rules.can_hit_fill && (style.HasFill() || !hit_rules.require_fill)))
return IsVisibleToHitTest(style, request);
return false;
}
inline bool IsVisibleToHitTest(const NGPhysicalFragment& fragment,
const HitTestRequest& request) {
const ComputedStyle& style = fragment.Style();
return IsVisibleToPaint(fragment, style) &&
IsVisibleToHitTest(style, request);
}
// Hit tests inline ancestor elements of |fragment| who do not have their own
// box fragments.
// @param physical_offset Physical offset of |fragment| in the paint layer.
bool HitTestCulledInlineAncestors(
HitTestResult& result,
const NGInlineCursor& parent_cursor,
const LayoutObject* current,
const LayoutObject* limit,
const NGInlineCursorPosition& previous_sibling,
const HitTestLocation& hit_test_location,
const PhysicalOffset fallback_accumulated_offset) {
DCHECK(current != limit && current->IsDescendantOf(limit));
// Check ancestors only when |current| is the first fragment in this line.
if (previous_sibling && current == previous_sibling.GetLayoutObject())
return false;
for (LayoutObject* parent = current->Parent(); parent && parent != limit;
current = parent, parent = parent->Parent()) {
// |culled_parent| is a culled inline element to be hit tested, since it's
// "between" |fragment| and |fragment->Parent()| but doesn't have its own
// box fragment.
// To ensure the correct hit test ordering, |culled_parent| must be hit
// tested only once after all of its descendants are hit tested:
// - Shortcut: when |current_layout_object| is the only child (of
// |culled_parent|), since it's just hit tested, we can safely hit test its
// parent;
// - General case: we hit test |culled_parent| only when it is not an
// ancestor of |previous_sibling|; otherwise, |previous_sibling| has to be
// hit tested first.
// TODO(crbug.com/849331): It's wrong for bidi inline fragmentation. Fix it.
const bool has_sibling =
current->PreviousSibling() || current->NextSibling();
if (has_sibling && previous_sibling &&
previous_sibling.GetLayoutObject()->IsDescendantOf(parent))
break;
if (auto* parent_layout_inline = DynamicTo<LayoutInline>(parent)) {
if (parent_layout_inline->HitTestCulledInline(result, hit_test_location,
fallback_accumulated_offset,
&parent_cursor))
return true;
}
}
return false;
}
bool HitTestCulledInlineAncestors(
HitTestResult& result,
const NGPhysicalBoxFragment& container,
const NGInlineCursor& parent_cursor,
const NGFragmentItem& item,
const NGInlineCursorPosition& previous_sibling,
const HitTestLocation& hit_test_location,
const PhysicalOffset& physical_offset) {
// Ellipsis can appear under a different parent from the ellipsized object
// that it can confuse culled inline logic.
if (UNLIKELY(item.IsEllipsis()))
return false;
// To be passed as |accumulated_offset| to LayoutInline::HitTestCulledInline,
// where it equals the physical offset of the containing block in paint layer.
const PhysicalOffset fallback_accumulated_offset =
physical_offset - item.OffsetInContainerFragment();
return HitTestCulledInlineAncestors(
result, parent_cursor, item.GetLayoutObject(),
// Limit the traversal up to the container fragment, or its container if
// the fragment is not a CSSBox.
container.GetSelfOrContainerLayoutObject(), previous_sibling,
hit_test_location, fallback_accumulated_offset);
}
// Returns if this fragment may not be laid out by LayoutNG.
//
// This function is for an optimization to skip a few virtual
// calls. When this is |false|, we know |LayoutObject::Paint()| calls
// |NGBoxFragmentPainter|, and that we can instantiate a child
// |NGBoxFragmentPainter| directly. All code should work without this.
//
// TODO(kojii): This may become more complicated when we use
// |NGBoxFragmentPainter| for all fragments, and we still want this
// oprimization.
bool FragmentRequiresLegacyFallback(const NGPhysicalFragment& fragment) {
// If |fragment| is |IsFormattingContextRoot|, it may be legacy.
// Avoid falling back to |LayoutObject| if |CanTraverse|, because it
// cannot handle block fragmented objects.
if (!fragment.IsFormattingContextRoot() || fragment.CanTraverse())
return false;
DCHECK(!To<NGPhysicalBoxFragment>(&fragment)->BreakToken());
return true;
}
// Returns a vector of backplates that surround the paragraphs of text within
// line_boxes.
//
// This function traverses descendants of an inline formatting context in
// pre-order DFS and build up backplates behind inline text boxes, each split at
// the paragraph level. Store the results in paragraph_backplates.
Vector<PhysicalRect> BuildBackplate(NGInlineCursor* descendants,
const PhysicalOffset& paint_offset) {
// The number of consecutive forced breaks that split the backplate by
// paragraph.
static constexpr int kMaxConsecutiveLineBreaks = 2;
struct Backplates {
STACK_ALLOCATED();
public:
void AddTextRect(const PhysicalRect& box_rect) {
if (consecutive_line_breaks >= kMaxConsecutiveLineBreaks) {
// This is a paragraph point.
paragraph_backplates.push_back(current_backplate);
current_backplate = PhysicalRect();
}
consecutive_line_breaks = 0;
current_backplate.Unite(box_rect);
}
void AddLineBreak() { consecutive_line_breaks++; }
Vector<PhysicalRect> paragraph_backplates;
PhysicalRect current_backplate;
int consecutive_line_breaks = 0;
} backplates;
// Build up and paint backplates of all child inline text boxes. We are not
// able to simply use the linebox rect to compute the backplate because the
// backplate should only be painted for inline text and not for atomic
// inlines.
for (; *descendants; descendants->MoveToNext()) {
if (const NGFragmentItem* child_item = descendants->CurrentItem()) {
if (child_item->IsHiddenForPaint())
continue;
if (child_item->IsText()) {
if (child_item->IsLineBreak()) {
backplates.AddLineBreak();
continue;
}
PhysicalRect box_rect(
child_item->OffsetInContainerFragment() + paint_offset,
child_item->Size());
backplates.AddTextRect(box_rect);
}
continue;
}
NOTREACHED();
}
if (!backplates.current_backplate.IsEmpty())
backplates.paragraph_backplates.push_back(backplates.current_backplate);
return backplates.paragraph_backplates;
}
bool HitTestAllPhasesInFragment(const NGPhysicalBoxFragment& fragment,
const HitTestLocation& hit_test_location,
PhysicalOffset accumulated_offset,
HitTestResult* result) {
// Hit test all phases of inline blocks, inline tables, replaced elements and
// non-positioned floats as if they created their own (pseudo- [1]) stacking
// context. https://www.w3.org/TR/CSS22/zindex.html#painting-order
//
// [1] As if it creates a new stacking context, but any positioned descendants
// and descendants which actually create a new stacking context should be
// considered part of the parent stacking context, not this new one.
if (!fragment.CanTraverse()) {
return fragment.GetMutableLayoutObject()->HitTestAllPhases(
*result, hit_test_location, accumulated_offset);
}
if (!fragment.MayIntersect(*result, hit_test_location, accumulated_offset))
return false;
return NGBoxFragmentPainter(To<NGPhysicalBoxFragment>(fragment))
.HitTestAllPhases(*result, hit_test_location, accumulated_offset);
}
bool NodeAtPointInFragment(const NGPhysicalBoxFragment& fragment,
const HitTestLocation& hit_test_location,
PhysicalOffset accumulated_offset,
HitTestAction action,
HitTestResult* result) {
if (!fragment.CanTraverse()) {
return fragment.GetMutableLayoutObject()->NodeAtPoint(
*result, hit_test_location, accumulated_offset, action);
}
if (!fragment.MayIntersect(*result, hit_test_location, accumulated_offset))
return false;
return NGBoxFragmentPainter(fragment).NodeAtPoint(*result, hit_test_location,
accumulated_offset, action);
}
// Return an ID for this fragmentainer, which is unique within the fragmentation
// context. We need to provide this ID when block-fragmenting, so that we can
// cache the painting of each individual fragment.
unsigned FragmentainerUniqueIdentifier(const NGPhysicalBoxFragment& fragment) {
if (const auto* break_token = To<NGBlockBreakToken>(fragment.BreakToken()))
return break_token->SequenceNumber() + 1;
return 0;
}
bool ShouldPaintCursorCaret(const NGPhysicalBoxFragment& fragment) {
return fragment.GetLayoutObject()->GetFrame()->Selection().ShouldPaintCaret(
fragment);
}
bool ShouldPaintDragCaret(const NGPhysicalBoxFragment& fragment) {
return fragment.GetLayoutObject()
->GetFrame()
->GetPage()
->GetDragCaret()
.ShouldPaintCaret(fragment);
}
bool ShouldPaintCarets(const NGPhysicalBoxFragment& fragment) {
return ShouldPaintCursorCaret(fragment) || ShouldPaintDragCaret(fragment);
}
PaintInfo FloatPaintInfo(const PaintInfo& paint_info) {
PaintInfo float_paint_info(paint_info);
if (paint_info.phase == PaintPhase::kFloat)
float_paint_info.phase = PaintPhase::kForeground;
return float_paint_info;
}
} // anonymous namespace
PhysicalRect NGBoxFragmentPainter::InkOverflowIncludingFilters() const {
if (box_item_)
return box_item_->SelfInkOverflow();
const NGPhysicalFragment& fragment = PhysicalFragment();
DCHECK(!fragment.IsInlineBox());
return To<LayoutBox>(fragment.GetLayoutObject())
->PhysicalVisualOverflowRectIncludingFilters();
}
void NGBoxFragmentPainter::Paint(const PaintInfo& paint_info) {
if (PhysicalFragment().IsHiddenForPaint())
return;
if (PhysicalFragment().IsPaintedAtomically() &&
!box_fragment_.HasSelfPaintingLayer() &&
paint_info.phase != PaintPhase::kOverlayOverflowControls) {
PaintAllPhasesAtomically(paint_info);
} else {
PaintInternal(paint_info);
}
}
void NGBoxFragmentPainter::PaintInternal(const PaintInfo& paint_info) {
// Avoid initialization of Optional ScopedPaintState::chunk_properties_
// and ScopedPaintState::adjusted_paint_info_.
STACK_UNINITIALIZED ScopedPaintState paint_state(box_fragment_, paint_info);
if (!ShouldPaint(paint_state))
return;
PaintInfo& info = paint_state.MutablePaintInfo();
const PhysicalOffset paint_offset = paint_state.PaintOffset();
const PaintPhase original_phase = info.phase;
// For text-combine-upright:all, we need to realize canvas here for scaling
// to fit text content in 1em and shear for "font-style: oblique -15deg".
absl::optional<DrawingRecorder> recorder;
absl::optional<GraphicsContextStateSaver> graphics_context_state_saver;
const auto* const text_combine =
DynamicTo<LayoutNGTextCombine>(box_fragment_.GetLayoutObject());
if (UNLIKELY(text_combine)) {
if (text_combine->NeedsAffineTransformInPaint()) {
if (original_phase == PaintPhase::kForeground)
PaintCaretsIfNeeded(paint_state, paint_info, paint_offset);
if (!paint_info.context.InDrawingRecorder()) {
if (DrawingRecorder::UseCachedDrawingIfPossible(
paint_info.context, GetDisplayItemClient(), paint_info.phase))
return;
recorder.emplace(paint_info.context, GetDisplayItemClient(),
paint_info.phase,
text_combine->VisualRectForPaint(paint_offset));
}
graphics_context_state_saver.emplace(paint_info.context);
paint_info.context.ConcatCTM(
text_combine->ComputeAffineTransformForPaint(paint_offset));
}
}
ScopedPaintTimingDetectorBlockPaintHook
scoped_paint_timing_detector_block_paint_hook;
if (original_phase == PaintPhase::kForeground &&
box_fragment_.GetLayoutObject()->IsBox()) {
scoped_paint_timing_detector_block_paint_hook.EmplaceIfNeeded(
To<LayoutBox>(*box_fragment_.GetLayoutObject()),
paint_info.context.GetPaintController().CurrentPaintChunkProperties());
}
if (original_phase == PaintPhase::kOutline) {
info.phase = PaintPhase::kDescendantOutlinesOnly;
} else if (ShouldPaintSelfBlockBackground(original_phase)) {
info.phase = PaintPhase::kSelfBlockBackgroundOnly;
// We need to call PaintObject twice: one for painting background in the
// border box space, and the other for painting background in the scrolling
// contents space.
auto paint_location = To<LayoutBox>(*box_fragment_.GetLayoutObject())
.GetBackgroundPaintLocation();
if (!(paint_location & kBackgroundPaintInBorderBoxSpace))
info.SetSkipsBackground(true);
PaintObject(info, paint_offset);
info.SetSkipsBackground(false);
if (paint_location & kBackgroundPaintInContentsSpace) {
info.SetIsPaintingBackgroundInContentsSpace(true);
PaintObject(info, paint_offset);
info.SetIsPaintingBackgroundInContentsSpace(false);
}
if (ShouldPaintDescendantBlockBackgrounds(original_phase))
info.phase = PaintPhase::kDescendantBlockBackgroundsOnly;
}
if (original_phase != PaintPhase::kSelfBlockBackgroundOnly &&
original_phase != PaintPhase::kSelfOutlineOnly &&
// kOverlayOverflowControls is for the current object itself, so we don't
// need to traverse descendants here.
original_phase != PaintPhase::kOverlayOverflowControls) {
if (original_phase == PaintPhase::kMask ||
!box_fragment_.GetLayoutObject()->IsBox()) {
PaintObject(info, paint_offset);
} else {
ScopedBoxContentsPaintState contents_paint_state(
paint_state, To<LayoutBox>(*box_fragment_.GetLayoutObject()));
PaintObject(contents_paint_state.GetPaintInfo(),
contents_paint_state.PaintOffset());
}
}
// If the caret's node's fragment's containing block is this block, and
// the paint action is PaintPhaseForeground, then paint the caret.
if (original_phase == PaintPhase::kForeground && LIKELY(!recorder)) {
DCHECK(!text_combine || !text_combine->NeedsAffineTransformInPaint());
PaintCaretsIfNeeded(paint_state, paint_info, paint_offset);
}
if (ShouldPaintSelfOutline(original_phase)) {
info.phase = PaintPhase::kSelfOutlineOnly;
PaintObject(info, paint_offset);
}
if (UNLIKELY(text_combine) &&
NGTextCombinePainter::ShouldPaint(*text_combine)) {
if (recorder) {
// Paint text decorations and emphasis marks without scaling and share.
DCHECK(text_combine->NeedsAffineTransformInPaint());
graphics_context_state_saver->Restore();
} else if (!paint_info.context.InDrawingRecorder()) {
if (DrawingRecorder::UseCachedDrawingIfPossible(
paint_info.context, GetDisplayItemClient(), paint_info.phase))
return;
recorder.emplace(paint_info.context, GetDisplayItemClient(),
paint_info.phase,
text_combine->VisualRectForPaint(paint_offset));
}
NGTextCombinePainter::Paint(info, paint_offset, *text_combine);
}
// We paint scrollbars after we painted other things, so that the scrollbars
// will sit above them.
info.phase = original_phase;
if (box_fragment_.IsScrollContainer()) {
DCHECK(!text_combine);
ScrollableAreaPainter(*PhysicalFragment().Layer()->GetScrollableArea())
.PaintOverflowControls(info, ToRoundedVector2d(paint_offset));
}
}
void NGBoxFragmentPainter::RecordScrollHitTestData(
const PaintInfo& paint_info,
const DisplayItemClient& background_client) {
if (!box_fragment_.GetLayoutObject()->IsBox())
return;
BoxPainter(To<LayoutBox>(*box_fragment_.GetLayoutObject()))
.RecordScrollHitTestData(paint_info, background_client);
}
bool NGBoxFragmentPainter::ShouldRecordHitTestData(
const PaintInfo& paint_info) {
if (paint_info.IsPaintingBackgroundInContentsSpace() &&
PhysicalFragment().EffectiveAllowedTouchAction() == TouchAction::kAuto &&
!PhysicalFragment().InsideBlockingWheelEventHandler()) {
return false;
}
// Hit test data are only needed for compositing. This flag is used for for
// printing and drag images which do not need hit testing.
if (paint_info.ShouldOmitCompositingInfo())
return false;
// If an object is not visible, it does not participate in hit testing.
if (PhysicalFragment().Style().Visibility() != EVisibility::kVisible)
return false;
// Table rows/sections do not participate in hit testing.
if (PhysicalFragment().IsTableNGRow() ||
PhysicalFragment().IsTableNGSection())
return false;
return true;
}
void NGBoxFragmentPainter::PaintObject(
const PaintInfo& paint_info,
const PhysicalOffset& paint_offset,
bool suppress_box_decoration_background) {
const PaintPhase paint_phase = paint_info.phase;
const NGPhysicalBoxFragment& fragment = PhysicalFragment();
const ComputedStyle& style = fragment.Style();
const bool is_visible = IsVisibleToPaint(fragment, style);
if (ShouldPaintSelfBlockBackground(paint_phase)) {
if (is_visible) {
PaintBoxDecorationBackground(paint_info, paint_offset,
suppress_box_decoration_background);
}
// We're done. We don't bother painting any children.
if (paint_phase == PaintPhase::kSelfBlockBackgroundOnly)
return;
}
if (paint_phase == PaintPhase::kMask && is_visible) {
PaintMask(paint_info, paint_offset);
return;
}
if (paint_phase == PaintPhase::kForeground) {
// NGBoxFragmentPainter::PaintLineBoxChildren() calls
// AddURLRectsForInlineChildrenRecursively(). So we don't need to call
// AddURLRectIfNeeded() for LayoutInline.
if (paint_info.ShouldAddUrlMetadata()) {
const auto* layout_object = fragment.GetLayoutObject();
if (layout_object &&
(!layout_object->IsLayoutInline() ||
To<LayoutBoxModelObject>(layout_object)->HasSelfPaintingLayer())) {
NGFragmentPainter(fragment, GetDisplayItemClient())
.AddURLRectIfNeeded(paint_info, paint_offset);
}
}
if (is_visible && fragment.HasExtraMathMLPainting())
NGMathMLPainter(fragment).Paint(paint_info, paint_offset);
}
// Paint children.
if (paint_phase != PaintPhase::kSelfOutlineOnly &&
(!fragment.Children().empty() || fragment.HasItems() ||
inline_box_cursor_) &&
!paint_info.DescendantPaintingBlocked()) {
if (is_visible && UNLIKELY(paint_phase == PaintPhase::kForeground &&
fragment.IsCSSBox() && style.HasColumnRule()))
PaintColumnRules(paint_info, paint_offset);
if (paint_phase != PaintPhase::kFloat) {
if (UNLIKELY(inline_box_cursor_)) {
// Use the descendants cursor for this painter if it is given.
// Self-painting inline box paints only parts of the container block.
// Adjust |paint_offset| because it is the offset of the inline box, but
// |descendants_| has offsets to the contaiing block.
DCHECK(box_item_);
NGInlineCursor descendants = inline_box_cursor_->CursorForDescendants();
const PhysicalOffset paint_offset_to_inline_formatting_context =
paint_offset - box_item_->OffsetInContainerFragment();
PaintInlineItems(paint_info.ForDescendants(),
paint_offset_to_inline_formatting_context,
box_item_->OffsetInContainerFragment(), &descendants);
} else if (items_) {
if (fragment.IsBlockFlow()) {
PaintBlockFlowContents(paint_info, paint_offset);
} else {
DCHECK(fragment.IsInlineBox());
NGInlineCursor cursor(fragment, *items_);
PaintInlineItems(paint_info.ForDescendants(), paint_offset,
PhysicalOffset(), &cursor);
}
} else if (!fragment.IsInlineFormattingContext()) {
PaintBlockChildren(paint_info, paint_offset);
}
}
if (paint_phase == PaintPhase::kFloat ||
paint_phase == PaintPhase::kSelectionDragImage ||
paint_phase == PaintPhase::kTextClip) {
if (fragment.HasFloatingDescendantsForPaint())
PaintFloats(paint_info);
}
}
if (!is_visible)
return;
// Collapsed borders paint *after* children have painted their backgrounds.
if (box_fragment_.IsTableNG() &&
paint_phase == PaintPhase::kDescendantBlockBackgroundsOnly) {
NGTablePainter(box_fragment_)
.PaintCollapsedBorders(paint_info, paint_offset,
VisualRect(paint_offset));
}
if (ShouldPaintSelfOutline(paint_phase)) {
if (NGOutlineUtils::HasPaintedOutline(style, fragment.GetNode())) {
NGFragmentPainter(fragment, GetDisplayItemClient())
.PaintOutline(paint_info, paint_offset, style);
}
} else if (ShouldPaintDescendantOutlines(paint_phase)) {
if (const ComputedStyle* outline_style =
fragment.StyleForContinuationOutline()) {
NGFragmentPainter(fragment, GetDisplayItemClient())
.PaintOutline(paint_info, paint_offset, *outline_style);
}
}
}
void NGBoxFragmentPainter::PaintCaretsIfNeeded(
const ScopedPaintState& paint_state,
const PaintInfo& paint_info,
const PhysicalOffset& paint_offset) {
if (!ShouldPaintCarets(box_fragment_))
return;
// Apply overflow clip if needed.
// reveal-caret-of-multiline-contenteditable.html needs this.
// TDOO(yoisn): We should share this code with |BlockPainter::Paint()|
absl::optional<ScopedPaintChunkProperties> paint_chunk_properties;
if (const auto* fragment = paint_state.FragmentToPaint()) {
if (const auto* properties = fragment->PaintProperties()) {
if (const auto* overflow_clip = properties->OverflowClip()) {
paint_chunk_properties.emplace(
paint_info.context.GetPaintController(), *overflow_clip,
*box_fragment_.GetLayoutObject(), DisplayItem::kCaret);
}
}
}
LocalFrame* frame = box_fragment_.GetLayoutObject()->GetFrame();
if (ShouldPaintCursorCaret(box_fragment_))
frame->Selection().PaintCaret(paint_info.context, paint_offset);
if (ShouldPaintDragCaret(box_fragment_)) {
frame->GetPage()->GetDragCaret().PaintDragCaret(frame, paint_info.context,
paint_offset);
}
}
void NGBoxFragmentPainter::PaintBlockFlowContents(
const PaintInfo& paint_info,
const PhysicalOffset& paint_offset) {
const NGPhysicalBoxFragment& fragment = PhysicalFragment();
const LayoutObject* layout_object = fragment.GetLayoutObject();
DCHECK(fragment.IsInlineFormattingContext());
// When the layout-tree gets into a bad state, we can end up trying to paint
// a fragment with inline children, without a paint fragment. See:
// http://crbug.com/1022545
if (!items_ || (layout_object && layout_object->NeedsLayout())) {
NOTREACHED();
return;
}
// MathML operators paint text (for example enlarged/stretched) content
// themselves using NGMathMLPainter.
if (UNLIKELY(fragment.IsMathMLOperator()))
return;
// Trying to rule out a null GraphicsContext, see: https://crbug.com/1040298
CHECK(&paint_info.context);
// Check if there were contents to be painted and return early if none.
// The union of |ContentsInkOverflow()| and |LocalRect()| covers the rect to
// check, in both cases of:
// 1. Painting non-scrolling contents.
// 2. Painting scrolling contents.
// For 1, check with |ContentsInkOverflow()|, except when there is no
// overflow, in which case check with |LocalRect()|. For 2, check with
// |LayoutOverflow()|, but this can be approximiated with
// |ContentsInkOverflow()|.
// TODO(crbug.com/829028): Column boxes do not have |ContentsInkOverflow| atm,
// hence skip the optimization. If we were to have it, this should be enabled.
// Otherwise, if we're ok with the perf, we can remove this TODO.
if (fragment.IsCSSBox()) {
PhysicalRect content_ink_rect = fragment.LocalRect();
content_ink_rect.Unite(fragment.ContentsInkOverflow());
if (!paint_info.IntersectsCullRect(content_ink_rect, paint_offset))
return;
}
DCHECK(items_);
NGInlineCursor children(fragment, *items_);
if (fragment.IsSvgText()) {
ScopedSVGPaintState paint_state(*fragment.GetLayoutObject(), paint_info);
PaintLineBoxChildren(&children, paint_info.ForDescendants(), paint_offset);
return;
}
PaintLineBoxChildren(&children, paint_info.ForDescendants(), paint_offset);
}
void NGBoxFragmentPainter::PaintBlockChildren(const PaintInfo& paint_info,
PhysicalOffset paint_offset) {
DCHECK(!box_fragment_.IsInlineFormattingContext());
PaintInfo paint_info_for_descendants = paint_info.ForDescendants();
paint_info_for_descendants.SetIsInFragmentTraversal();
for (const NGLink& child : box_fragment_.Children()) {
const NGPhysicalFragment& child_fragment = *child;
DCHECK(child_fragment.IsBox());
if (child_fragment.HasSelfPaintingLayer() || child_fragment.IsFloating())
continue;
PaintBlockChild(child, paint_info, paint_info_for_descendants,
paint_offset);
}
}
void NGBoxFragmentPainter::PaintBlockChild(
const NGLink& child,
const PaintInfo& paint_info,
const PaintInfo& paint_info_for_descendants,
PhysicalOffset paint_offset) {
const NGPhysicalFragment& child_fragment = *child;
DCHECK(child_fragment.IsBox());
DCHECK(!child_fragment.HasSelfPaintingLayer());
DCHECK(!child_fragment.IsFloating());
const auto& box_child_fragment = To<NGPhysicalBoxFragment>(child_fragment);
if (box_child_fragment.CanTraverse()) {
if (!box_child_fragment.GetLayoutObject()) {
// It's normally FragmentData that provides us with the paint offset.
// FragmentData is (at least currently) associated with a LayoutObject.
// If we have no LayoutObject, we have no FragmentData, so we need to
// calculate the offset on our own (which is very simple, anyway).
// Bypass Paint() and jump directly to PaintObject(), to skip the code
// that assumes that we have a LayoutObject (and FragmentData).
PhysicalOffset child_offset = paint_offset + child.offset;
if (box_child_fragment.IsFragmentainerBox()) {
// This is a fragmentainer, and when node inside a fragmentation
// context paints multiple block fragments, we need to distinguish
// between them somehow, for paint caching to work. Therefore,
// establish a display item scope here.
unsigned identifier = FragmentainerUniqueIdentifier(box_child_fragment);
ScopedDisplayItemFragment scope(paint_info.context, identifier);
NGBoxFragmentPainter(box_child_fragment)
.PaintObject(paint_info, child_offset);
return;
}
NGBoxFragmentPainter(box_child_fragment)
.PaintObject(paint_info, child_offset);
return;
}
NGBoxFragmentPainter(box_child_fragment).Paint(paint_info_for_descendants);
return;
}
// Fall back to flow-thread painting when reaching a column (the flow thread
// is treated as a self-painting PaintLayer when fragment traversal is
// disabled, so nothing to do here).
if (box_child_fragment.IsColumnBox())
return;
auto* layout_object = child_fragment.GetLayoutObject();
DCHECK(layout_object);
if (child_fragment.IsPaintedAtomically() &&
child_fragment.IsLegacyLayoutRoot()) {
ObjectPainter(*layout_object)
.PaintAllPhasesAtomically(paint_info_for_descendants);
} else {
// TODO(ikilpatrick): Once FragmentItem ships we should call the
// NGBoxFragmentPainter directly for NG objects.
layout_object->Paint(paint_info_for_descendants);
}
}
void NGBoxFragmentPainter::PaintFloatingItems(const PaintInfo& paint_info,
NGInlineCursor* cursor) {
while (*cursor) {
const NGFragmentItem* item = cursor->Current().Item();
DCHECK(item);
const NGPhysicalBoxFragment* child_fragment = item->BoxFragment();
if (!child_fragment) {
cursor->MoveToNext();
continue;
}
if (child_fragment->HasSelfPaintingLayer()) {
cursor->MoveToNextSkippingChildren();
continue;
}
if (child_fragment->IsFloating()) {
PaintInfo float_paint_info = FloatPaintInfo(paint_info);
if (child_fragment->CanTraverse()) {
NGBoxFragmentPainter(*child_fragment).Paint(float_paint_info);
} else {
ObjectPainter(*child_fragment->GetLayoutObject())
.PaintAllPhasesAtomically(float_paint_info);
}
} else if (child_fragment->IsBlockInInline() &&
child_fragment->HasFloatingDescendantsForPaint()) {
NGBoxFragmentPainter(*child_fragment).Paint(paint_info);
}
DCHECK(child_fragment->IsInlineBox() || !cursor->Current().HasChildren());
cursor->MoveToNext();
}
}
void NGBoxFragmentPainter::PaintFloatingChildren(
const NGPhysicalFragment& container,
const PaintInfo& paint_info) {
DCHECK(container.HasFloatingDescendantsForPaint());
const PaintInfo* local_paint_info = &paint_info;
absl::optional<ScopedPaintState> paint_state;
absl::optional<ScopedBoxContentsPaintState> contents_paint_state;
if (const auto* box = DynamicTo<LayoutBox>(container.GetLayoutObject())) {
paint_state.emplace(container, paint_info);
contents_paint_state.emplace(*paint_state, *box);
local_paint_info = &contents_paint_state->GetPaintInfo();
}
DCHECK(container.HasFloatingDescendantsForPaint());
for (const NGLink& child : container.Children()) {
const NGPhysicalFragment& child_fragment = *child;
if (child_fragment.HasSelfPaintingLayer())
continue;
if (child_fragment.CanTraverse()) {
if (child_fragment.IsFloating()) {
NGBoxFragmentPainter(To<NGPhysicalBoxFragment>(child_fragment))
.Paint(FloatPaintInfo(*local_paint_info));
continue;
}
// Any non-floated children which paint atomically shouldn't be traversed.
if (child_fragment.IsPaintedAtomically())
continue;
} else {
if (child_fragment.IsFloating()) {
// TODO(kojii): The float is outside of the inline formatting context
// and that it maybe another NG inline formatting context, NG block
// layout, or legacy. NGBoxFragmentPainter can handle only the first
// case. In order to cover more tests for other two cases, we always
// fallback to legacy, which will forward back to NGBoxFragmentPainter
// if the float is for NGBoxFragmentPainter. We can shortcut this for
// the first case when we're more stable.
ObjectPainter(*child_fragment.GetLayoutObject())
.PaintAllPhasesAtomically(FloatPaintInfo(*local_paint_info));
continue;
}
// Any children which paint atomically shouldn't be traversed.
if (child_fragment.IsPaintedAtomically())
continue;
// Drawing in SelectionDragImage phase can result in an exponential
// paint time: crbug.com://1182106
if (local_paint_info->phase != PaintPhase::kSelectionDragImage &&
child_fragment.Type() == NGPhysicalFragment::kFragmentBox &&
FragmentRequiresLegacyFallback(child_fragment)) {
child_fragment.GetLayoutObject()->Paint(*local_paint_info);
continue;
}
}
// The selection paint traversal is special. We will visit all fragments
// (including floats) in the normal paint traversal. There isn't any point
// performing the special float traversal here.
if (local_paint_info->phase == PaintPhase::kSelectionDragImage)
continue;
if (!child_fragment.HasFloatingDescendantsForPaint())
continue;
if (child_fragment.HasNonVisibleOverflow()) {
// We need to properly visit this fragment for painting, rather than
// jumping directly to its children (which is what we normally do when
// looking for floats), in order to set up the clip rectangle.
NGBoxFragmentPainter(To<NGPhysicalBoxFragment>(child_fragment))
.Paint(*local_paint_info);
continue;
}
if (child_fragment.IsFragmentainerBox()) {
// This is a fragmentainer, and when node inside a fragmentation context
// paints multiple block fragments, we need to distinguish between them
// somehow, for paint caching to work. Therefore, establish a display item
// scope here.
unsigned identifier = FragmentainerUniqueIdentifier(
To<NGPhysicalBoxFragment>(child_fragment));
ScopedDisplayItemFragment scope(paint_info.context, identifier);
PaintFloatingChildren(child_fragment, *local_paint_info);
} else {
PaintFloatingChildren(child_fragment, *local_paint_info);
}
}
// Now process the inline formatting context, if any. Note that even if this
// is an inline formatting context, we still need to walk the box fragment
// children (like we did above). If a float is block-fragmented, it is resumed
// as a regular box fragment child, rather than becoming a fragment item.
if (const NGPhysicalBoxFragment* box =
DynamicTo<NGPhysicalBoxFragment>(&container)) {
if (const NGFragmentItems* items = box->Items()) {
NGInlineCursor cursor(*box, *items);
PaintFloatingItems(*local_paint_info, &cursor);
return;
}
if (inline_box_cursor_) {
DCHECK(box->IsInlineBox());
NGInlineCursor descendants = inline_box_cursor_->CursorForDescendants();
PaintFloatingItems(*local_paint_info, &descendants);
return;
}
DCHECK(!box->IsInlineBox());
}
}
void NGBoxFragmentPainter::PaintFloats(const PaintInfo& paint_info) {
DCHECK(PhysicalFragment().HasFloatingDescendantsForPaint() ||
!PhysicalFragment().IsInlineFormattingContext());
PaintFloatingChildren(PhysicalFragment(), paint_info);
}
void NGBoxFragmentPainter::PaintMask(const PaintInfo& paint_info,
const PhysicalOffset& paint_offset) {
DCHECK_EQ(PaintPhase::kMask, paint_info.phase);
const NGPhysicalBoxFragment& physical_box_fragment = PhysicalFragment();
const ComputedStyle& style = physical_box_fragment.Style();
if (!style.HasMask() || !IsVisibleToPaint(physical_box_fragment, style))
return;
if (DrawingRecorder::UseCachedDrawingIfPossible(
paint_info.context, GetDisplayItemClient(), paint_info.phase))
return;
if (physical_box_fragment.IsFieldsetContainer()) {
NGFieldsetPainter(box_fragment_).PaintMask(paint_info, paint_offset);
return;
}
// TODO(eae): Switch to LayoutNG version of BackgroundImageGeometry.
BackgroundImageGeometry geometry(*static_cast<const LayoutBoxModelObject*>(
box_fragment_.GetLayoutObject()));
DrawingRecorder recorder(paint_info.context, GetDisplayItemClient(),
paint_info.phase, VisualRect(paint_offset));
PhysicalRect paint_rect(paint_offset, box_fragment_.Size());
PaintMaskImages(paint_info, paint_rect, *box_fragment_.GetLayoutObject(),
geometry, box_fragment_.SidesToInclude());
}
// TODO(kojii): This logic is kept in sync with BoxPainter. Not much efforts to
// eliminate LayoutObject dependency were done yet.
void NGBoxFragmentPainter::PaintBoxDecorationBackground(
const PaintInfo& paint_info,
const PhysicalOffset& paint_offset,
bool suppress_box_decoration_background) {
// TODO(mstensho): Break dependency on LayoutObject functionality.
const LayoutObject& layout_object = *box_fragment_.GetLayoutObject();
if (const auto* view = DynamicTo<LayoutView>(&layout_object)) {