1 | /*
|
---|
2 | * Copyright (C) 2004-2017 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 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 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 "config.h"
|
---|
27 | #include "RenderTreeAsText.h"
|
---|
28 |
|
---|
29 | #include "ClipRect.h"
|
---|
30 | #include "ColorSerialization.h"
|
---|
31 | #include "Document.h"
|
---|
32 | #include "ElementInlines.h"
|
---|
33 | #include "Frame.h"
|
---|
34 | #include "FrameSelection.h"
|
---|
35 | #include "FrameView.h"
|
---|
36 | #include "HTMLElement.h"
|
---|
37 | #include "HTMLNames.h"
|
---|
38 | #include "HTMLSpanElement.h"
|
---|
39 | #include "InlineIteratorTextBox.h"
|
---|
40 | #include "LegacyInlineTextBox.h"
|
---|
41 | #include "LegacyRenderSVGContainer.h"
|
---|
42 | #include "LegacyRenderSVGRoot.h"
|
---|
43 | #include "LegacyRenderSVGShape.h"
|
---|
44 | #include "Logging.h"
|
---|
45 | #include "PrintContext.h"
|
---|
46 | #include "PseudoElement.h"
|
---|
47 | #include "RenderBlockFlow.h"
|
---|
48 | #include "RenderCounter.h"
|
---|
49 | #include "RenderDetailsMarker.h"
|
---|
50 | #include "RenderFileUploadControl.h"
|
---|
51 | #include "RenderFragmentContainer.h"
|
---|
52 | #include "RenderInline.h"
|
---|
53 | #include "RenderIterator.h"
|
---|
54 | #include "RenderLayer.h"
|
---|
55 | #include "RenderLayerBacking.h"
|
---|
56 | #include "RenderLayerScrollableArea.h"
|
---|
57 | #include "RenderLineBreak.h"
|
---|
58 | #include "RenderListItem.h"
|
---|
59 | #include "RenderListMarker.h"
|
---|
60 | #include "RenderQuote.h"
|
---|
61 | #include "RenderRuby.h"
|
---|
62 | #include "RenderSVGContainer.h"
|
---|
63 | #include "RenderSVGGradientStop.h"
|
---|
64 | #include "RenderSVGImage.h"
|
---|
65 | #include "RenderSVGInlineText.h"
|
---|
66 | #include "RenderSVGResourceContainer.h"
|
---|
67 | #include "RenderSVGRoot.h"
|
---|
68 | #include "RenderSVGShape.h"
|
---|
69 | #include "RenderSVGText.h"
|
---|
70 | #include "RenderTableCell.h"
|
---|
71 | #include "RenderView.h"
|
---|
72 | #include "RenderWidget.h"
|
---|
73 | #include "SVGRenderTreeAsText.h"
|
---|
74 | #include "ShadowRoot.h"
|
---|
75 | #include "StyleProperties.h"
|
---|
76 | #include <wtf/HexNumber.h>
|
---|
77 | #include <wtf/Vector.h>
|
---|
78 | #include <wtf/text/TextStream.h>
|
---|
79 | #include <wtf/unicode/CharacterNames.h>
|
---|
80 |
|
---|
81 | #if PLATFORM(MAC)
|
---|
82 | #include "ScrollbarThemeMac.h"
|
---|
83 | #endif
|
---|
84 |
|
---|
85 | namespace WebCore {
|
---|
86 |
|
---|
87 | using namespace HTMLNames;
|
---|
88 |
|
---|
89 | static void writeLayers(TextStream&, const RenderLayer& rootLayer, RenderLayer&, const LayoutRect& paintDirtyRect, OptionSet<RenderAsTextFlag>);
|
---|
90 |
|
---|
91 | static void printBorderStyle(TextStream& ts, const BorderStyle borderStyle)
|
---|
92 | {
|
---|
93 | switch (borderStyle) {
|
---|
94 | case BorderStyle::None:
|
---|
95 | ts << "none";
|
---|
96 | break;
|
---|
97 | case BorderStyle::Hidden:
|
---|
98 | ts << "hidden";
|
---|
99 | break;
|
---|
100 | case BorderStyle::Inset:
|
---|
101 | ts << "inset";
|
---|
102 | break;
|
---|
103 | case BorderStyle::Groove:
|
---|
104 | ts << "groove";
|
---|
105 | break;
|
---|
106 | case BorderStyle::Ridge:
|
---|
107 | ts << "ridge";
|
---|
108 | break;
|
---|
109 | case BorderStyle::Outset:
|
---|
110 | ts << "outset";
|
---|
111 | break;
|
---|
112 | case BorderStyle::Dotted:
|
---|
113 | ts << "dotted";
|
---|
114 | break;
|
---|
115 | case BorderStyle::Dashed:
|
---|
116 | ts << "dashed";
|
---|
117 | break;
|
---|
118 | case BorderStyle::Solid:
|
---|
119 | ts << "solid";
|
---|
120 | break;
|
---|
121 | case BorderStyle::Double:
|
---|
122 | ts << "double";
|
---|
123 | break;
|
---|
124 | }
|
---|
125 |
|
---|
126 | ts << " ";
|
---|
127 | }
|
---|
128 |
|
---|
129 | static String getTagName(Node* n)
|
---|
130 | {
|
---|
131 | if (n->isDocumentNode())
|
---|
132 | return ""_s;
|
---|
133 | if (n->nodeType() == Node::COMMENT_NODE)
|
---|
134 | return "COMMENT"_s;
|
---|
135 | return n->nodeName();
|
---|
136 | }
|
---|
137 |
|
---|
138 | static bool isEmptyOrUnstyledAppleStyleSpan(const Node* node)
|
---|
139 | {
|
---|
140 | if (!is<HTMLSpanElement>(node))
|
---|
141 | return false;
|
---|
142 |
|
---|
143 | const HTMLElement& element = downcast<HTMLSpanElement>(*node);
|
---|
144 | if (element.getAttribute(classAttr) != "Apple-style-span"_s)
|
---|
145 | return false;
|
---|
146 |
|
---|
147 | if (!node->hasChildNodes())
|
---|
148 | return true;
|
---|
149 |
|
---|
150 | const StyleProperties* inlineStyleDecl = element.inlineStyle();
|
---|
151 | return (!inlineStyleDecl || inlineStyleDecl->isEmpty());
|
---|
152 | }
|
---|
153 |
|
---|
154 | String quoteAndEscapeNonPrintables(StringView s)
|
---|
155 | {
|
---|
156 | StringBuilder result;
|
---|
157 | result.append('"');
|
---|
158 | for (unsigned i = 0; i != s.length(); ++i) {
|
---|
159 | UChar c = s[i];
|
---|
160 | if (c == '\\') {
|
---|
161 | result.append("\\\\");
|
---|
162 | } else if (c == '"') {
|
---|
163 | result.append("\\\"");
|
---|
164 | } else if (c == '\n' || c == noBreakSpace)
|
---|
165 | result.append(' ');
|
---|
166 | else {
|
---|
167 | if (c >= 0x20 && c < 0x7F)
|
---|
168 | result.append(c);
|
---|
169 | else
|
---|
170 | result.append("\\x{", hex(c), '}');
|
---|
171 | }
|
---|
172 | }
|
---|
173 | result.append('"');
|
---|
174 | return result.toString();
|
---|
175 | }
|
---|
176 |
|
---|
177 | static inline bool isRenderInlineEmpty(const RenderInline& inlineRenderer)
|
---|
178 | {
|
---|
179 | if (isEmptyInline(inlineRenderer))
|
---|
180 | return true;
|
---|
181 |
|
---|
182 | for (auto& child : childrenOfType<RenderObject>(inlineRenderer)) {
|
---|
183 | if (child.isFloatingOrOutOfFlowPositioned())
|
---|
184 | continue;
|
---|
185 | auto isChildEmpty = false;
|
---|
186 | if (is<RenderInline>(child))
|
---|
187 | isChildEmpty = isRenderInlineEmpty(downcast<RenderInline>(child));
|
---|
188 | else if (is<RenderText>(child))
|
---|
189 | isChildEmpty = !downcast<RenderText>(child).linesBoundingBox().height();
|
---|
190 | if (!isChildEmpty)
|
---|
191 | return false;
|
---|
192 | }
|
---|
193 | return true;
|
---|
194 | }
|
---|
195 |
|
---|
196 | static inline bool hasNonEmptySibling(const RenderInline& inlineRenderer)
|
---|
197 | {
|
---|
198 | auto* parent = inlineRenderer.parent();
|
---|
199 | if (!parent)
|
---|
200 | return false;
|
---|
201 |
|
---|
202 | for (auto& sibling : childrenOfType<RenderObject>(*parent)) {
|
---|
203 | if (&sibling == &inlineRenderer || sibling.isFloatingOrOutOfFlowPositioned())
|
---|
204 | continue;
|
---|
205 | if (!is<RenderInline>(sibling))
|
---|
206 | return true;
|
---|
207 | auto& siblingRendererInline = downcast<RenderInline>(sibling);
|
---|
208 | if (siblingRendererInline.mayAffectLayout() || !isRenderInlineEmpty(siblingRendererInline))
|
---|
209 | return true;
|
---|
210 | }
|
---|
211 | return false;
|
---|
212 | }
|
---|
213 |
|
---|
214 | void RenderTreeAsText::writeRenderObject(TextStream& ts, const RenderObject& o, OptionSet<RenderAsTextFlag> behavior)
|
---|
215 | {
|
---|
216 | ts << o.renderName().characters();
|
---|
217 |
|
---|
218 | if (behavior.contains(RenderAsTextFlag::ShowAddresses))
|
---|
219 | ts << " " << &o;
|
---|
220 |
|
---|
221 | if (o.style().usedZIndex()) // FIXME: This should use !hasAutoUsedZIndex().
|
---|
222 | ts << " zI: " << o.style().usedZIndex();
|
---|
223 |
|
---|
224 | if (o.node()) {
|
---|
225 | String tagName = getTagName(o.node());
|
---|
226 | // FIXME: Temporary hack to make tests pass by simulating the old generated content output.
|
---|
227 | if (o.isPseudoElement() || (o.parent() && o.parent()->isPseudoElement()))
|
---|
228 | tagName = emptyAtom();
|
---|
229 | if (!tagName.isEmpty()) {
|
---|
230 | ts << " {" << tagName << "}";
|
---|
231 | // flag empty or unstyled AppleStyleSpan because we never
|
---|
232 | // want to leave them in the DOM
|
---|
233 | if (isEmptyOrUnstyledAppleStyleSpan(o.node()))
|
---|
234 | ts << " *empty or unstyled AppleStyleSpan*";
|
---|
235 | }
|
---|
236 | }
|
---|
237 |
|
---|
238 | RenderBlock* cb = o.containingBlock();
|
---|
239 | bool adjustForTableCells = cb ? cb->isTableCell() : false;
|
---|
240 |
|
---|
241 | LayoutRect r;
|
---|
242 | if (is<RenderText>(o)) {
|
---|
243 | // FIXME: Would be better to dump the bounding box x and y rather than the first run's x and y, but that would involve updating
|
---|
244 | // many test results.
|
---|
245 | const RenderText& text = downcast<RenderText>(o);
|
---|
246 | r = IntRect(text.firstRunLocation(), text.linesBoundingBox().size());
|
---|
247 | if (!InlineIterator::firstTextBoxFor(text))
|
---|
248 | adjustForTableCells = false;
|
---|
249 | } else if (o.isBR()) {
|
---|
250 | const RenderLineBreak& br = downcast<RenderLineBreak>(o);
|
---|
251 | IntRect linesBox = br.linesBoundingBox();
|
---|
252 | r = IntRect(linesBox.x(), linesBox.y(), linesBox.width(), linesBox.height());
|
---|
253 | if (!br.inlineBoxWrapper() && !InlineIterator::boxFor(br))
|
---|
254 | adjustForTableCells = false;
|
---|
255 | } else if (is<RenderInline>(o)) {
|
---|
256 | const RenderInline& inlineFlow = downcast<RenderInline>(o);
|
---|
257 | // FIXME: Would be better not to just dump 0, 0 as the x and y here.
|
---|
258 | auto width = inlineFlow.linesBoundingBox().width();
|
---|
259 | auto inlineHeight = [&] {
|
---|
260 | // Let's match legacy line layout's RenderInline behavior and report 0 height when the inline box is "empty".
|
---|
261 | // FIXME: Remove and rebaseline when LFC inline boxes are enabled (see webkit.org/b/220722)
|
---|
262 | auto height = inlineFlow.linesBoundingBox().height();
|
---|
263 | if (width)
|
---|
264 | return height;
|
---|
265 | if (is<RenderQuote>(inlineFlow) || is<RenderRubyAsInline>(inlineFlow))
|
---|
266 | return height;
|
---|
267 | if (inlineFlow.marginStart() || inlineFlow.marginEnd())
|
---|
268 | return height;
|
---|
269 | // This is mostly pre/post continuation content. Also see webkit.org/b/220735
|
---|
270 | if (hasNonEmptySibling(inlineFlow))
|
---|
271 | return height;
|
---|
272 | if (isRenderInlineEmpty(inlineFlow))
|
---|
273 | return 0;
|
---|
274 | return height;
|
---|
275 | };
|
---|
276 | r = IntRect(0, 0, width, inlineHeight());
|
---|
277 | adjustForTableCells = false;
|
---|
278 | } else if (is<RenderTableCell>(o)) {
|
---|
279 | // FIXME: Deliberately dump the "inner" box of table cells, since that is what current results reflect. We'd like
|
---|
280 | // to clean up the results to dump both the outer box and the intrinsic padding so that both bits of information are
|
---|
281 | // captured by the results.
|
---|
282 | const RenderTableCell& cell = downcast<RenderTableCell>(o);
|
---|
283 | r = LayoutRect(cell.x(), cell.y() + cell.intrinsicPaddingBefore(), cell.width(), cell.height() - cell.intrinsicPaddingBefore() - cell.intrinsicPaddingAfter());
|
---|
284 | } else if (is<RenderBox>(o))
|
---|
285 | r = downcast<RenderBox>(o).frameRect();
|
---|
286 |
|
---|
287 | // FIXME: Temporary in order to ensure compatibility with existing layout test results.
|
---|
288 | if (adjustForTableCells)
|
---|
289 | r.move(0_lu, -downcast<RenderTableCell>(*o.containingBlock()).intrinsicPaddingBefore());
|
---|
290 |
|
---|
291 | // FIXME: Convert layout test results to report sub-pixel values, in the meantime using enclosingIntRect
|
---|
292 | // for consistency with old results.
|
---|
293 | ts << " " << enclosingIntRect(r);
|
---|
294 |
|
---|
295 | if (!is<RenderText>(o)) {
|
---|
296 | if (is<RenderFileUploadControl>(o))
|
---|
297 | ts << " " << quoteAndEscapeNonPrintables(downcast<RenderFileUploadControl>(o).fileTextValue());
|
---|
298 |
|
---|
299 | if (o.parent()) {
|
---|
300 | Color color = o.style().visitedDependentColor(CSSPropertyColor);
|
---|
301 | if (!equalIgnoringSemanticColor(o.parent()->style().visitedDependentColor(CSSPropertyColor), color))
|
---|
302 | ts << " [color=" << serializationForRenderTreeAsText(color) << "]";
|
---|
303 |
|
---|
304 | // Do not dump invalid or transparent backgrounds, since that is the default.
|
---|
305 | Color backgroundColor = o.style().visitedDependentColor(CSSPropertyBackgroundColor);
|
---|
306 | if (!equalIgnoringSemanticColor(o.parent()->style().visitedDependentColor(CSSPropertyBackgroundColor), backgroundColor)
|
---|
307 | && backgroundColor != Color::transparentBlack)
|
---|
308 | ts << " [bgcolor=" << serializationForRenderTreeAsText(backgroundColor) << "]";
|
---|
309 |
|
---|
310 | Color textFillColor = o.style().visitedDependentColor(CSSPropertyWebkitTextFillColor);
|
---|
311 | if (!equalIgnoringSemanticColor(o.parent()->style().visitedDependentColor(CSSPropertyWebkitTextFillColor), textFillColor)
|
---|
312 | && textFillColor != color && textFillColor != Color::transparentBlack)
|
---|
313 | ts << " [textFillColor=" << serializationForRenderTreeAsText(textFillColor) << "]";
|
---|
314 |
|
---|
315 | Color textStrokeColor = o.style().visitedDependentColor(CSSPropertyWebkitTextStrokeColor);
|
---|
316 | if (!equalIgnoringSemanticColor(o.parent()->style().visitedDependentColor(CSSPropertyWebkitTextStrokeColor), textStrokeColor)
|
---|
317 | && textStrokeColor != color && textStrokeColor != Color::transparentBlack)
|
---|
318 | ts << " [textStrokeColor=" << serializationForRenderTreeAsText(textStrokeColor) << "]";
|
---|
319 |
|
---|
320 | if (o.parent()->style().textStrokeWidth() != o.style().textStrokeWidth() && o.style().textStrokeWidth() > 0)
|
---|
321 | ts << " [textStrokeWidth=" << o.style().textStrokeWidth() << "]";
|
---|
322 | }
|
---|
323 |
|
---|
324 | if (!is<RenderBoxModelObject>(o) || is<RenderLineBreak>(o))
|
---|
325 | return;
|
---|
326 |
|
---|
327 | const RenderBoxModelObject& box = downcast<RenderBoxModelObject>(o);
|
---|
328 | LayoutUnit borderTop = box.borderTop();
|
---|
329 | LayoutUnit borderRight = box.borderRight();
|
---|
330 | LayoutUnit borderBottom = box.borderBottom();
|
---|
331 | LayoutUnit borderLeft = box.borderLeft();
|
---|
332 | bool overridden = o.style().borderImage().overridesBorderWidths();
|
---|
333 | if (box.isFieldset()) {
|
---|
334 | const auto& block = downcast<RenderBlock>(box);
|
---|
335 | if (o.style().writingMode() == WritingMode::TopToBottom)
|
---|
336 | borderTop -= block.intrinsicBorderForFieldset();
|
---|
337 | else if (o.style().writingMode() == WritingMode::BottomToTop)
|
---|
338 | borderBottom -= block.intrinsicBorderForFieldset();
|
---|
339 | else if (o.style().writingMode() == WritingMode::LeftToRight)
|
---|
340 | borderLeft -= block.intrinsicBorderForFieldset();
|
---|
341 | else if (o.style().writingMode() == WritingMode::RightToLeft)
|
---|
342 | borderRight -= block.intrinsicBorderForFieldset();
|
---|
343 |
|
---|
344 | }
|
---|
345 | if (borderTop || borderRight || borderBottom || borderLeft) {
|
---|
346 | ts << " [border:";
|
---|
347 |
|
---|
348 | BorderValue prevBorder = o.style().borderTop();
|
---|
349 | if (!borderTop)
|
---|
350 | ts << " none";
|
---|
351 | else {
|
---|
352 | ts << " (" << borderTop << "px ";
|
---|
353 | printBorderStyle(ts, o.style().borderTopStyle());
|
---|
354 | auto color = o.style().borderTopColor();
|
---|
355 | if (!color.isValid())
|
---|
356 | color = o.style().color();
|
---|
357 | ts << serializationForRenderTreeAsText(color) << ")";
|
---|
358 | }
|
---|
359 |
|
---|
360 | if (o.style().borderRight() != prevBorder || (overridden && borderRight != borderTop)) {
|
---|
361 | prevBorder = o.style().borderRight();
|
---|
362 | if (!borderRight)
|
---|
363 | ts << " none";
|
---|
364 | else {
|
---|
365 | ts << " (" << borderRight << "px ";
|
---|
366 | printBorderStyle(ts, o.style().borderRightStyle());
|
---|
367 | auto color = o.style().borderRightColor();
|
---|
368 | if (!color.isValid())
|
---|
369 | color = o.style().color();
|
---|
370 | ts << serializationForRenderTreeAsText(color) << ")";
|
---|
371 | }
|
---|
372 | }
|
---|
373 |
|
---|
374 | if (o.style().borderBottom() != prevBorder || (overridden && borderBottom != borderRight)) {
|
---|
375 | prevBorder = box.style().borderBottom();
|
---|
376 | if (!borderBottom)
|
---|
377 | ts << " none";
|
---|
378 | else {
|
---|
379 | ts << " (" << borderBottom << "px ";
|
---|
380 | printBorderStyle(ts, o.style().borderBottomStyle());
|
---|
381 | auto color = o.style().borderBottomColor();
|
---|
382 | if (!color.isValid())
|
---|
383 | color = o.style().color();
|
---|
384 | ts << serializationForRenderTreeAsText(color) << ")";
|
---|
385 | }
|
---|
386 | }
|
---|
387 |
|
---|
388 | if (o.style().borderLeft() != prevBorder || (overridden && borderLeft != borderBottom)) {
|
---|
389 | prevBorder = o.style().borderLeft();
|
---|
390 | if (!borderLeft)
|
---|
391 | ts << " none";
|
---|
392 | else {
|
---|
393 | ts << " (" << borderLeft << "px ";
|
---|
394 | printBorderStyle(ts, o.style().borderLeftStyle());
|
---|
395 | auto color = o.style().borderLeftColor();
|
---|
396 | if (!color.isValid())
|
---|
397 | color = o.style().color();
|
---|
398 | ts << serializationForRenderTreeAsText(color) << ")";
|
---|
399 | }
|
---|
400 | }
|
---|
401 |
|
---|
402 | ts << "]";
|
---|
403 | }
|
---|
404 |
|
---|
405 | #if ENABLE(MATHML)
|
---|
406 | // We want to show any layout padding, both CSS padding and intrinsic padding, so we can't just check o.style().hasPadding().
|
---|
407 | if (o.isRenderMathMLBlock() && (box.paddingTop() || box.paddingRight() || box.paddingBottom() || box.paddingLeft())) {
|
---|
408 | ts << " [";
|
---|
409 | LayoutUnit cssTop = box.computedCSSPaddingTop();
|
---|
410 | LayoutUnit cssRight = box.computedCSSPaddingRight();
|
---|
411 | LayoutUnit cssBottom = box.computedCSSPaddingBottom();
|
---|
412 | LayoutUnit cssLeft = box.computedCSSPaddingLeft();
|
---|
413 | if (box.paddingTop() != cssTop || box.paddingRight() != cssRight || box.paddingBottom() != cssBottom || box.paddingLeft() != cssLeft) {
|
---|
414 | ts << "intrinsic ";
|
---|
415 | if (cssTop || cssRight || cssBottom || cssLeft)
|
---|
416 | ts << "+ CSS ";
|
---|
417 | }
|
---|
418 | ts << "padding: " << roundToInt(box.paddingTop()) << " " << roundToInt(box.paddingRight()) << " " << roundToInt(box.paddingBottom()) << " " << roundToInt(box.paddingLeft()) << "]";
|
---|
419 | }
|
---|
420 | #endif
|
---|
421 | }
|
---|
422 |
|
---|
423 | if (is<RenderTableCell>(o)) {
|
---|
424 | const RenderTableCell& c = downcast<RenderTableCell>(o);
|
---|
425 | ts << " [r=" << c.rowIndex() << " c=" << c.col() << " rs=" << c.rowSpan() << " cs=" << c.colSpan() << "]";
|
---|
426 | }
|
---|
427 |
|
---|
428 | if (is<RenderDetailsMarker>(o)) {
|
---|
429 | ts << ": ";
|
---|
430 | switch (downcast<RenderDetailsMarker>(o).orientation()) {
|
---|
431 | case RenderDetailsMarker::Left:
|
---|
432 | ts << "left";
|
---|
433 | break;
|
---|
434 | case RenderDetailsMarker::Right:
|
---|
435 | ts << "right";
|
---|
436 | break;
|
---|
437 | case RenderDetailsMarker::Up:
|
---|
438 | ts << "up";
|
---|
439 | break;
|
---|
440 | case RenderDetailsMarker::Down:
|
---|
441 | ts << "down";
|
---|
442 | break;
|
---|
443 | }
|
---|
444 | }
|
---|
445 |
|
---|
446 | if (is<RenderListMarker>(o)) {
|
---|
447 | String text = downcast<RenderListMarker>(o).textWithoutSuffix().toString();
|
---|
448 | if (!text.isEmpty()) {
|
---|
449 | if (text.length() != 1)
|
---|
450 | text = quoteAndEscapeNonPrintables(text);
|
---|
451 | else {
|
---|
452 | switch (text[0]) {
|
---|
453 | case bullet:
|
---|
454 | text = "bullet"_s;
|
---|
455 | break;
|
---|
456 | case blackSquare:
|
---|
457 | text = "black square"_s;
|
---|
458 | break;
|
---|
459 | case whiteBullet:
|
---|
460 | text = "white bullet"_s;
|
---|
461 | break;
|
---|
462 | default:
|
---|
463 | text = quoteAndEscapeNonPrintables(text);
|
---|
464 | }
|
---|
465 | }
|
---|
466 | ts << ": " << text;
|
---|
467 | }
|
---|
468 | }
|
---|
469 |
|
---|
470 | writeDebugInfo(ts, o, behavior);
|
---|
471 | }
|
---|
472 |
|
---|
473 | void writeDebugInfo(TextStream& ts, const RenderObject& object, OptionSet<RenderAsTextFlag> behavior)
|
---|
474 | {
|
---|
475 | if (behavior.contains(RenderAsTextFlag::ShowIDAndClass)) {
|
---|
476 | if (auto* element = dynamicDowncast<Element>(object.node())) {
|
---|
477 | if (element->hasID())
|
---|
478 | ts << " id=\"" << element->getIdAttribute() << "\"";
|
---|
479 |
|
---|
480 | if (element->hasClass()) {
|
---|
481 | ts << " class=\"";
|
---|
482 | for (size_t i = 0; i < element->classNames().size(); ++i) {
|
---|
483 | if (i > 0)
|
---|
484 | ts << " ";
|
---|
485 | ts << element->classNames()[i];
|
---|
486 | }
|
---|
487 | ts << "\"";
|
---|
488 | }
|
---|
489 | }
|
---|
490 | }
|
---|
491 |
|
---|
492 | if (behavior.contains(RenderAsTextFlag::ShowLayoutState)) {
|
---|
493 | bool needsLayout = object.selfNeedsLayout() || object.needsPositionedMovementLayout() || object.posChildNeedsLayout() || object.normalChildNeedsLayout();
|
---|
494 | if (needsLayout)
|
---|
495 | ts << " (needs layout:";
|
---|
496 |
|
---|
497 | bool havePrevious = false;
|
---|
498 | if (object.selfNeedsLayout()) {
|
---|
499 | ts << " self";
|
---|
500 | havePrevious = true;
|
---|
501 | }
|
---|
502 |
|
---|
503 | if (object.needsPositionedMovementLayout()) {
|
---|
504 | if (havePrevious)
|
---|
505 | ts << ",";
|
---|
506 | havePrevious = true;
|
---|
507 | ts << " positioned movement";
|
---|
508 | }
|
---|
509 |
|
---|
510 | if (object.normalChildNeedsLayout()) {
|
---|
511 | if (havePrevious)
|
---|
512 | ts << ",";
|
---|
513 | havePrevious = true;
|
---|
514 | ts << " child";
|
---|
515 | }
|
---|
516 |
|
---|
517 | if (object.posChildNeedsLayout()) {
|
---|
518 | if (havePrevious)
|
---|
519 | ts << ",";
|
---|
520 | ts << " positioned child";
|
---|
521 | }
|
---|
522 |
|
---|
523 | if (needsLayout)
|
---|
524 | ts << ")";
|
---|
525 | }
|
---|
526 |
|
---|
527 | if (behavior.contains(RenderAsTextFlag::ShowOverflow) && is<RenderBox>(object)) {
|
---|
528 | const auto& box = downcast<RenderBox>(object);
|
---|
529 | if (box.hasRenderOverflow()) {
|
---|
530 | LayoutRect layoutOverflow = box.layoutOverflowRect();
|
---|
531 | ts << " (layout overflow " << layoutOverflow.x().toInt() << "," << layoutOverflow.y().toInt() << " " << layoutOverflow.width().toInt() << "x" << layoutOverflow.height().toInt() << ")";
|
---|
532 |
|
---|
533 | if (box.hasVisualOverflow()) {
|
---|
534 | LayoutRect visualOverflow = box.visualOverflowRect();
|
---|
535 | ts << " (visual overflow " << visualOverflow.x().toInt() << "," << visualOverflow.y().toInt() << " " << visualOverflow.width().toInt() << "x" << visualOverflow.height().toInt() << ")";
|
---|
536 | }
|
---|
537 | }
|
---|
538 | }
|
---|
539 | }
|
---|
540 |
|
---|
541 | void write(TextStream& ts, const RenderObject& o, OptionSet<RenderAsTextFlag> behavior)
|
---|
542 | {
|
---|
543 | auto writeTextRun = [&](auto& textRenderer, auto& textRun)
|
---|
544 | {
|
---|
545 | auto rect = textRun.visualRectIgnoringBlockDirection();
|
---|
546 | int x = rect.x();
|
---|
547 | int y = rect.y();
|
---|
548 | // FIXME: Use non-logical width. webkit.org/b/206809.
|
---|
549 | int logicalWidth = ceilf(rect.x() + (textRun.isHorizontal() ? rect.width() : rect.height())) - x;
|
---|
550 | // FIXME: Table cell adjustment is temporary until results can be updated.
|
---|
551 | if (is<RenderTableCell>(*o.containingBlock()))
|
---|
552 | y -= floorToInt(downcast<RenderTableCell>(*o.containingBlock()).intrinsicPaddingBefore());
|
---|
553 |
|
---|
554 | ts << "text run at (" << x << "," << y << ") width " << logicalWidth;
|
---|
555 | if (!textRun.isLeftToRightDirection())
|
---|
556 | ts << " RTL";
|
---|
557 | ts << ": "
|
---|
558 | << quoteAndEscapeNonPrintables(textRun.text());
|
---|
559 | if (textRun.hasHyphen())
|
---|
560 | ts << " + hyphen string " << quoteAndEscapeNonPrintables(textRenderer.style().hyphenString().string());
|
---|
561 | ts << "\n";
|
---|
562 | };
|
---|
563 |
|
---|
564 | #if ENABLE(LAYER_BASED_SVG_ENGINE)
|
---|
565 | if (is<RenderSVGShape>(o)) {
|
---|
566 | write(ts, downcast<RenderSVGShape>(o), behavior);
|
---|
567 | return;
|
---|
568 | }
|
---|
569 | #endif
|
---|
570 | if (is<LegacyRenderSVGShape>(o)) {
|
---|
571 | write(ts, downcast<LegacyRenderSVGShape>(o), behavior);
|
---|
572 | return;
|
---|
573 | }
|
---|
574 | if (is<RenderSVGGradientStop>(o)) {
|
---|
575 | writeSVGGradientStop(ts, downcast<RenderSVGGradientStop>(o), behavior);
|
---|
576 | return;
|
---|
577 | }
|
---|
578 | if (is<RenderSVGResourceContainer>(o)) {
|
---|
579 | writeSVGResourceContainer(ts, downcast<RenderSVGResourceContainer>(o), behavior);
|
---|
580 | return;
|
---|
581 | }
|
---|
582 | #if ENABLE(LAYER_BASED_SVG_ENGINE)
|
---|
583 | if (is<RenderSVGContainer>(o)) {
|
---|
584 | writeSVGContainer(ts, downcast<RenderSVGContainer>(o), behavior);
|
---|
585 | return;
|
---|
586 | }
|
---|
587 | #endif
|
---|
588 | if (is<LegacyRenderSVGContainer>(o)) {
|
---|
589 | writeSVGContainer(ts, downcast<LegacyRenderSVGContainer>(o), behavior);
|
---|
590 | return;
|
---|
591 | }
|
---|
592 | #if ENABLE(LAYER_BASED_SVG_ENGINE)
|
---|
593 | if (is<RenderSVGRoot>(o)) {
|
---|
594 | write(ts, downcast<RenderSVGRoot>(o), behavior);
|
---|
595 | return;
|
---|
596 | }
|
---|
597 | #endif
|
---|
598 | if (is<LegacyRenderSVGRoot>(o)) {
|
---|
599 | write(ts, downcast<LegacyRenderSVGRoot>(o), behavior);
|
---|
600 | return;
|
---|
601 | }
|
---|
602 | if (is<RenderSVGText>(o)) {
|
---|
603 | writeSVGText(ts, downcast<RenderSVGText>(o), behavior);
|
---|
604 | return;
|
---|
605 | }
|
---|
606 | if (is<RenderSVGInlineText>(o)) {
|
---|
607 | writeSVGInlineText(ts, downcast<RenderSVGInlineText>(o), behavior);
|
---|
608 | return;
|
---|
609 | }
|
---|
610 | if (is<RenderSVGImage>(o)) {
|
---|
611 | writeSVGImage(ts, downcast<RenderSVGImage>(o), behavior);
|
---|
612 | return;
|
---|
613 | }
|
---|
614 |
|
---|
615 | ts << indent;
|
---|
616 |
|
---|
617 | RenderTreeAsText::writeRenderObject(ts, o, behavior);
|
---|
618 | ts << "\n";
|
---|
619 |
|
---|
620 | TextStream::IndentScope indentScope(ts);
|
---|
621 |
|
---|
622 | if (is<RenderText>(o)) {
|
---|
623 | auto& text = downcast<RenderText>(o);
|
---|
624 | for (auto& run : InlineIterator::textBoxesFor(text)) {
|
---|
625 | ts << indent;
|
---|
626 | writeTextRun(text, run);
|
---|
627 | }
|
---|
628 | } else {
|
---|
629 | for (auto& child : childrenOfType<RenderObject>(downcast<RenderElement>(o))) {
|
---|
630 | if (child.hasLayer())
|
---|
631 | continue;
|
---|
632 | write(ts, child, behavior);
|
---|
633 | }
|
---|
634 | }
|
---|
635 |
|
---|
636 | if (is<RenderWidget>(o)) {
|
---|
637 | Widget* widget = downcast<RenderWidget>(o).widget();
|
---|
638 | if (is<FrameView>(widget)) {
|
---|
639 | FrameView& view = downcast<FrameView>(*widget);
|
---|
640 | if (RenderView* root = view.frame().contentRenderer()) {
|
---|
641 | if (!(behavior.contains(RenderAsTextFlag::DontUpdateLayout)))
|
---|
642 | view.layoutContext().layout();
|
---|
643 | if (RenderLayer* layer = root->layer())
|
---|
644 | writeLayers(ts, *layer, *layer, layer->rect(), behavior);
|
---|
645 | }
|
---|
646 | }
|
---|
647 | }
|
---|
648 | }
|
---|
649 |
|
---|
650 | enum LayerPaintPhase {
|
---|
651 | LayerPaintPhaseAll = 0,
|
---|
652 | LayerPaintPhaseBackground = -1,
|
---|
653 | LayerPaintPhaseForeground = 1
|
---|
654 | };
|
---|
655 |
|
---|
656 | static void writeLayer(TextStream& ts, const RenderLayer& layer, const LayoutRect& layerBounds, const LayoutRect& backgroundClipRect, const LayoutRect& clipRect,
|
---|
657 | LayerPaintPhase paintPhase = LayerPaintPhaseAll, OptionSet<RenderAsTextFlag> behavior = { })
|
---|
658 | {
|
---|
659 | IntRect adjustedLayoutBounds = snappedIntRect(layerBounds);
|
---|
660 | IntRect adjustedBackgroundClipRect = snappedIntRect(backgroundClipRect);
|
---|
661 | IntRect adjustedClipRect = snappedIntRect(clipRect);
|
---|
662 |
|
---|
663 | ts << indent << "layer ";
|
---|
664 |
|
---|
665 | if (behavior.contains(RenderAsTextFlag::ShowAddresses)) {
|
---|
666 | ts << &layer << " ";
|
---|
667 | if (auto* scrollableArea = layer.scrollableArea())
|
---|
668 | ts << "scrollableArea " << scrollableArea << " ";
|
---|
669 | }
|
---|
670 |
|
---|
671 | ts << adjustedLayoutBounds;
|
---|
672 |
|
---|
673 | if (!adjustedLayoutBounds.isEmpty()) {
|
---|
674 | if (!adjustedBackgroundClipRect.contains(adjustedLayoutBounds))
|
---|
675 | ts << " backgroundClip " << adjustedBackgroundClipRect;
|
---|
676 | if (!adjustedClipRect.contains(adjustedLayoutBounds))
|
---|
677 | ts << " clip " << adjustedClipRect;
|
---|
678 | }
|
---|
679 |
|
---|
680 | if (layer.renderer().hasNonVisibleOverflow()) {
|
---|
681 | if (auto* scrollableArea = layer.scrollableArea()) {
|
---|
682 | if (scrollableArea->scrollOffset().x())
|
---|
683 | ts << " scrollX " << scrollableArea->scrollOffset().x();
|
---|
684 | if (scrollableArea->scrollOffset().y())
|
---|
685 | ts << " scrollY " << scrollableArea->scrollOffset().y();
|
---|
686 | if (layer.renderBox() && roundToInt(layer.renderBox()->clientWidth()) != scrollableArea->scrollWidth())
|
---|
687 | ts << " scrollWidth " << scrollableArea->scrollWidth();
|
---|
688 | if (layer.renderBox() && roundToInt(layer.renderBox()->clientHeight()) != scrollableArea->scrollHeight())
|
---|
689 | ts << " scrollHeight " << scrollableArea->scrollHeight();
|
---|
690 | }
|
---|
691 | #if PLATFORM(MAC)
|
---|
692 | ScrollbarTheme& scrollbarTheme = ScrollbarTheme::theme();
|
---|
693 | if (!scrollbarTheme.isMockTheme() && layer.scrollableArea() && layer.scrollableArea()->hasVerticalScrollbar()) {
|
---|
694 | ScrollbarThemeMac& macTheme = *static_cast<ScrollbarThemeMac*>(&scrollbarTheme);
|
---|
695 | if (macTheme.isLayoutDirectionRTL(*layer.scrollableArea()->verticalScrollbar()))
|
---|
696 | ts << " scrollbarHasRTLLayoutDirection";
|
---|
697 | }
|
---|
698 | #endif
|
---|
699 | }
|
---|
700 |
|
---|
701 | if (paintPhase == LayerPaintPhaseBackground)
|
---|
702 | ts << " layerType: background only";
|
---|
703 | else if (paintPhase == LayerPaintPhaseForeground)
|
---|
704 | ts << " layerType: foreground only";
|
---|
705 |
|
---|
706 | if (behavior.contains(RenderAsTextFlag::ShowCompositedLayers)) {
|
---|
707 | if (layer.isComposited()) {
|
---|
708 | ts << " (composited " << layer.compositor().reasonsForCompositing(layer)
|
---|
709 | << ", bounds=" << layer.backing()->compositedBounds()
|
---|
710 | << ", drawsContent=" << layer.backing()->graphicsLayer()->drawsContent()
|
---|
711 | << ", paints into ancestor=" << layer.backing()->paintsIntoCompositedAncestor() << ")";
|
---|
712 | } else if (layer.paintsIntoProvidedBacking())
|
---|
713 | ts << " (shared backing of " << layer.backingProviderLayer() << ")";
|
---|
714 | }
|
---|
715 |
|
---|
716 | #if ENABLE(CSS_COMPOSITING)
|
---|
717 | if (layer.isolatesBlending())
|
---|
718 | ts << " isolatesBlending";
|
---|
719 | if (layer.hasBlendMode())
|
---|
720 | ts << " blendMode: " << compositeOperatorName(CompositeOperator::SourceOver, layer.blendMode());
|
---|
721 | #endif
|
---|
722 |
|
---|
723 | ts << "\n";
|
---|
724 | }
|
---|
725 |
|
---|
726 | static void writeLayerRenderers(TextStream& ts, const RenderLayer& layer, LayerPaintPhase paintPhase, OptionSet<RenderAsTextFlag> behavior)
|
---|
727 | {
|
---|
728 | if (paintPhase != LayerPaintPhaseBackground) {
|
---|
729 | TextStream::IndentScope indentScope(ts);
|
---|
730 | write(ts, layer.renderer(), behavior);
|
---|
731 | }
|
---|
732 | }
|
---|
733 |
|
---|
734 | static LayoutSize maxLayoutOverflow(const RenderBox* box)
|
---|
735 | {
|
---|
736 | LayoutRect overflowRect = box->layoutOverflowRect();
|
---|
737 | return LayoutSize(overflowRect.maxX(), overflowRect.maxY());
|
---|
738 | }
|
---|
739 |
|
---|
740 | static void writeLayers(TextStream& ts, const RenderLayer& rootLayer, RenderLayer& layer, const LayoutRect& paintRect, OptionSet<RenderAsTextFlag> behavior)
|
---|
741 | {
|
---|
742 | // FIXME: Apply overflow to the root layer to not break every test. Complete hack. Sigh.
|
---|
743 | LayoutRect paintDirtyRect(paintRect);
|
---|
744 | if (&rootLayer == &layer) {
|
---|
745 | paintDirtyRect.setWidth(std::max<LayoutUnit>(paintDirtyRect.width(), rootLayer.renderBox()->layoutOverflowRect().maxX()));
|
---|
746 | paintDirtyRect.setHeight(std::max<LayoutUnit>(paintDirtyRect.height(), rootLayer.renderBox()->layoutOverflowRect().maxY()));
|
---|
747 | layer.setSize(layer.size().expandedTo(snappedIntSize(maxLayoutOverflow(layer.renderBox()), LayoutPoint(0, 0))));
|
---|
748 | }
|
---|
749 |
|
---|
750 | // Calculate the clip rects we should use.
|
---|
751 | LayoutRect layerBounds;
|
---|
752 | ClipRect damageRect;
|
---|
753 | ClipRect clipRectToApply;
|
---|
754 | LayoutSize offsetFromRoot = layer.offsetFromAncestor(&rootLayer);
|
---|
755 | layer.calculateRects(RenderLayer::ClipRectsContext(&rootLayer, TemporaryClipRects), paintDirtyRect, layerBounds, damageRect, clipRectToApply, offsetFromRoot);
|
---|
756 |
|
---|
757 | // Ensure our lists are up-to-date.
|
---|
758 | layer.updateLayerListsIfNeeded();
|
---|
759 | layer.updateDescendantDependentFlags();
|
---|
760 |
|
---|
761 | bool shouldPaint = (behavior.contains(RenderAsTextFlag::ShowAllLayers)) ? true : layer.intersectsDamageRect(layerBounds, damageRect.rect(), &rootLayer, layer.offsetFromAncestor(&rootLayer));
|
---|
762 | auto negativeZOrderLayers = layer.negativeZOrderLayers();
|
---|
763 | bool paintsBackgroundSeparately = negativeZOrderLayers.size() > 0;
|
---|
764 | if (shouldPaint && paintsBackgroundSeparately) {
|
---|
765 | writeLayer(ts, layer, layerBounds, damageRect.rect(), clipRectToApply.rect(), LayerPaintPhaseBackground, behavior);
|
---|
766 | writeLayerRenderers(ts, layer, LayerPaintPhaseBackground, behavior);
|
---|
767 | }
|
---|
768 |
|
---|
769 | if (negativeZOrderLayers.size()) {
|
---|
770 | if (behavior.contains(RenderAsTextFlag::ShowLayerNesting)) {
|
---|
771 | ts << indent << " negative z-order list (" << negativeZOrderLayers.size() << ")\n";
|
---|
772 | ts.increaseIndent();
|
---|
773 | }
|
---|
774 |
|
---|
775 | for (auto* currLayer : negativeZOrderLayers)
|
---|
776 | writeLayers(ts, rootLayer, *currLayer, paintDirtyRect, behavior);
|
---|
777 |
|
---|
778 | if (behavior.contains(RenderAsTextFlag::ShowLayerNesting))
|
---|
779 | ts.decreaseIndent();
|
---|
780 | }
|
---|
781 |
|
---|
782 | if (shouldPaint) {
|
---|
783 | writeLayer(ts, layer, layerBounds, damageRect.rect(), clipRectToApply.rect(), paintsBackgroundSeparately ? LayerPaintPhaseForeground : LayerPaintPhaseAll, behavior);
|
---|
784 |
|
---|
785 | if (behavior.contains(RenderAsTextFlag::ShowLayerFragments)) {
|
---|
786 | LayerFragments layerFragments;
|
---|
787 | layer.collectFragments(layerFragments, &rootLayer, paintDirtyRect, RenderLayer::PaginationInclusionMode::ExcludeCompositedPaginatedLayers, TemporaryClipRects, IgnoreOverlayScrollbarSize, RespectOverflowClip, offsetFromRoot);
|
---|
788 |
|
---|
789 | if (layerFragments.size() > 1) {
|
---|
790 | TextStream::IndentScope indentScope(ts, 2);
|
---|
791 | for (unsigned i = 0; i < layerFragments.size(); ++i) {
|
---|
792 | const auto& fragment = layerFragments[i];
|
---|
793 | ts << indent << " fragment " << i << ": bounds in layer " << fragment.layerBounds << " fragment bounds " << fragment.boundingBox << "\n";
|
---|
794 | }
|
---|
795 | }
|
---|
796 | }
|
---|
797 |
|
---|
798 | writeLayerRenderers(ts, layer, paintsBackgroundSeparately ? LayerPaintPhaseForeground : LayerPaintPhaseAll, behavior);
|
---|
799 | }
|
---|
800 |
|
---|
801 | auto normalFlowLayers = layer.normalFlowLayers();
|
---|
802 | if (normalFlowLayers.size()) {
|
---|
803 | if (behavior.contains(RenderAsTextFlag::ShowLayerNesting)) {
|
---|
804 | ts << indent << " normal flow list (" << normalFlowLayers.size() << ")\n";
|
---|
805 | ts.increaseIndent();
|
---|
806 | }
|
---|
807 |
|
---|
808 | for (auto* currLayer : normalFlowLayers)
|
---|
809 | writeLayers(ts, rootLayer, *currLayer, paintDirtyRect, behavior);
|
---|
810 |
|
---|
811 | if (behavior.contains(RenderAsTextFlag::ShowLayerNesting))
|
---|
812 | ts.decreaseIndent();
|
---|
813 | }
|
---|
814 |
|
---|
815 | auto positiveZOrderLayers = layer.positiveZOrderLayers();
|
---|
816 | if (positiveZOrderLayers.size()) {
|
---|
817 | size_t layerCount = positiveZOrderLayers.size();
|
---|
818 |
|
---|
819 | if (layerCount) {
|
---|
820 | if (behavior.contains(RenderAsTextFlag::ShowLayerNesting)) {
|
---|
821 | ts << indent << " positive z-order list (" << layerCount << ")\n";
|
---|
822 | ts.increaseIndent();
|
---|
823 | }
|
---|
824 |
|
---|
825 | for (auto* currLayer : positiveZOrderLayers)
|
---|
826 | writeLayers(ts, rootLayer, *currLayer, paintDirtyRect, behavior);
|
---|
827 |
|
---|
828 | if (behavior.contains(RenderAsTextFlag::ShowLayerNesting))
|
---|
829 | ts.decreaseIndent();
|
---|
830 | }
|
---|
831 | }
|
---|
832 | }
|
---|
833 |
|
---|
834 | static String nodePosition(Node* node)
|
---|
835 | {
|
---|
836 | StringBuilder result;
|
---|
837 |
|
---|
838 | auto* body = node->document().bodyOrFrameset();
|
---|
839 | Node* parent;
|
---|
840 | for (Node* n = node; n; n = parent) {
|
---|
841 | parent = n->parentOrShadowHostNode();
|
---|
842 | if (n != node)
|
---|
843 | result.append(" of ");
|
---|
844 | if (parent) {
|
---|
845 | if (body && n == body) {
|
---|
846 | // We don't care what offset body may be in the document.
|
---|
847 | result.append("body");
|
---|
848 | break;
|
---|
849 | }
|
---|
850 | if (n->isShadowRoot())
|
---|
851 | result.append('{', getTagName(n), '}');
|
---|
852 | else
|
---|
853 | result.append("child ", n->computeNodeIndex(), " {", getTagName(n), '}');
|
---|
854 | } else
|
---|
855 | result.append("document");
|
---|
856 | }
|
---|
857 |
|
---|
858 | return result.toString();
|
---|
859 | }
|
---|
860 |
|
---|
861 | static void writeSelection(TextStream& ts, const RenderBox& renderer)
|
---|
862 | {
|
---|
863 | if (!renderer.isRenderView())
|
---|
864 | return;
|
---|
865 |
|
---|
866 | Frame* frame = renderer.document().frame();
|
---|
867 | if (!frame)
|
---|
868 | return;
|
---|
869 |
|
---|
870 | VisibleSelection selection = frame->selection().selection();
|
---|
871 | if (selection.isCaret()) {
|
---|
872 | ts << "caret: position " << selection.start().deprecatedEditingOffset() << " of " << nodePosition(selection.start().deprecatedNode());
|
---|
873 | if (selection.affinity() == Affinity::Upstream)
|
---|
874 | ts << " (upstream affinity)";
|
---|
875 | ts << "\n";
|
---|
876 | } else if (selection.isRange())
|
---|
877 | ts << "selection start: position " << selection.start().deprecatedEditingOffset() << " of " << nodePosition(selection.start().deprecatedNode()) << "\n"
|
---|
878 | << "selection end: position " << selection.end().deprecatedEditingOffset() << " of " << nodePosition(selection.end().deprecatedNode()) << "\n";
|
---|
879 | }
|
---|
880 |
|
---|
881 | static String externalRepresentation(RenderBox& renderer, OptionSet<RenderAsTextFlag> behavior)
|
---|
882 | {
|
---|
883 | TextStream ts(TextStream::LineMode::MultipleLine, { TextStream::Formatting::SVGStyleRect, TextStream::Formatting::LayoutUnitsAsIntegers });
|
---|
884 | if (!renderer.hasLayer())
|
---|
885 | return ts.release();
|
---|
886 |
|
---|
887 | LOG(Layout, "externalRepresentation: dumping layer tree");
|
---|
888 |
|
---|
889 | RenderLayer& layer = *renderer.layer();
|
---|
890 | writeLayers(ts, layer, layer, layer.rect(), behavior);
|
---|
891 | writeSelection(ts, renderer);
|
---|
892 | return ts.release();
|
---|
893 | }
|
---|
894 |
|
---|
895 | static void updateLayoutIgnoringPendingStylesheetsIncludingSubframes(Document& document)
|
---|
896 | {
|
---|
897 | document.updateLayoutIgnorePendingStylesheets();
|
---|
898 | auto* frame = document.frame();
|
---|
899 | for (auto* subframe = frame; subframe; subframe = subframe->tree().traverseNext(frame)) {
|
---|
900 | if (auto* document = subframe->document())
|
---|
901 | document->updateLayoutIgnorePendingStylesheets();
|
---|
902 | }
|
---|
903 | }
|
---|
904 |
|
---|
905 | String externalRepresentation(Frame* frame, OptionSet<RenderAsTextFlag> behavior)
|
---|
906 | {
|
---|
907 | ASSERT(frame);
|
---|
908 | ASSERT(frame->document());
|
---|
909 |
|
---|
910 | if (!(behavior.contains(RenderAsTextFlag::DontUpdateLayout)))
|
---|
911 | updateLayoutIgnoringPendingStylesheetsIncludingSubframes(*frame->document());
|
---|
912 |
|
---|
913 | auto* renderer = frame->contentRenderer();
|
---|
914 | if (!renderer)
|
---|
915 | return String();
|
---|
916 |
|
---|
917 | PrintContext printContext(frame);
|
---|
918 | if (behavior.contains(RenderAsTextFlag::PrintingMode))
|
---|
919 | printContext.begin(renderer->width());
|
---|
920 |
|
---|
921 | return externalRepresentation(*renderer, behavior);
|
---|
922 | }
|
---|
923 |
|
---|
924 | String externalRepresentation(Element* element, OptionSet<RenderAsTextFlag> behavior)
|
---|
925 | {
|
---|
926 | ASSERT(element);
|
---|
927 |
|
---|
928 | // This function doesn't support printing mode.
|
---|
929 | ASSERT(!(behavior.contains(RenderAsTextFlag::PrintingMode)));
|
---|
930 |
|
---|
931 | if (!(behavior.contains(RenderAsTextFlag::DontUpdateLayout)))
|
---|
932 | updateLayoutIgnoringPendingStylesheetsIncludingSubframes(element->document());
|
---|
933 |
|
---|
934 | auto* renderer = element->renderer();
|
---|
935 | if (!is<RenderBox>(renderer))
|
---|
936 | return String();
|
---|
937 |
|
---|
938 | return externalRepresentation(downcast<RenderBox>(*renderer), behavior | RenderAsTextFlag::ShowAllLayers);
|
---|
939 | }
|
---|
940 |
|
---|
941 | static void writeCounterValuesFromChildren(TextStream& stream, const RenderElement* parent, bool& isFirstCounter)
|
---|
942 | {
|
---|
943 | if (!parent)
|
---|
944 | return;
|
---|
945 | for (auto& counter : childrenOfType<RenderCounter>(*parent)) {
|
---|
946 | if (!isFirstCounter)
|
---|
947 | stream << " ";
|
---|
948 | isFirstCounter = false;
|
---|
949 | String str(counter.text());
|
---|
950 | stream << str;
|
---|
951 | }
|
---|
952 | }
|
---|
953 |
|
---|
954 | String counterValueForElement(Element* element)
|
---|
955 | {
|
---|
956 | // Make sure the element is not freed during the layout.
|
---|
957 | RefPtr<Element> elementRef(element);
|
---|
958 | element->document().updateLayout();
|
---|
959 | TextStream stream(TextStream::LineMode::MultipleLine, { TextStream::Formatting::SVGStyleRect, TextStream::Formatting::LayoutUnitsAsIntegers });
|
---|
960 | bool isFirstCounter = true;
|
---|
961 | // The counter renderers should be children of :before or :after pseudo-elements.
|
---|
962 | if (PseudoElement* before = element->beforePseudoElement())
|
---|
963 | writeCounterValuesFromChildren(stream, before->renderer(), isFirstCounter);
|
---|
964 | if (PseudoElement* after = element->afterPseudoElement())
|
---|
965 | writeCounterValuesFromChildren(stream, after->renderer(), isFirstCounter);
|
---|
966 | return stream.release();
|
---|
967 | }
|
---|
968 |
|
---|
969 | String markerTextForListItem(Element* element)
|
---|
970 | {
|
---|
971 | // Make sure the element is not freed during the layout.
|
---|
972 | RefPtr<Element> elementRef(element);
|
---|
973 | element->document().updateLayout();
|
---|
974 |
|
---|
975 | RenderElement* renderer = element->renderer();
|
---|
976 | if (!is<RenderListItem>(renderer))
|
---|
977 | return String();
|
---|
978 |
|
---|
979 | return downcast<RenderListItem>(*renderer).markerTextWithoutSuffix().toString();
|
---|
980 | }
|
---|
981 |
|
---|
982 | } // namespace WebCore
|
---|