1 /*
2 * Copyright (C) 2004, 2006, 2007 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26 #include "third_party/blink/renderer/core/layout/layout_tree_as_text.h"
27
28 #include "third_party/blink/renderer/core/css/css_property_value_set.h"
29 #include "third_party/blink/renderer/core/display_lock/display_lock_context.h"
30 #include "third_party/blink/renderer/core/dom/document.h"
31 #include "third_party/blink/renderer/core/dom/pseudo_element.h"
32 #include "third_party/blink/renderer/core/editing/frame_selection.h"
33 #include "third_party/blink/renderer/core/editing/visible_selection.h"
34 #include "third_party/blink/renderer/core/frame/local_frame.h"
35 #include "third_party/blink/renderer/core/frame/local_frame_view.h"
36 #include "third_party/blink/renderer/core/frame/settings.h"
37 #include "third_party/blink/renderer/core/html/html_element.h"
38 #include "third_party/blink/renderer/core/html_names.h"
39 #include "third_party/blink/renderer/core/layout/layout_block_flow.h"
40 #include "third_party/blink/renderer/core/layout/layout_details_marker.h"
41 #include "third_party/blink/renderer/core/layout/layout_embedded_content.h"
42 #include "third_party/blink/renderer/core/layout/layout_file_upload_control.h"
43 #include "third_party/blink/renderer/core/layout/layout_inline.h"
44 #include "third_party/blink/renderer/core/layout/layout_list_item.h"
45 #include "third_party/blink/renderer/core/layout/layout_list_marker.h"
46 #include "third_party/blink/renderer/core/layout/layout_table_cell.h"
47 #include "third_party/blink/renderer/core/layout/layout_view.h"
48 #include "third_party/blink/renderer/core/layout/line/inline_text_box.h"
49 #include "third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.h"
50 #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h"
51 #include "third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment.h"
52 #include "third_party/blink/renderer/core/layout/ng/list/list_marker.h"
53 #include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h"
54 #include "third_party/blink/renderer/core/layout/svg/layout_svg_image.h"
55 #include "third_party/blink/renderer/core/layout/svg/layout_svg_inline.h"
56 #include "third_party/blink/renderer/core/layout/svg/layout_svg_inline_text.h"
57 #include "third_party/blink/renderer/core/layout/svg/layout_svg_root.h"
58 #include "third_party/blink/renderer/core/layout/svg/layout_svg_shape.h"
59 #include "third_party/blink/renderer/core/layout/svg/layout_svg_text.h"
60 #include "third_party/blink/renderer/core/layout/svg/svg_layout_tree_as_text.h"
61 #include "third_party/blink/renderer/core/page/print_context.h"
62 #include "third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.h"
63 #include "third_party/blink/renderer/core/paint/paint_layer.h"
64 #include "third_party/blink/renderer/core/paint/paint_layer_paint_order_iterator.h"
65 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
66 #include "third_party/blink/renderer/platform/geometry/layout_unit.h"
67 #include "third_party/blink/renderer/platform/wtf/text/character_names.h"
68 #include "third_party/blink/renderer/platform/wtf/vector.h"
69
70 namespace blink {
71
PrintBorderStyle(WTF::TextStream & ts,const EBorderStyle border_style)72 static void PrintBorderStyle(WTF::TextStream& ts,
73 const EBorderStyle border_style) {
74 switch (border_style) {
75 case EBorderStyle::kNone:
76 ts << "none";
77 break;
78 case EBorderStyle::kHidden:
79 ts << "hidden";
80 break;
81 case EBorderStyle::kInset:
82 ts << "inset";
83 break;
84 case EBorderStyle::kGroove:
85 ts << "groove";
86 break;
87 case EBorderStyle::kRidge:
88 ts << "ridge";
89 break;
90 case EBorderStyle::kOutset:
91 ts << "outset";
92 break;
93 case EBorderStyle::kDotted:
94 ts << "dotted";
95 break;
96 case EBorderStyle::kDashed:
97 ts << "dashed";
98 break;
99 case EBorderStyle::kSolid:
100 ts << "solid";
101 break;
102 case EBorderStyle::kDouble:
103 ts << "double";
104 break;
105 }
106
107 ts << " ";
108 }
109
GetTagName(Node * n)110 static String GetTagName(Node* n) {
111 if (n->IsDocumentNode())
112 return "";
113 if (n->getNodeType() == Node::kCommentNode)
114 return "COMMENT";
115 return n->nodeName();
116 }
117
QuoteAndEscapeNonPrintables(const String & s)118 String QuoteAndEscapeNonPrintables(const String& s) {
119 StringBuilder result;
120 result.Append('"');
121 for (unsigned i = 0; i != s.length(); ++i) {
122 UChar c = s[i];
123 if (c == '\\') {
124 result.Append('\\');
125 result.Append('\\');
126 } else if (c == '"') {
127 result.Append('\\');
128 result.Append('"');
129 } else if (c == '\n' || c == kNoBreakSpaceCharacter) {
130 result.Append(' ');
131 } else {
132 if (c >= 0x20 && c < 0x7F) {
133 result.Append(c);
134 } else {
135 result.AppendFormat("\\x{%X}", c);
136 }
137 }
138 }
139 result.Append('"');
140 return result.ToString();
141 }
142
operator <<(WTF::TextStream & ts,const Color & c)143 WTF::TextStream& operator<<(WTF::TextStream& ts, const Color& c) {
144 return ts << c.NameForLayoutTreeAsText();
145 }
146
WriteLayoutObject(WTF::TextStream & ts,const LayoutObject & o,LayoutAsTextBehavior behavior)147 void LayoutTreeAsText::WriteLayoutObject(WTF::TextStream& ts,
148 const LayoutObject& o,
149 LayoutAsTextBehavior behavior) {
150 ts << o.DecoratedName();
151
152 if (behavior & kLayoutAsTextShowAddresses)
153 ts << " " << static_cast<const void*>(&o);
154
155 if (o.Style() && o.StyleRef().ZIndex())
156 ts << " zI: " << o.StyleRef().ZIndex();
157
158 if (o.GetNode()) {
159 String tag_name = GetTagName(o.GetNode());
160 if (!tag_name.IsEmpty())
161 ts << " {" << tag_name << "}";
162 }
163
164 PhysicalRect rect = o.DebugRect();
165 ts << " " << rect;
166
167 if (!(o.IsText() && !o.IsBR())) {
168 if (o.IsFileUploadControl())
169 ts << " "
170 << QuoteAndEscapeNonPrintables(
171 ToLayoutFileUploadControl(&o)->FileTextValue());
172
173 if (o.Parent()) {
174 Color color = o.ResolveColor(GetCSSPropertyColor());
175 if (o.Parent()->ResolveColor(GetCSSPropertyColor()) != color)
176 ts << " [color=" << color << "]";
177
178 // Do not dump invalid or transparent backgrounds, since that is the
179 // default.
180 Color background_color = o.ResolveColor(GetCSSPropertyBackgroundColor());
181 if (o.Parent()->ResolveColor(GetCSSPropertyBackgroundColor()) !=
182 background_color &&
183 background_color.Rgb())
184 ts << " [bgcolor=" << background_color << "]";
185
186 Color text_fill_color =
187 o.ResolveColor(GetCSSPropertyWebkitTextFillColor());
188 if (o.Parent()->ResolveColor(GetCSSPropertyWebkitTextFillColor()) !=
189 text_fill_color &&
190 text_fill_color != color && text_fill_color.Rgb())
191 ts << " [textFillColor=" << text_fill_color << "]";
192
193 Color text_stroke_color =
194 o.ResolveColor(GetCSSPropertyWebkitTextStrokeColor());
195 if (o.Parent()->ResolveColor(GetCSSPropertyWebkitTextStrokeColor()) !=
196 text_stroke_color &&
197 text_stroke_color != color && text_stroke_color.Rgb())
198 ts << " [textStrokeColor=" << text_stroke_color << "]";
199
200 if (o.Parent()->StyleRef().TextStrokeWidth() !=
201 o.StyleRef().TextStrokeWidth() &&
202 o.StyleRef().TextStrokeWidth() > 0)
203 ts << " [textStrokeWidth=" << o.StyleRef().TextStrokeWidth() << "]";
204 }
205
206 if (!o.IsBoxModelObject())
207 return;
208
209 const LayoutBoxModelObject& box = ToLayoutBoxModelObject(o);
210 if (box.BorderTop() || box.BorderRight() || box.BorderBottom() ||
211 box.BorderLeft()) {
212 ts << " [border:";
213
214 BorderValue prev_border = o.StyleRef().BorderTop();
215 if (!box.BorderTop()) {
216 ts << " none";
217 } else {
218 ts << " (" << box.BorderTop() << "px ";
219 PrintBorderStyle(ts, o.StyleRef().BorderTopStyle());
220 ts << o.ResolveColor(GetCSSPropertyBorderTopColor()) << ")";
221 }
222
223 if (!o.StyleRef().BorderRightEquals(prev_border)) {
224 prev_border = o.StyleRef().BorderRight();
225 if (!box.BorderRight()) {
226 ts << " none";
227 } else {
228 ts << " (" << box.BorderRight() << "px ";
229 PrintBorderStyle(ts, o.StyleRef().BorderRightStyle());
230 ts << o.ResolveColor(GetCSSPropertyBorderRightColor()) << ")";
231 }
232 }
233
234 if (!o.StyleRef().BorderBottomEquals(prev_border)) {
235 prev_border = box.StyleRef().BorderBottom();
236 if (!box.BorderBottom()) {
237 ts << " none";
238 } else {
239 ts << " (" << box.BorderBottom() << "px ";
240 PrintBorderStyle(ts, o.StyleRef().BorderBottomStyle());
241 ts << o.ResolveColor(GetCSSPropertyBorderBottomColor()) << ")";
242 }
243 }
244
245 if (!o.StyleRef().BorderLeftEquals(prev_border)) {
246 prev_border = o.StyleRef().BorderLeft();
247 if (!box.BorderLeft()) {
248 ts << " none";
249 } else {
250 ts << " (" << box.BorderLeft() << "px ";
251 PrintBorderStyle(ts, o.StyleRef().BorderLeftStyle());
252 ts << o.ResolveColor(GetCSSPropertyBorderLeftColor()) << ")";
253 }
254 }
255
256 ts << "]";
257 }
258 }
259
260 if (o.IsTableCell()) {
261 const LayoutNGTableCellInterface& c =
262 ToInterface<LayoutNGTableCellInterface>(o);
263 ts << " [r=" << c.RowIndex() << " c=" << c.AbsoluteColumnIndex()
264 << " rs=" << c.ResolvedRowSpan() << " cs=" << c.ColSpan() << "]";
265 }
266
267 if (o.IsDetailsMarker()) {
268 ts << ": ";
269 switch (ToLayoutDetailsMarker(&o)->GetOrientation()) {
270 case LayoutDetailsMarker::kLeft:
271 ts << "left";
272 break;
273 case LayoutDetailsMarker::kRight:
274 ts << "right";
275 break;
276 case LayoutDetailsMarker::kUp:
277 ts << "up";
278 break;
279 case LayoutDetailsMarker::kDown:
280 ts << "down";
281 break;
282 }
283 }
284
285 if (o.IsListMarker()) {
286 String text = ToLayoutListMarker(o).GetText();
287 if (!text.IsEmpty()) {
288 if (text.length() != 1) {
289 text = QuoteAndEscapeNonPrintables(text);
290 } else {
291 switch (text[0]) {
292 case kBulletCharacter:
293 text = "bullet";
294 break;
295 case kBlackSquareCharacter:
296 text = "black square";
297 break;
298 case kWhiteBulletCharacter:
299 text = "white bullet";
300 break;
301 default:
302 text = QuoteAndEscapeNonPrintables(text);
303 }
304 }
305 ts << ": " << text;
306 }
307 }
308
309 if (behavior & kLayoutAsTextShowIDAndClass) {
310 if (auto* element = DynamicTo<Element>(o.GetNode())) {
311 if (element->HasID())
312 ts << " id=\"" + element->GetIdAttribute() + "\"";
313
314 if (element->HasClass()) {
315 ts << " class=\"";
316 for (wtf_size_t i = 0; i < element->ClassNames().size(); ++i) {
317 if (i > 0)
318 ts << " ";
319 ts << element->ClassNames()[i];
320 }
321 ts << "\"";
322 }
323 }
324 }
325
326 if (behavior & kLayoutAsTextShowLayoutState) {
327 bool needs_layout = o.SelfNeedsLayout() ||
328 o.NeedsPositionedMovementLayout() ||
329 o.PosChildNeedsLayout() || o.NormalChildNeedsLayout();
330 if (needs_layout)
331 ts << " (needs layout:";
332
333 bool have_previous = false;
334 if (o.SelfNeedsLayout()) {
335 ts << " self";
336 have_previous = true;
337 }
338
339 if (o.NeedsPositionedMovementLayout()) {
340 if (have_previous)
341 ts << ",";
342 have_previous = true;
343 ts << " positioned movement";
344 }
345
346 if (o.NormalChildNeedsLayout()) {
347 if (have_previous)
348 ts << ",";
349 have_previous = true;
350 ts << " child";
351 }
352
353 if (o.PosChildNeedsLayout()) {
354 if (have_previous)
355 ts << ",";
356 ts << " positioned child";
357 }
358
359 if (needs_layout)
360 ts << ")";
361 }
362
363 if (o.LayoutBlockedByDisplayLock(DisplayLockLifecycleTarget::kChildren))
364 ts << " (display-locked)";
365 }
366
WriteInlineBox(WTF::TextStream & ts,const InlineBox & box,int indent)367 static void WriteInlineBox(WTF::TextStream& ts,
368 const InlineBox& box,
369 int indent) {
370 WriteIndent(ts, indent);
371 ts << "+ ";
372 ts << box.BoxName() << " {" << box.GetLineLayoutItem().DebugName() << "}"
373 << " pos=(" << box.X() << "," << box.Y() << ")"
374 << " size=(" << box.Width() << "," << box.Height() << ")"
375 << " baseline=" << box.BaselinePosition(kAlphabeticBaseline) << "/"
376 << box.BaselinePosition(kIdeographicBaseline);
377 }
378
WriteInlineTextBox(WTF::TextStream & ts,const InlineTextBox & text_box,int indent)379 static void WriteInlineTextBox(WTF::TextStream& ts,
380 const InlineTextBox& text_box,
381 int indent) {
382 WriteInlineBox(ts, text_box, indent);
383 String value = text_box.GetText();
384 value.Replace('\\', "\\\\");
385 value.Replace('\n', "\\n");
386 value.Replace('"', "\\\"");
387 ts << " range=(" << text_box.Start() << ","
388 << (text_box.Start() + text_box.Len()) << ")"
389 << " \"" << value << "\"";
390 }
391
WriteInlineFlowBox(WTF::TextStream & ts,const InlineFlowBox & root_box,int indent)392 static void WriteInlineFlowBox(WTF::TextStream& ts,
393 const InlineFlowBox& root_box,
394 int indent) {
395 WriteInlineBox(ts, root_box, indent);
396 ts << "\n";
397 for (const InlineBox* box = root_box.FirstChild(); box;
398 box = box->NextOnLine()) {
399 if (box->IsInlineFlowBox()) {
400 WriteInlineFlowBox(ts, static_cast<const InlineFlowBox&>(*box),
401 indent + 1);
402 continue;
403 }
404 if (box->IsInlineTextBox())
405 WriteInlineTextBox(ts, static_cast<const InlineTextBox&>(*box),
406 indent + 1);
407 else
408 WriteInlineBox(ts, *box, indent + 1);
409 ts << "\n";
410 }
411 }
412
WriteLineBoxTree(WTF::TextStream & ts,const LayoutBlockFlow & o,int indent)413 void LayoutTreeAsText::WriteLineBoxTree(WTF::TextStream& ts,
414 const LayoutBlockFlow& o,
415 int indent) {
416 for (const InlineFlowBox* root_box : o.LineBoxes()) {
417 WriteInlineFlowBox(ts, *root_box, indent);
418 }
419 }
420
WriteTextRun(WTF::TextStream & ts,const LayoutText & o,const InlineTextBox & run)421 static void WriteTextRun(WTF::TextStream& ts,
422 const LayoutText& o,
423 const InlineTextBox& run) {
424 // FIXME: For now use an "enclosingIntRect" model for x, y and logicalWidth,
425 // although this makes it harder to detect any changes caused by the
426 // conversion to floating point. :(
427 int x = run.X().ToInt();
428 int y = run.Y().ToInt();
429 int logical_width = (run.X() + run.LogicalWidth()).Ceil() - x;
430
431 // FIXME: Table cell adjustment is temporary until results can be updated.
432 if (o.ContainingBlock()->IsTableCell()) {
433 y -= ToInterface<LayoutNGTableCellInterface>(o.ContainingBlock())
434 ->IntrinsicPaddingBefore();
435 }
436
437 ts << "text run at (" << x << "," << y << ") width " << logical_width;
438 if (!run.IsLeftToRightDirection() || run.DirOverride()) {
439 ts << (!run.IsLeftToRightDirection() ? " RTL" : " LTR");
440 if (run.DirOverride())
441 ts << " override";
442 }
443 ts << ": "
444 << QuoteAndEscapeNonPrintables(
445 String(o.GetText()).Substring(run.Start(), run.Len()));
446 if (run.HasHyphen()) {
447 ts << " + hyphen string "
448 << QuoteAndEscapeNonPrintables(o.StyleRef().HyphenString());
449 }
450 ts << "\n";
451 }
452
WriteTextFragment(WTF::TextStream & ts,const LayoutObject * layout_object,PhysicalRect rect,const ComputedStyle & style,StringView text,LayoutUnit inline_size)453 static void WriteTextFragment(WTF::TextStream& ts,
454 const LayoutObject* layout_object,
455 PhysicalRect rect,
456 const ComputedStyle& style,
457 StringView text,
458 LayoutUnit inline_size) {
459 // TODO(layout-dev): Dump physical coordinates when removing the legacy inline
460 // layout code.
461 PhysicalOffset offset_to_container_box = rect.offset;
462 if (UNLIKELY(style.IsFlippedBlocksWritingMode())) {
463 if (layout_object) {
464 const LayoutBlock* containing_block = layout_object->ContainingBlock();
465 LayoutRect layout_rect = containing_block->FlipForWritingMode(rect);
466 offset_to_container_box.left = layout_rect.X();
467 }
468 }
469
470 // See WriteTextRun() for why we convert to int.
471 int x = offset_to_container_box.left.ToInt();
472 int y = offset_to_container_box.top.ToInt();
473 int logical_width = (offset_to_container_box.left + inline_size).Ceil() - x;
474 ts << "text run at (" << x << "," << y << ") width " << logical_width;
475 ts << ": " << QuoteAndEscapeNonPrintables(text.ToString());
476 ts << "\n";
477 }
478
WriteTextFragment(WTF::TextStream & ts,const NGInlineCursor & cursor)479 static void WriteTextFragment(WTF::TextStream& ts,
480 const NGInlineCursor& cursor) {
481 if (const NGPaintFragment* const paint_fragment =
482 cursor.CurrentPaintFragment()) {
483 const auto* physical_text_fragment =
484 DynamicTo<NGPhysicalTextFragment>(paint_fragment->PhysicalFragment());
485 if (!physical_text_fragment)
486 return;
487 const NGTextFragment fragment(paint_fragment->Style().GetWritingMode(),
488 *physical_text_fragment);
489 WriteTextFragment(ts, paint_fragment->GetLayoutObject(),
490 paint_fragment->RectInContainerBlock(),
491 paint_fragment->Style(), physical_text_fragment->Text(),
492 fragment.InlineSize());
493 return;
494 }
495 DCHECK(cursor.CurrentItem());
496 const NGFragmentItem& item = *cursor.CurrentItem();
497 DCHECK(item.Type() == NGFragmentItem::kText ||
498 item.Type() == NGFragmentItem::kGeneratedText);
499 const LayoutUnit inline_size =
500 item.IsHorizontal() ? item.Size().width : item.Size().height;
501 WriteTextFragment(ts, item.GetLayoutObject(), item.RectInContainerBlock(),
502 item.Style(), item.Text(cursor.Items()), inline_size);
503 }
504
WritePaintProperties(WTF::TextStream & ts,const LayoutObject & o,int indent)505 static void WritePaintProperties(WTF::TextStream& ts,
506 const LayoutObject& o,
507 int indent) {
508 bool has_fragments = o.FirstFragment().NextFragment();
509 if (has_fragments) {
510 WriteIndent(ts, indent);
511 ts << "fragments:\n";
512 }
513 int fragment_index = 0;
514 for (const auto *fragment = &o.FirstFragment(); fragment;
515 fragment = fragment->NextFragment(), ++fragment_index) {
516 WriteIndent(ts, indent);
517 if (has_fragments)
518 ts << " " << fragment_index << ":";
519 ts << " paint_offset=(" << fragment->PaintOffset().ToString()
520 << ") visual_rect=(" << fragment->VisualRect().ToString() << ")";
521 if (fragment->HasLocalBorderBoxProperties()) {
522 // To know where they point into the paint property tree, you can dump
523 // the tree using ShowAllPropertyTrees(frame_view).
524 ts << " state=(" << fragment->LocalBorderBoxProperties().ToString()
525 << ")";
526 }
527 ts << "\n";
528 }
529 }
530
Write(WTF::TextStream & ts,const LayoutObject & o,int indent,LayoutAsTextBehavior behavior)531 void Write(WTF::TextStream& ts,
532 const LayoutObject& o,
533 int indent,
534 LayoutAsTextBehavior behavior) {
535 if (o.IsSVGShape()) {
536 Write(ts, ToLayoutSVGShape(o), indent);
537 return;
538 }
539 if (o.IsSVGResourceContainer()) {
540 WriteSVGResourceContainer(ts, o, indent);
541 return;
542 }
543 if (o.IsSVGContainer()) {
544 WriteSVGContainer(ts, o, indent);
545 return;
546 }
547 if (o.IsSVGRoot()) {
548 Write(ts, ToLayoutSVGRoot(o), indent);
549 return;
550 }
551 if (o.IsSVGText()) {
552 WriteSVGText(ts, ToLayoutSVGText(o), indent);
553 return;
554 }
555 if (o.IsSVGInline()) {
556 WriteSVGInline(ts, ToLayoutSVGInline(o), indent);
557 return;
558 }
559 if (o.IsSVGInlineText()) {
560 WriteSVGInlineText(ts, ToLayoutSVGInlineText(o), indent);
561 return;
562 }
563 if (o.IsSVGImage()) {
564 WriteSVGImage(ts, ToLayoutSVGImage(o), indent);
565 return;
566 }
567
568 WriteIndent(ts, indent);
569
570 LayoutTreeAsText::WriteLayoutObject(ts, o, behavior);
571 ts << "\n";
572
573 if (behavior & kLayoutAsTextShowPaintProperties) {
574 WritePaintProperties(ts, o, indent + 1);
575 }
576
577 auto* layout_block_flow = DynamicTo<LayoutBlockFlow>(o);
578 if ((behavior & kLayoutAsTextShowLineTrees) && layout_block_flow) {
579 LayoutTreeAsText::WriteLineBoxTree(ts, *layout_block_flow, indent + 1);
580 }
581
582 if (o.IsText() && !o.IsBR()) {
583 const LayoutText& text = ToLayoutText(o);
584 if (const LayoutBlockFlow* block_flow = text.ContainingNGBlockFlow()) {
585 NGInlineCursor cursor(*block_flow);
586 cursor.MoveTo(text);
587 for (; cursor; cursor.MoveToNextForSameLayoutObject()) {
588 WriteIndent(ts, indent + 1);
589 WriteTextFragment(ts, cursor);
590 }
591 } else {
592 for (InlineTextBox* box : text.TextBoxes()) {
593 WriteIndent(ts, indent + 1);
594 WriteTextRun(ts, text, *box);
595 }
596 }
597 }
598
599 if (!o.LayoutBlockedByDisplayLock(DisplayLockLifecycleTarget::kChildren)) {
600 for (LayoutObject* child = o.SlowFirstChild(); child;
601 child = child->NextSibling()) {
602 if (child->HasLayer())
603 continue;
604 Write(ts, *child, indent + 1, behavior);
605 }
606 }
607
608 if (o.IsLayoutEmbeddedContent()) {
609 FrameView* frame_view = ToLayoutEmbeddedContent(o).ChildFrameView();
610 if (auto* local_frame_view = DynamicTo<LocalFrameView>(frame_view)) {
611 if (auto* layout_view = local_frame_view->GetLayoutView()) {
612 layout_view->GetDocument().UpdateStyleAndLayout(
613 DocumentUpdateReason::kTest);
614 if (auto* layer = layout_view->Layer()) {
615 LayoutTreeAsText::WriteLayers(ts, layer, layer, indent + 1, behavior);
616 }
617 }
618 }
619 }
620 }
621
622 enum LayerPaintPhase {
623 kLayerPaintPhaseAll = 0,
624 kLayerPaintPhaseBackground = -1,
625 kLayerPaintPhaseForeground = 1
626 };
627
Write(WTF::TextStream & ts,PaintLayer & layer,const PhysicalRect & layer_bounds,const PhysicalRect & background_clip_rect,const PhysicalRect & clip_rect,LayerPaintPhase paint_phase=kLayerPaintPhaseAll,int indent=0,LayoutAsTextBehavior behavior=kLayoutAsTextBehaviorNormal,const PaintLayer * marked_layer=nullptr)628 static void Write(WTF::TextStream& ts,
629 PaintLayer& layer,
630 const PhysicalRect& layer_bounds,
631 const PhysicalRect& background_clip_rect,
632 const PhysicalRect& clip_rect,
633 LayerPaintPhase paint_phase = kLayerPaintPhaseAll,
634 int indent = 0,
635 LayoutAsTextBehavior behavior = kLayoutAsTextBehaviorNormal,
636 const PaintLayer* marked_layer = nullptr) {
637 IntRect adjusted_layout_bounds = PixelSnappedIntRect(layer_bounds);
638 IntRect adjusted_background_clip_rect =
639 PixelSnappedIntRect(background_clip_rect);
640 IntRect adjusted_clip_rect = PixelSnappedIntRect(clip_rect);
641
642 if (marked_layer)
643 ts << (marked_layer == &layer ? "*" : " ");
644
645 WriteIndent(ts, indent);
646
647 if (layer.GetLayoutObject().StyleRef().Visibility() == EVisibility::kHidden)
648 ts << "hidden ";
649
650 ts << "layer ";
651
652 if (behavior & kLayoutAsTextShowAddresses)
653 ts << static_cast<const void*>(&layer) << " ";
654
655 ts << adjusted_layout_bounds;
656
657 if (!adjusted_layout_bounds.IsEmpty()) {
658 if (!adjusted_background_clip_rect.Contains(adjusted_layout_bounds))
659 ts << " backgroundClip " << adjusted_background_clip_rect;
660 if (!adjusted_clip_rect.Contains(adjusted_layout_bounds))
661 ts << " clip " << adjusted_clip_rect;
662 }
663 if (layer.IsTransparent())
664 ts << " transparent";
665
666 if (layer.GetLayoutObject().HasOverflowClip()) {
667 PaintLayerScrollableArea* scrollable_area = layer.GetScrollableArea();
668 ScrollOffset adjusted_scroll_offset =
669 scrollable_area->GetScrollOffset() +
670 ToFloatSize(FloatPoint(scrollable_area->ScrollOrigin()));
671 if (adjusted_scroll_offset.Width())
672 ts << " scrollX " << adjusted_scroll_offset.Width();
673 if (adjusted_scroll_offset.Height())
674 ts << " scrollY " << adjusted_scroll_offset.Height();
675 if (layer.GetLayoutBox() &&
676 layer.GetLayoutBox()->PixelSnappedClientWidth() !=
677 layer.GetLayoutBox()->PixelSnappedScrollWidth())
678 ts << " scrollWidth " << layer.GetLayoutBox()->PixelSnappedScrollWidth();
679 if (layer.GetLayoutBox() &&
680 layer.GetLayoutBox()->PixelSnappedClientHeight() !=
681 layer.GetLayoutBox()->PixelSnappedScrollHeight())
682 ts << " scrollHeight "
683 << layer.GetLayoutBox()->PixelSnappedScrollHeight();
684 }
685
686 if (paint_phase == kLayerPaintPhaseBackground)
687 ts << " layerType: background only";
688 else if (paint_phase == kLayerPaintPhaseForeground)
689 ts << " layerType: foreground only";
690
691 if (layer.GetLayoutObject().StyleRef().HasBlendMode()) {
692 ts << " blendMode: "
693 << CompositeOperatorName(
694 kCompositeSourceOver,
695 layer.GetLayoutObject().StyleRef().GetBlendMode());
696 }
697
698 if (behavior & kLayoutAsTextShowCompositedLayers) {
699 if (layer.HasCompositedLayerMapping()) {
700 ts << " (composited, bounds="
701 << layer.GetCompositedLayerMapping()->CompositedBounds()
702 << ", drawsContent="
703 << layer.GetCompositedLayerMapping()
704 ->MainGraphicsLayer()
705 ->DrawsContent()
706 << (layer.ShouldIsolateCompositedDescendants()
707 ? ", isolatesCompositedBlending"
708 : "")
709 << ")";
710 }
711 }
712
713 if ((behavior & kLayoutAsTextShowPaintProperties) && layer.SelfNeedsRepaint())
714 ts << " needsRepaint";
715
716 ts << "\n";
717
718 if (paint_phase != kLayerPaintPhaseBackground)
719 Write(ts, layer.GetLayoutObject(), indent + 1, behavior);
720 }
721
ChildLayers(const PaintLayer * layer,PaintLayerIteration which_children)722 static Vector<PaintLayer*> ChildLayers(const PaintLayer* layer,
723 PaintLayerIteration which_children) {
724 Vector<PaintLayer*> vector;
725 PaintLayerPaintOrderIterator it(*layer, which_children);
726 while (PaintLayer* child = it.Next())
727 vector.push_back(child);
728 return vector;
729 }
730
WriteLayers(WTF::TextStream & ts,const PaintLayer * root_layer,PaintLayer * layer,int indent,LayoutAsTextBehavior behavior,const PaintLayer * marked_layer)731 void LayoutTreeAsText::WriteLayers(WTF::TextStream& ts,
732 const PaintLayer* root_layer,
733 PaintLayer* layer,
734 int indent,
735 LayoutAsTextBehavior behavior,
736 const PaintLayer* marked_layer) {
737 // Calculate the clip rects we should use.
738 PhysicalRect layer_bounds;
739 ClipRect damage_rect, clip_rect_to_apply;
740 if (layer->GetLayoutObject().FirstFragment().HasLocalBorderBoxProperties()) {
741 layer->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
742 .CalculateRects(
743 ClipRectsContext(root_layer,
744 &root_layer->GetLayoutObject().FirstFragment(),
745 kUncachedClipRects),
746 &layer->GetLayoutObject().FirstFragment(), nullptr, layer_bounds,
747 damage_rect, clip_rect_to_apply);
748 } else {
749 layer->Clipper(PaintLayer::GeometryMapperOption::kDoNotUseGeometryMapper)
750 .CalculateRects(
751 ClipRectsContext(root_layer, nullptr, kUncachedClipRects), nullptr,
752 nullptr, layer_bounds, damage_rect, clip_rect_to_apply);
753 }
754
755 PhysicalOffset offset_from_root;
756 layer->ConvertToLayerCoords(root_layer, offset_from_root);
757 bool should_paint =
758 (behavior & kLayoutAsTextShowAllLayers)
759 ? true
760 : layer->IntersectsDamageRect(layer_bounds, damage_rect.Rect(),
761 offset_from_root);
762
763 if (layer->GetLayoutObject().IsLayoutEmbeddedContent() &&
764 ToLayoutEmbeddedContent(layer->GetLayoutObject()).IsThrottledFrameView())
765 should_paint = false;
766
767 #if DCHECK_IS_ON()
768 if (layer->NeedsPositionUpdate()) {
769 WriteIndent(ts, indent);
770 ts << " NEEDS POSITION UPDATE\n";
771 }
772 #endif
773
774 const auto& neg_list = ChildLayers(layer, kNegativeZOrderChildren);
775 bool paints_background_separately = !neg_list.IsEmpty();
776 if (should_paint && paints_background_separately) {
777 Write(ts, *layer, layer_bounds, damage_rect.Rect(),
778 clip_rect_to_apply.Rect(), kLayerPaintPhaseBackground, indent,
779 behavior, marked_layer);
780 }
781
782 if (!neg_list.IsEmpty()) {
783 int curr_indent = indent;
784 if (behavior & kLayoutAsTextShowLayerNesting) {
785 WriteIndent(ts, indent);
786 ts << " negative z-order list(" << neg_list.size() << ")\n";
787 ++curr_indent;
788 }
789 for (auto* layer : neg_list)
790 WriteLayers(ts, root_layer, layer, curr_indent, behavior, marked_layer);
791 }
792
793 if (should_paint) {
794 Write(ts, *layer, layer_bounds, damage_rect.Rect(),
795 clip_rect_to_apply.Rect(),
796 paints_background_separately ? kLayerPaintPhaseForeground
797 : kLayerPaintPhaseAll,
798 indent, behavior, marked_layer);
799 }
800
801 const auto& normal_flow_list = ChildLayers(layer, kNormalFlowChildren);
802 if (!normal_flow_list.IsEmpty()) {
803 int curr_indent = indent;
804 if (behavior & kLayoutAsTextShowLayerNesting) {
805 WriteIndent(ts, indent);
806 ts << " normal flow list(" << normal_flow_list.size() << ")\n";
807 ++curr_indent;
808 }
809 for (auto* layer : normal_flow_list)
810 WriteLayers(ts, root_layer, layer, curr_indent, behavior, marked_layer);
811 }
812
813 const auto& pos_list = ChildLayers(layer, kPositiveZOrderChildren);
814 if (!pos_list.IsEmpty()) {
815 int curr_indent = indent;
816 if (behavior & kLayoutAsTextShowLayerNesting) {
817 WriteIndent(ts, indent);
818 ts << " positive z-order list(" << pos_list.size() << ")\n";
819 ++curr_indent;
820 }
821 for (auto* layer : pos_list)
822 WriteLayers(ts, root_layer, layer, curr_indent, behavior, marked_layer);
823 }
824 }
825
NodePosition(Node * node)826 static String NodePosition(Node* node) {
827 StringBuilder result;
828
829 Element* body = node->GetDocument().body();
830 Node* parent;
831 for (Node* n = node; n; n = parent) {
832 parent = n->ParentOrShadowHostNode();
833 if (n != node)
834 result.Append(" of ");
835 if (parent) {
836 if (body && n == body) {
837 // We don't care what offset body may be in the document.
838 result.Append("body");
839 break;
840 }
841 if (n->IsShadowRoot()) {
842 result.Append('{');
843 result.Append(GetTagName(n));
844 result.Append('}');
845 } else {
846 result.Append("child ");
847 result.AppendNumber(n->NodeIndex());
848 result.Append(" {");
849 result.Append(GetTagName(n));
850 result.Append('}');
851 }
852 } else {
853 result.Append("document");
854 }
855 }
856
857 return result.ToString();
858 }
859
WriteSelection(WTF::TextStream & ts,const LayoutObject * o)860 static void WriteSelection(WTF::TextStream& ts, const LayoutObject* o) {
861 Document* doc = DynamicTo<Document>(o->GetNode());
862 if (!doc)
863 return;
864
865 LocalFrame* frame = doc->GetFrame();
866 if (!frame)
867 return;
868
869 const VisibleSelection& selection =
870 frame->Selection().ComputeVisibleSelectionInDOMTree();
871 if (selection.IsCaret()) {
872 ts << "caret: position " << selection.Start().ComputeEditingOffset()
873 << " of " << NodePosition(selection.Start().AnchorNode());
874 if (selection.Affinity() == TextAffinity::kUpstream)
875 ts << " (upstream affinity)";
876 ts << "\n";
877 } else if (selection.IsRange()) {
878 ts << "selection start: position "
879 << selection.Start().ComputeEditingOffset() << " of "
880 << NodePosition(selection.Start().AnchorNode()) << "\n"
881 << "selection end: position " << selection.End().ComputeEditingOffset()
882 << " of " << NodePosition(selection.End().AnchorNode()) << "\n";
883 }
884 }
885
ExternalRepresentation(LayoutBox * layout_object,LayoutAsTextBehavior behavior,const PaintLayer * marked_layer=nullptr)886 static String ExternalRepresentation(LayoutBox* layout_object,
887 LayoutAsTextBehavior behavior,
888 const PaintLayer* marked_layer = nullptr) {
889 WTF::TextStream ts;
890 if (!layout_object->HasLayer())
891 return ts.Release();
892
893 PaintLayer* layer = layout_object->Layer();
894 LayoutTreeAsText::WriteLayers(ts, layer, layer, 0, behavior, marked_layer);
895 WriteSelection(ts, layout_object);
896 return ts.Release();
897 }
898
ExternalRepresentation(LocalFrame * frame,LayoutAsTextBehavior behavior,const PaintLayer * marked_layer)899 String ExternalRepresentation(LocalFrame* frame,
900 LayoutAsTextBehavior behavior,
901 const PaintLayer* marked_layer) {
902 if (!(behavior & kLayoutAsTextDontUpdateLayout)) {
903 bool success = frame->View()->UpdateAllLifecyclePhasesExceptPaint(
904 DocumentUpdateReason::kTest);
905 DCHECK(success);
906 };
907
908 LayoutObject* layout_object = frame->ContentLayoutObject();
909 if (!layout_object || !layout_object->IsBox())
910 return String();
911 LayoutBox* layout_box = ToLayoutBox(layout_object);
912
913 PrintContext print_context(frame, /*use_printing_layout=*/true);
914 bool is_text_printing_mode = !!(behavior & kLayoutAsTextPrintingMode);
915 if (is_text_printing_mode) {
916 print_context.BeginPrintMode(layout_box->ClientWidth(),
917 layout_box->ClientHeight());
918
919 // The lifecycle needs to be run again after changing printing mode,
920 // to account for any style updates due to media query change.
921 if (!(behavior & kLayoutAsTextDontUpdateLayout))
922 frame->View()->UpdateLifecyclePhasesForPrinting();
923 }
924
925 String representation = ExternalRepresentation(ToLayoutBox(layout_object),
926 behavior, marked_layer);
927 if (is_text_printing_mode)
928 print_context.EndPrintMode();
929 return representation;
930 }
931
ExternalRepresentation(Element * element,LayoutAsTextBehavior behavior)932 String ExternalRepresentation(Element* element, LayoutAsTextBehavior behavior) {
933 // Doesn't support printing mode.
934 DCHECK(!(behavior & kLayoutAsTextPrintingMode));
935 if (!(behavior & kLayoutAsTextDontUpdateLayout)) {
936 element->GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
937 }
938
939 LayoutObject* layout_object = element->GetLayoutObject();
940 if (!layout_object || !layout_object->IsBox())
941 return String();
942
943 return ExternalRepresentation(ToLayoutBox(layout_object),
944 behavior | kLayoutAsTextShowAllLayers);
945 }
946
WriteCounterValuesFromChildren(WTF::TextStream & stream,LayoutObject * parent,bool & is_first_counter)947 static void WriteCounterValuesFromChildren(WTF::TextStream& stream,
948 LayoutObject* parent,
949 bool& is_first_counter) {
950 for (LayoutObject* child = parent->SlowFirstChild(); child;
951 child = child->NextSibling()) {
952 if (child->IsCounter()) {
953 if (!is_first_counter)
954 stream << " ";
955 is_first_counter = false;
956 String str(ToLayoutText(child)->GetText());
957 stream << str;
958 }
959 }
960 }
961
CounterValueForElement(Element * element)962 String CounterValueForElement(Element* element) {
963 element->GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
964 WTF::TextStream stream;
965 bool is_first_counter = true;
966 // The counter LayoutObjects should be children of ::marker, ::before or
967 // ::after pseudo-elements.
968 if (LayoutObject* marker =
969 element->PseudoElementLayoutObject(kPseudoIdMarker))
970 WriteCounterValuesFromChildren(stream, marker, is_first_counter);
971 if (LayoutObject* before =
972 element->PseudoElementLayoutObject(kPseudoIdBefore))
973 WriteCounterValuesFromChildren(stream, before, is_first_counter);
974 if (LayoutObject* after = element->PseudoElementLayoutObject(kPseudoIdAfter))
975 WriteCounterValuesFromChildren(stream, after, is_first_counter);
976 return stream.Release();
977 }
978
MarkerTextForListItem(Element * element)979 String MarkerTextForListItem(Element* element) {
980 element->GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
981
982 LayoutObject* layout_object = element->GetLayoutObject();
983 if (layout_object) {
984 if (layout_object->IsListItem())
985 return ToLayoutListItem(layout_object)->MarkerText();
986 if (layout_object->IsLayoutNGListItem()) {
987 if (LayoutObject* marker = ToLayoutNGListItem(layout_object)->Marker())
988 return ListMarker::Get(marker)->MarkerTextWithoutSuffix(*marker);
989 }
990 }
991 return String();
992 }
993
994 } // namespace blink
995