1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "third_party/blink/renderer/core/inspector/inspector_highlight.h"
6
7 #include "base/macros.h"
8 #include "third_party/blink/renderer/core/css/css_color_value.h"
9 #include "third_party/blink/renderer/core/css/css_computed_style_declaration.h"
10 #include "third_party/blink/renderer/core/css/css_property_names.h"
11 #include "third_party/blink/renderer/core/display_lock/display_lock_utilities.h"
12 #include "third_party/blink/renderer/core/dom/pseudo_element.h"
13 #include "third_party/blink/renderer/core/frame/local_frame_view.h"
14 #include "third_party/blink/renderer/core/frame/visual_viewport.h"
15 #include "third_party/blink/renderer/core/geometry/dom_rect.h"
16 #include "third_party/blink/renderer/core/inspector/inspector_dom_agent.h"
17 #include "third_party/blink/renderer/core/inspector/inspector_dom_snapshot_agent.h"
18 #include "third_party/blink/renderer/core/layout/adjust_for_absolute_zoom.h"
19 #include "third_party/blink/renderer/core/layout/layout_box.h"
20 #include "third_party/blink/renderer/core/layout/layout_grid.h"
21 #include "third_party/blink/renderer/core/layout/layout_inline.h"
22 #include "third_party/blink/renderer/core/layout/layout_object.h"
23 #include "third_party/blink/renderer/core/layout/layout_view.h"
24 #include "third_party/blink/renderer/core/layout/shapes/shape_outside_info.h"
25 #include "third_party/blink/renderer/core/page/chrome_client.h"
26 #include "third_party/blink/renderer/core/page/page.h"
27 #include "third_party/blink/renderer/core/style/computed_style_constants.h"
28 #include "third_party/blink/renderer/platform/graphics/path.h"
29 #include "third_party/blink/renderer/platform/web_test_support.h"
30
31 namespace blink {
32
33 namespace {
34
35 class PathBuilder {
36 STACK_ALLOCATED();
37
38 public:
PathBuilder()39 PathBuilder() : path_(protocol::ListValue::create()) {}
40 virtual ~PathBuilder() = default;
41
Release()42 std::unique_ptr<protocol::ListValue> Release() { return std::move(path_); }
43
AppendPath(const Path & path,float scale)44 void AppendPath(const Path& path, float scale) {
45 Path transform_path(path);
46 transform_path.Transform(AffineTransform().Scale(scale));
47 transform_path.Apply(this, &PathBuilder::AppendPathElement);
48 }
49
50 protected:
TranslatePoint(const FloatPoint & point)51 virtual FloatPoint TranslatePoint(const FloatPoint& point) { return point; }
52
53 private:
AppendPathElement(void * path_builder,const PathElement * path_element)54 static void AppendPathElement(void* path_builder,
55 const PathElement* path_element) {
56 static_cast<PathBuilder*>(path_builder)->AppendPathElement(path_element);
57 }
58
59 void AppendPathElement(const PathElement*);
60 void AppendPathCommandAndPoints(const char* command,
61 const FloatPoint points[],
62 size_t length);
63
64 std::unique_ptr<protocol::ListValue> path_;
65 DISALLOW_COPY_AND_ASSIGN(PathBuilder);
66 };
67
AppendPathCommandAndPoints(const char * command,const FloatPoint points[],size_t length)68 void PathBuilder::AppendPathCommandAndPoints(const char* command,
69 const FloatPoint points[],
70 size_t length) {
71 path_->pushValue(protocol::StringValue::create(command));
72 for (size_t i = 0; i < length; i++) {
73 FloatPoint point = TranslatePoint(points[i]);
74 path_->pushValue(protocol::FundamentalValue::create(point.X()));
75 path_->pushValue(protocol::FundamentalValue::create(point.Y()));
76 }
77 }
78
AppendPathElement(const PathElement * path_element)79 void PathBuilder::AppendPathElement(const PathElement* path_element) {
80 switch (path_element->type) {
81 // The points member will contain 1 value.
82 case kPathElementMoveToPoint:
83 AppendPathCommandAndPoints("M", path_element->points, 1);
84 break;
85 // The points member will contain 1 value.
86 case kPathElementAddLineToPoint:
87 AppendPathCommandAndPoints("L", path_element->points, 1);
88 break;
89 // The points member will contain 3 values.
90 case kPathElementAddCurveToPoint:
91 AppendPathCommandAndPoints("C", path_element->points, 3);
92 break;
93 // The points member will contain 2 values.
94 case kPathElementAddQuadCurveToPoint:
95 AppendPathCommandAndPoints("Q", path_element->points, 2);
96 break;
97 // The points member will contain no values.
98 case kPathElementCloseSubpath:
99 AppendPathCommandAndPoints("Z", nullptr, 0);
100 break;
101 }
102 }
103
104 class ShapePathBuilder : public PathBuilder {
105 public:
ShapePathBuilder(LocalFrameView & view,LayoutObject & layout_object,const ShapeOutsideInfo & shape_outside_info)106 ShapePathBuilder(LocalFrameView& view,
107 LayoutObject& layout_object,
108 const ShapeOutsideInfo& shape_outside_info)
109 : view_(&view),
110 layout_object_(layout_object),
111 shape_outside_info_(shape_outside_info) {}
112
BuildPath(LocalFrameView & view,LayoutObject & layout_object,const ShapeOutsideInfo & shape_outside_info,const Path & path,float scale)113 static std::unique_ptr<protocol::ListValue> BuildPath(
114 LocalFrameView& view,
115 LayoutObject& layout_object,
116 const ShapeOutsideInfo& shape_outside_info,
117 const Path& path,
118 float scale) {
119 ShapePathBuilder builder(view, layout_object, shape_outside_info);
120 builder.AppendPath(path, scale);
121 return builder.Release();
122 }
123
124 protected:
TranslatePoint(const FloatPoint & point)125 FloatPoint TranslatePoint(const FloatPoint& point) override {
126 PhysicalOffset layout_object_point = PhysicalOffset::FromFloatPointRound(
127 shape_outside_info_.ShapeToLayoutObjectPoint(point));
128 // TODO(pfeldman): Is this kIgnoreTransforms correct?
129 return FloatPoint(view_->FrameToViewport(
130 RoundedIntPoint(layout_object_.LocalToAbsolutePoint(
131 layout_object_point, kIgnoreTransforms))));
132 }
133
134 private:
135 LocalFrameView* view_;
136 LayoutObject& layout_object_;
137 const ShapeOutsideInfo& shape_outside_info_;
138 };
139
BuildArrayForQuad(const FloatQuad & quad)140 std::unique_ptr<protocol::Array<double>> BuildArrayForQuad(
141 const FloatQuad& quad) {
142 return std::make_unique<std::vector<double>, std::initializer_list<double>>(
143 {quad.P1().X(), quad.P1().Y(), quad.P2().X(), quad.P2().Y(),
144 quad.P3().X(), quad.P3().Y(), quad.P4().X(), quad.P4().Y()});
145 }
146
QuadToPath(const FloatQuad & quad)147 Path QuadToPath(const FloatQuad& quad) {
148 Path quad_path;
149 quad_path.MoveTo(quad.P1());
150 quad_path.AddLineTo(quad.P2());
151 quad_path.AddLineTo(quad.P3());
152 quad_path.AddLineTo(quad.P4());
153 quad_path.CloseSubpath();
154 return quad_path;
155 }
156
FramePointToViewport(const LocalFrameView * view,FloatPoint point_in_frame)157 FloatPoint FramePointToViewport(const LocalFrameView* view,
158 FloatPoint point_in_frame) {
159 FloatPoint point_in_root_frame = view->ConvertToRootFrame(point_in_frame);
160 return view->GetPage()->GetVisualViewport().RootFrameToViewport(
161 point_in_root_frame);
162 }
163
FrameQuadToViewport(const LocalFrameView * view,FloatQuad & quad)164 void FrameQuadToViewport(const LocalFrameView* view, FloatQuad& quad) {
165 quad.SetP1(FramePointToViewport(view, quad.P1()));
166 quad.SetP2(FramePointToViewport(view, quad.P2()));
167 quad.SetP3(FramePointToViewport(view, quad.P3()));
168 quad.SetP4(FramePointToViewport(view, quad.P4()));
169 }
170
ShapeOutsideInfoForNode(Node * node,Shape::DisplayPaths * paths,FloatQuad * bounds)171 const ShapeOutsideInfo* ShapeOutsideInfoForNode(Node* node,
172 Shape::DisplayPaths* paths,
173 FloatQuad* bounds) {
174 LayoutObject* layout_object = node->GetLayoutObject();
175 if (!layout_object || !layout_object->IsBox() ||
176 !ToLayoutBox(layout_object)->GetShapeOutsideInfo())
177 return nullptr;
178
179 LocalFrameView* containing_view = node->GetDocument().View();
180 LayoutBox* layout_box = ToLayoutBox(layout_object);
181 const ShapeOutsideInfo* shape_outside_info =
182 layout_box->GetShapeOutsideInfo();
183
184 shape_outside_info->ComputedShape().BuildDisplayPaths(*paths);
185
186 PhysicalRect shape_bounds =
187 shape_outside_info->ComputedShapePhysicalBoundingBox();
188 *bounds = layout_box->LocalRectToAbsoluteQuad(shape_bounds);
189 FrameQuadToViewport(containing_view, *bounds);
190
191 return shape_outside_info;
192 }
193
ToHEXA(const Color & color)194 String ToHEXA(const Color& color) {
195 return String::Format("#%02X%02X%02X%02X", color.Red(), color.Green(),
196 color.Blue(), color.Alpha());
197 }
198
AppendStyleInfo(Node * node,protocol::DictionaryValue * element_info,const InspectorHighlightContrastInfo & node_contrast)199 void AppendStyleInfo(Node* node,
200 protocol::DictionaryValue* element_info,
201 const InspectorHighlightContrastInfo& node_contrast) {
202 std::unique_ptr<protocol::DictionaryValue> computed_style =
203 protocol::DictionaryValue::create();
204 CSSComputedStyleDeclaration* style =
205 MakeGarbageCollected<CSSComputedStyleDeclaration>(node, true);
206 Vector<CSSPropertyID> properties;
207
208 // For text nodes, we can show color & font properties.
209 bool has_text_children = false;
210 for (Node* child = node->firstChild(); !has_text_children && child;
211 child = child->nextSibling()) {
212 has_text_children = child->IsTextNode();
213 }
214 if (has_text_children) {
215 properties.push_back(CSSPropertyID::kColor);
216 properties.push_back(CSSPropertyID::kFontFamily);
217 properties.push_back(CSSPropertyID::kFontSize);
218 properties.push_back(CSSPropertyID::kLineHeight);
219 }
220
221 properties.push_back(CSSPropertyID::kPadding);
222 properties.push_back(CSSPropertyID::kMargin);
223 properties.push_back(CSSPropertyID::kBackgroundColor);
224
225 for (size_t i = 0; i < properties.size(); ++i) {
226 const CSSValue* value = style->GetPropertyCSSValue(properties[i]);
227 if (!value)
228 continue;
229 AtomicString name = CSSPropertyName(properties[i]).ToAtomicString();
230 if (value->IsColorValue()) {
231 Color color = static_cast<const cssvalue::CSSColorValue*>(value)->Value();
232 computed_style->setString(name, ToHEXA(color));
233 } else {
234 computed_style->setString(name, value->CssText());
235 }
236 }
237 element_info->setValue("style", std::move(computed_style));
238
239 if (!node_contrast.font_size.IsEmpty()) {
240 std::unique_ptr<protocol::DictionaryValue> contrast =
241 protocol::DictionaryValue::create();
242 contrast->setString("fontSize", node_contrast.font_size);
243 contrast->setString("fontWeight", node_contrast.font_weight);
244 contrast->setString("backgroundColor",
245 ToHEXA(node_contrast.background_color));
246 element_info->setValue("contrast", std::move(contrast));
247 }
248 }
249
BuildElementInfo(Element * element)250 std::unique_ptr<protocol::DictionaryValue> BuildElementInfo(Element* element) {
251 std::unique_ptr<protocol::DictionaryValue> element_info =
252 protocol::DictionaryValue::create();
253 Element* real_element = element;
254 auto* pseudo_element = DynamicTo<PseudoElement>(element);
255 if (pseudo_element) {
256 real_element = element->ParentOrShadowHostElement();
257 }
258 bool is_xhtml = real_element->GetDocument().IsXHTMLDocument();
259 element_info->setString(
260 "tagName", is_xhtml ? real_element->nodeName()
261 : real_element->nodeName().DeprecatedLower());
262 element_info->setString("idValue", real_element->GetIdAttribute());
263 StringBuilder class_names;
264 if (real_element->HasClass() && real_element->IsStyledElement()) {
265 HashSet<AtomicString> used_class_names;
266 const SpaceSplitString& class_names_string = real_element->ClassNames();
267 wtf_size_t class_name_count = class_names_string.size();
268 for (wtf_size_t i = 0; i < class_name_count; ++i) {
269 const AtomicString& class_name = class_names_string[i];
270 if (!used_class_names.insert(class_name).is_new_entry)
271 continue;
272 class_names.Append('.');
273 class_names.Append(class_name);
274 }
275 }
276 if (pseudo_element) {
277 if (pseudo_element->GetPseudoId() == kPseudoIdBefore)
278 class_names.Append("::before");
279 else if (pseudo_element->GetPseudoId() == kPseudoIdAfter)
280 class_names.Append("::after");
281 else if (pseudo_element->GetPseudoId() == kPseudoIdMarker)
282 class_names.Append("::marker");
283 }
284 if (!class_names.IsEmpty())
285 element_info->setString("className", class_names.ToString());
286
287 LayoutObject* layout_object = element->GetLayoutObject();
288 LocalFrameView* containing_view = element->GetDocument().View();
289 if (!layout_object || !containing_view)
290 return element_info;
291
292 // if (auto* context = element->GetDisplayLockContext()) {
293 // if (context->IsLocked()) {
294 // // If it's a locked element, use the values from the locked frame rect.
295 // // TODO(vmpstr): Verify that these values are correct here.
296 // element_info->setString(
297 // "nodeWidth",
298 // String::Number(context->GetLockedContentLogicalWidth().ToDouble()));
299 // element_info->setString(
300 // "nodeHeight",
301 // String::Number(context->GetLockedContentLogicalHeight().ToDouble()));
302 // }
303 // return element_info;
304 //}
305
306 // layoutObject the getBoundingClientRect() data in the tooltip
307 // to be consistent with the rulers (see http://crbug.com/262338).
308 DOMRect* bounding_box = element->getBoundingClientRect();
309 element_info->setString("nodeWidth", String::Number(bounding_box->width()));
310 element_info->setString("nodeHeight", String::Number(bounding_box->height()));
311 return element_info;
312 }
313
BuildTextNodeInfo(Text * text_node)314 std::unique_ptr<protocol::DictionaryValue> BuildTextNodeInfo(Text* text_node) {
315 std::unique_ptr<protocol::DictionaryValue> text_info =
316 protocol::DictionaryValue::create();
317 LayoutObject* layout_object = text_node->GetLayoutObject();
318 if (!layout_object || !layout_object->IsText())
319 return text_info;
320 PhysicalRect bounding_box =
321 ToLayoutText(layout_object)->PhysicalVisualOverflowRect();
322 text_info->setString("nodeWidth", bounding_box.Width().ToString());
323 text_info->setString("nodeHeight", bounding_box.Height().ToString());
324 text_info->setString("tagName", "#text");
325 return text_info;
326 }
327
BuildGridInfo(LocalFrameView * containing_view,LayoutGrid * layout_grid,Color color,float scale,bool isPrimary)328 std::unique_ptr<protocol::DictionaryValue> BuildGridInfo(
329 LocalFrameView* containing_view,
330 LayoutGrid* layout_grid,
331 Color color,
332 float scale,
333 bool isPrimary) {
334 std::unique_ptr<protocol::DictionaryValue> grid_info =
335 protocol::DictionaryValue::create();
336
337 const auto& rows = layout_grid->RowPositions();
338 const auto& columns = layout_grid->ColumnPositions();
339
340 PathBuilder cell_builder;
341 auto row_gap =
342 layout_grid->GridGap(kForRows) + layout_grid->GridItemOffset(kForRows);
343 auto column_gap = layout_grid->GridGap(kForColumns) +
344 layout_grid->GridItemOffset(kForColumns);
345
346 for (size_t i = 1; i < rows.size(); ++i) {
347 for (size_t j = 1; j < columns.size(); ++j) {
348 PhysicalOffset position(columns.at(j - 1), rows.at(i - 1));
349 PhysicalSize size(columns.at(j) - columns.at(j - 1),
350 rows.at(i) - rows.at(i - 1));
351 if (i != rows.size() - 1)
352 size.height -= row_gap;
353 if (j != columns.size() - 1)
354 size.width -= column_gap;
355 PhysicalRect cell(position, size);
356 FloatQuad cell_quad = layout_grid->LocalRectToAbsoluteQuad(cell);
357 FrameQuadToViewport(containing_view, cell_quad);
358 cell_builder.AppendPath(QuadToPath(cell_quad), scale);
359 }
360 }
361 grid_info->setValue("cells", cell_builder.Release());
362 grid_info->setString("color", color.Serialized());
363 grid_info->setBoolean("isPrimaryGrid", isPrimary);
364 return grid_info;
365 }
366
CollectQuadsRecursive(Node * node,Vector<FloatQuad> & out_quads)367 void CollectQuadsRecursive(Node* node, Vector<FloatQuad>& out_quads) {
368 LayoutObject* layout_object = node->GetLayoutObject();
369 if (!layout_object)
370 return;
371
372 // For inline elements, absoluteQuads will return a line box based on the
373 // line-height and font metrics, which is technically incorrect as replaced
374 // elements like images should use their intristic height and expand the
375 // linebox as needed. To get an appropriate quads we descend
376 // into the children and have them add their boxes.
377 if (layout_object->IsLayoutInline() &&
378 LayoutTreeBuilderTraversal::FirstChild(*node)) {
379 for (Node* child = LayoutTreeBuilderTraversal::FirstChild(*node); child;
380 child = LayoutTreeBuilderTraversal::NextSibling(*child))
381 CollectQuadsRecursive(child, out_quads);
382 } else {
383 layout_object->AbsoluteQuads(out_quads);
384 }
385 }
386
CollectQuads(Node * node,Vector<FloatQuad> & out_quads)387 void CollectQuads(Node* node, Vector<FloatQuad>& out_quads) {
388 CollectQuadsRecursive(node, out_quads);
389 LocalFrameView* containing_view =
390 node->GetLayoutObject() ? node->GetLayoutObject()->GetFrameView()
391 : nullptr;
392 if (containing_view) {
393 for (FloatQuad& quad : out_quads)
394 FrameQuadToViewport(containing_view, quad);
395 }
396 }
397
RectForPhysicalRect(const PhysicalRect & rect)398 std::unique_ptr<protocol::Array<double>> RectForPhysicalRect(
399 const PhysicalRect& rect) {
400 return std::make_unique<std::vector<double>, std::initializer_list<double>>(
401 {rect.X(), rect.Y(), rect.Width(), rect.Height()});
402 }
403
404 // Returns |layout_object|'s bounding box in document coordinates.
RectInRootFrame(const LayoutObject * layout_object)405 PhysicalRect RectInRootFrame(const LayoutObject* layout_object) {
406 LocalFrameView* local_frame_view = layout_object->GetFrameView();
407 PhysicalRect rect_in_absolute = PhysicalRect::EnclosingRect(
408 layout_object->AbsoluteBoundingBoxFloatRect());
409 return local_frame_view
410 ? local_frame_view->ConvertToRootFrame(rect_in_absolute)
411 : rect_in_absolute;
412 }
413
TextFragmentRectInRootFrame(const LayoutObject * layout_object,const LayoutText::TextBoxInfo & text_box)414 PhysicalRect TextFragmentRectInRootFrame(
415 const LayoutObject* layout_object,
416 const LayoutText::TextBoxInfo& text_box) {
417 PhysicalRect absolute_coords_text_box_rect =
418 layout_object->LocalToAbsoluteRect(
419 layout_object->FlipForWritingMode(text_box.local_rect));
420 LocalFrameView* local_frame_view = layout_object->GetFrameView();
421 return local_frame_view ? local_frame_view->ConvertToRootFrame(
422 absolute_coords_text_box_rect)
423 : absolute_coords_text_box_rect;
424 }
425
426 } // namespace
427
InspectorHighlight(float scale)428 InspectorHighlight::InspectorHighlight(float scale)
429 : highlight_paths_(protocol::ListValue::create()),
430 show_rulers_(false),
431 show_extension_lines_(false),
432 scale_(scale) {}
433
InspectorHighlightConfig()434 InspectorHighlightConfig::InspectorHighlightConfig()
435 : show_info(false),
436 show_styles(false),
437 show_rulers(false),
438 show_extension_lines(false) {}
439
InspectorHighlight(Node * node,const InspectorHighlightConfig & highlight_config,const InspectorHighlightContrastInfo & node_contrast,bool append_element_info,bool append_distance_info,bool is_locked_ancestor)440 InspectorHighlight::InspectorHighlight(
441 Node* node,
442 const InspectorHighlightConfig& highlight_config,
443 const InspectorHighlightContrastInfo& node_contrast,
444 bool append_element_info,
445 bool append_distance_info,
446 bool is_locked_ancestor)
447 : highlight_paths_(protocol::ListValue::create()),
448 show_rulers_(highlight_config.show_rulers),
449 show_extension_lines_(highlight_config.show_extension_lines),
450 scale_(1.f) {
451 DCHECK(!DisplayLockUtilities::NearestLockedExclusiveAncestor(*node));
452 LocalFrameView* frame_view = node->GetDocument().View();
453 if (frame_view) {
454 scale_ = 1.f / frame_view->GetChromeClient()->WindowToViewportScalar(
455 &frame_view->GetFrame(), 1.f);
456 }
457 AppendPathsForShapeOutside(node, highlight_config);
458 AppendNodeHighlight(node, highlight_config);
459 auto* text_node = DynamicTo<Text>(node);
460 auto* element = DynamicTo<Element>(node);
461 if (append_element_info && element)
462 element_info_ = BuildElementInfo(element);
463 else if (append_element_info && text_node)
464 element_info_ = BuildTextNodeInfo(text_node);
465 if (element_info_ && highlight_config.show_styles)
466 AppendStyleInfo(node, element_info_.get(), node_contrast);
467
468 if (element_info_ && is_locked_ancestor)
469 element_info_->setString("isLockedAncestor", "true");
470 if (append_distance_info)
471 AppendDistanceInfo(node);
472 }
473
474 InspectorHighlight::~InspectorHighlight() = default;
475
AppendDistanceInfo(Node * node)476 void InspectorHighlight::AppendDistanceInfo(Node* node) {
477 if (!InspectorHighlight::GetBoxModel(node, &model_, false))
478 return;
479 boxes_ = std::make_unique<protocol::Array<protocol::Array<double>>>();
480 computed_style_ = protocol::DictionaryValue::create();
481
482 node->GetDocument().EnsurePaintLocationDataValidForNode(
483 node, DocumentUpdateReason::kInspector);
484 LayoutObject* layout_object = node->GetLayoutObject();
485 if (!layout_object)
486 return;
487
488 CSSComputedStyleDeclaration* style =
489 MakeGarbageCollected<CSSComputedStyleDeclaration>(node, true);
490 for (size_t i = 0; i < style->length(); ++i) {
491 AtomicString name(style->item(i));
492 const CSSValue* value = style->GetPropertyCSSValue(
493 cssPropertyID(node->GetExecutionContext(), name));
494 if (!value)
495 continue;
496 if (value->IsColorValue()) {
497 Color color = static_cast<const cssvalue::CSSColorValue*>(value)->Value();
498 String hex_color =
499 String::Format("#%02X%02X%02X%02X", color.Red(), color.Green(),
500 color.Blue(), color.Alpha());
501 computed_style_->setString(name, hex_color);
502 } else {
503 computed_style_->setString(name, value->CssText());
504 }
505 }
506
507 VisitAndCollectDistanceInfo(&(node->GetDocument()));
508 PhysicalRect document_rect(
509 node->GetDocument().GetLayoutView()->DocumentRect());
510 LocalFrameView* local_frame_view = node->GetDocument().View();
511 boxes_->emplace_back(
512 RectForPhysicalRect(local_frame_view->ConvertToRootFrame(document_rect)));
513 }
514
VisitAndCollectDistanceInfo(Node * node)515 void InspectorHighlight::VisitAndCollectDistanceInfo(Node* node) {
516 LayoutObject* layout_object = node->GetLayoutObject();
517 if (layout_object)
518 AddLayoutBoxToDistanceInfo(layout_object);
519
520 if (auto* element = DynamicTo<Element>(node)) {
521 if (element->GetPseudoId()) {
522 if (layout_object)
523 VisitAndCollectDistanceInfo(element->GetPseudoId(), layout_object);
524 } else {
525 for (PseudoId pseudo_id :
526 {kPseudoIdFirstLetter, kPseudoIdBefore, kPseudoIdAfter}) {
527 if (Node* pseudo_node = element->GetPseudoElement(pseudo_id))
528 VisitAndCollectDistanceInfo(pseudo_node);
529 }
530 }
531 }
532
533 if (!node->IsContainerNode())
534 return;
535 Node* first_child = InspectorDOMSnapshotAgent::FirstChild(*node, false);
536 for (Node* child = first_child; child;
537 child = InspectorDOMSnapshotAgent::NextSibling(*child, false))
538 VisitAndCollectDistanceInfo(child);
539 }
540
VisitAndCollectDistanceInfo(PseudoId pseudo_id,LayoutObject * layout_object)541 void InspectorHighlight::VisitAndCollectDistanceInfo(
542 PseudoId pseudo_id,
543 LayoutObject* layout_object) {
544 protocol::DOM::PseudoType pseudo_type;
545 if (pseudo_id == kPseudoIdNone)
546 return;
547 for (LayoutObject* child = layout_object->SlowFirstChild(); child;
548 child = child->NextSibling()) {
549 if (child->IsAnonymous())
550 AddLayoutBoxToDistanceInfo(child);
551 }
552 }
553
AddLayoutBoxToDistanceInfo(LayoutObject * layout_object)554 void InspectorHighlight::AddLayoutBoxToDistanceInfo(
555 LayoutObject* layout_object) {
556 if (layout_object->IsText()) {
557 LayoutText* layout_text = ToLayoutText(layout_object);
558 for (const auto& text_box : layout_text->GetTextBoxInfo()) {
559 PhysicalRect text_rect(
560 TextFragmentRectInRootFrame(layout_object, text_box));
561 boxes_->emplace_back(RectForPhysicalRect(text_rect));
562 }
563 } else {
564 PhysicalRect rect(RectInRootFrame(layout_object));
565 boxes_->emplace_back(RectForPhysicalRect(rect));
566 }
567 }
568
AppendQuad(const FloatQuad & quad,const Color & fill_color,const Color & outline_color,const String & name)569 void InspectorHighlight::AppendQuad(const FloatQuad& quad,
570 const Color& fill_color,
571 const Color& outline_color,
572 const String& name) {
573 Path path = QuadToPath(quad);
574 PathBuilder builder;
575 builder.AppendPath(path, scale_);
576 AppendPath(builder.Release(), fill_color, outline_color, name);
577 }
578
AppendPath(std::unique_ptr<protocol::ListValue> path,const Color & fill_color,const Color & outline_color,const String & name)579 void InspectorHighlight::AppendPath(std::unique_ptr<protocol::ListValue> path,
580 const Color& fill_color,
581 const Color& outline_color,
582 const String& name) {
583 std::unique_ptr<protocol::DictionaryValue> object =
584 protocol::DictionaryValue::create();
585 object->setValue("path", std::move(path));
586 object->setString("fillColor", fill_color.Serialized());
587 if (outline_color != Color::kTransparent)
588 object->setString("outlineColor", outline_color.Serialized());
589 if (!name.IsEmpty())
590 object->setString("name", name);
591 highlight_paths_->pushValue(std::move(object));
592 }
593
AppendEventTargetQuads(Node * event_target_node,const InspectorHighlightConfig & highlight_config)594 void InspectorHighlight::AppendEventTargetQuads(
595 Node* event_target_node,
596 const InspectorHighlightConfig& highlight_config) {
597 if (event_target_node->GetLayoutObject()) {
598 FloatQuad border, unused;
599 if (BuildNodeQuads(event_target_node, &unused, &unused, &border, &unused))
600 AppendQuad(border, highlight_config.event_target);
601 }
602 }
603
AppendPathsForShapeOutside(Node * node,const InspectorHighlightConfig & config)604 void InspectorHighlight::AppendPathsForShapeOutside(
605 Node* node,
606 const InspectorHighlightConfig& config) {
607 Shape::DisplayPaths paths;
608 FloatQuad bounds_quad;
609
610 const ShapeOutsideInfo* shape_outside_info =
611 ShapeOutsideInfoForNode(node, &paths, &bounds_quad);
612 if (!shape_outside_info)
613 return;
614
615 if (!paths.shape.length()) {
616 AppendQuad(bounds_quad, config.shape);
617 return;
618 }
619
620 AppendPath(ShapePathBuilder::BuildPath(
621 *node->GetDocument().View(), *node->GetLayoutObject(),
622 *shape_outside_info, paths.shape, scale_),
623 config.shape, Color::kTransparent);
624 if (paths.margin_shape.length())
625 AppendPath(ShapePathBuilder::BuildPath(
626 *node->GetDocument().View(), *node->GetLayoutObject(),
627 *shape_outside_info, paths.margin_shape, scale_),
628 config.shape_margin, Color::kTransparent);
629 }
630
AppendNodeHighlight(Node * node,const InspectorHighlightConfig & highlight_config)631 void InspectorHighlight::AppendNodeHighlight(
632 Node* node,
633 const InspectorHighlightConfig& highlight_config) {
634 LayoutObject* layout_object = node->GetLayoutObject();
635 if (!layout_object)
636 return;
637
638 Vector<FloatQuad> svg_quads;
639 if (BuildSVGQuads(node, svg_quads)) {
640 for (wtf_size_t i = 0; i < svg_quads.size(); ++i) {
641 AppendQuad(svg_quads[i], highlight_config.content,
642 highlight_config.content_outline);
643 }
644 return;
645 }
646
647 FloatQuad content, padding, border, margin;
648 if (!BuildNodeQuads(node, &content, &padding, &border, &margin))
649 return;
650 AppendQuad(content, highlight_config.content,
651 highlight_config.content_outline, "content");
652 AppendQuad(padding, highlight_config.padding, Color::kTransparent, "padding");
653 AppendQuad(border, highlight_config.border, Color::kTransparent, "border");
654 AppendQuad(margin, highlight_config.margin, Color::kTransparent, "margin");
655
656 if (highlight_config.css_grid == Color::kTransparent)
657 return;
658 grid_info_ = protocol::ListValue::create();
659 if (layout_object->IsLayoutGrid()) {
660 grid_info_->pushValue(
661 BuildGridInfo(node->GetDocument().View(), ToLayoutGrid(layout_object),
662 highlight_config.css_grid, scale_, true));
663 }
664 LayoutObject* parent = layout_object->Parent();
665 if (!parent || !parent->IsLayoutGrid())
666 return;
667 if (!BuildNodeQuads(parent->GetNode(), &content, &padding, &border, &margin))
668 return;
669 grid_info_->pushValue(
670 BuildGridInfo(node->GetDocument().View(), ToLayoutGrid(parent),
671 highlight_config.css_grid, scale_, false));
672 }
673
AsProtocolValue() const674 std::unique_ptr<protocol::DictionaryValue> InspectorHighlight::AsProtocolValue()
675 const {
676 std::unique_ptr<protocol::DictionaryValue> object =
677 protocol::DictionaryValue::create();
678 object->setValue("paths", highlight_paths_->clone());
679 object->setBoolean("showRulers", show_rulers_);
680 object->setBoolean("showExtensionLines", show_extension_lines_);
681 if (model_) {
682 std::unique_ptr<protocol::DictionaryValue> distance_info =
683 protocol::DictionaryValue::create();
684 distance_info->setArray(
685 "boxes",
686 protocol::ValueConversions<std::vector<
687 std::unique_ptr<std::vector<double>>>>::toValue(boxes_.get()));
688 distance_info->setArray(
689 "content", protocol::ValueConversions<std::vector<double>>::toValue(
690 model_->getContent()));
691 distance_info->setArray(
692 "padding", protocol::ValueConversions<std::vector<double>>::toValue(
693 model_->getPadding()));
694 distance_info->setArray(
695 "border", protocol::ValueConversions<std::vector<double>>::toValue(
696 model_->getBorder()));
697 distance_info->setValue("style", computed_style_->clone());
698 object->setValue("distanceInfo", std::move(distance_info));
699 }
700 if (element_info_)
701 object->setValue("elementInfo", element_info_->clone());
702 if (grid_info_ && grid_info_->size() > 0)
703 object->setValue("gridInfo", grid_info_->clone());
704 return object;
705 }
706
707 // static
GetBoxModel(Node * node,std::unique_ptr<protocol::DOM::BoxModel> * model,bool use_absolute_zoom)708 bool InspectorHighlight::GetBoxModel(
709 Node* node,
710 std::unique_ptr<protocol::DOM::BoxModel>* model,
711 bool use_absolute_zoom) {
712 node->GetDocument().EnsurePaintLocationDataValidForNode(
713 node, DocumentUpdateReason::kInspector);
714 LayoutObject* layout_object = node->GetLayoutObject();
715 LocalFrameView* view = node->GetDocument().View();
716 if (!layout_object || !view)
717 return false;
718
719 FloatQuad content, padding, border, margin;
720 Vector<FloatQuad> svg_quads;
721 if (BuildSVGQuads(node, svg_quads)) {
722 if (!svg_quads.size())
723 return false;
724 content = svg_quads[0];
725 padding = svg_quads[0];
726 border = svg_quads[0];
727 margin = svg_quads[0];
728 } else if (!BuildNodeQuads(node, &content, &padding, &border, &margin)) {
729 return false;
730 }
731
732 if (use_absolute_zoom) {
733 AdjustForAbsoluteZoom::AdjustFloatQuad(content, *layout_object);
734 AdjustForAbsoluteZoom::AdjustFloatQuad(padding, *layout_object);
735 AdjustForAbsoluteZoom::AdjustFloatQuad(border, *layout_object);
736 AdjustForAbsoluteZoom::AdjustFloatQuad(margin, *layout_object);
737 }
738
739 float scale = 1 / view->GetPage()->GetVisualViewport().Scale();
740 content.Scale(scale, scale);
741 padding.Scale(scale, scale);
742 border.Scale(scale, scale);
743 margin.Scale(scale, scale);
744
745 IntRect bounding_box =
746 view->ConvertToRootFrame(layout_object->AbsoluteBoundingBoxRect());
747 LayoutBoxModelObject* model_object =
748 layout_object->IsBoxModelObject() ? ToLayoutBoxModelObject(layout_object)
749 : nullptr;
750
751 *model =
752 protocol::DOM::BoxModel::create()
753 .setContent(BuildArrayForQuad(content))
754 .setPadding(BuildArrayForQuad(padding))
755 .setBorder(BuildArrayForQuad(border))
756 .setMargin(BuildArrayForQuad(margin))
757 .setWidth(model_object ? AdjustForAbsoluteZoom::AdjustInt(
758 model_object->PixelSnappedOffsetWidth(
759 model_object->OffsetParent()),
760 model_object)
761 : bounding_box.Width())
762 .setHeight(model_object ? AdjustForAbsoluteZoom::AdjustInt(
763 model_object->PixelSnappedOffsetHeight(
764 model_object->OffsetParent()),
765 model_object)
766 : bounding_box.Height())
767 .build();
768
769 Shape::DisplayPaths paths;
770 FloatQuad bounds_quad;
771 protocol::ErrorSupport errors;
772 if (const ShapeOutsideInfo* shape_outside_info =
773 ShapeOutsideInfoForNode(node, &paths, &bounds_quad)) {
774 auto shape = ShapePathBuilder::BuildPath(
775 *view, *layout_object, *shape_outside_info, paths.shape, 1.f);
776 auto margin_shape = ShapePathBuilder::BuildPath(
777 *view, *layout_object, *shape_outside_info, paths.margin_shape, 1.f);
778 (*model)->setShapeOutside(
779 protocol::DOM::ShapeOutsideInfo::create()
780 .setBounds(BuildArrayForQuad(bounds_quad))
781 .setShape(protocol::ValueConversions<
782 protocol::Array<protocol::Value>>::fromValue(shape.get(),
783 &errors))
784 .setMarginShape(
785 protocol::ValueConversions<protocol::Array<protocol::Value>>::
786 fromValue(margin_shape.get(), &errors))
787 .build());
788 }
789
790 return true;
791 }
792
793 // static
BuildSVGQuads(Node * node,Vector<FloatQuad> & quads)794 bool InspectorHighlight::BuildSVGQuads(Node* node, Vector<FloatQuad>& quads) {
795 LayoutObject* layout_object = node->GetLayoutObject();
796 if (!layout_object)
797 return false;
798 if (!layout_object->GetNode() || !layout_object->GetNode()->IsSVGElement() ||
799 layout_object->IsSVGRoot())
800 return false;
801 CollectQuads(node, quads);
802 return true;
803 }
804
805 // static
GetContentQuads(Node * node,std::unique_ptr<protocol::Array<protocol::Array<double>>> * result)806 bool InspectorHighlight::GetContentQuads(
807 Node* node,
808 std::unique_ptr<protocol::Array<protocol::Array<double>>>* result) {
809 LayoutObject* layout_object = node->GetLayoutObject();
810 LocalFrameView* view = node->GetDocument().View();
811 if (!layout_object || !view)
812 return false;
813 Vector<FloatQuad> quads;
814 CollectQuads(node, quads);
815 float scale = 1 / view->GetPage()->GetVisualViewport().Scale();
816 for (FloatQuad& quad : quads) {
817 AdjustForAbsoluteZoom::AdjustFloatQuad(quad, *layout_object);
818 quad.Scale(scale, scale);
819 }
820
821 result->reset(new protocol::Array<protocol::Array<double>>());
822 for (FloatQuad& quad : quads)
823 (*result)->emplace_back(BuildArrayForQuad(quad));
824 return true;
825 }
826
BuildNodeQuads(Node * node,FloatQuad * content,FloatQuad * padding,FloatQuad * border,FloatQuad * margin)827 bool InspectorHighlight::BuildNodeQuads(Node* node,
828 FloatQuad* content,
829 FloatQuad* padding,
830 FloatQuad* border,
831 FloatQuad* margin) {
832 LayoutObject* layout_object = node->GetLayoutObject();
833 if (!layout_object)
834 return false;
835
836 LocalFrameView* containing_view = layout_object->GetFrameView();
837 if (!containing_view)
838 return false;
839 if (!layout_object->IsBox() && !layout_object->IsLayoutInline() &&
840 !layout_object->IsText())
841 return false;
842
843 PhysicalRect content_box;
844 PhysicalRect padding_box;
845 PhysicalRect border_box;
846 PhysicalRect margin_box;
847
848 if (layout_object->IsText()) {
849 LayoutText* layout_text = ToLayoutText(layout_object);
850 PhysicalRect text_rect = layout_text->PhysicalVisualOverflowRect();
851 content_box = text_rect;
852 padding_box = text_rect;
853 border_box = text_rect;
854 margin_box = text_rect;
855 } else if (layout_object->IsBox()) {
856 LayoutBox* layout_box = ToLayoutBox(layout_object);
857
858 // LayoutBox returns the "pure" content area box, exclusive of the
859 // scrollbars (if present), which also count towards the content area in
860 // CSS.
861 const int vertical_scrollbar_width = layout_box->VerticalScrollbarWidth();
862 const int horizontal_scrollbar_height =
863 layout_box->HorizontalScrollbarHeight();
864 content_box = layout_box->PhysicalContentBoxRect();
865 content_box.SetWidth(content_box.Width() + vertical_scrollbar_width);
866 content_box.SetHeight(content_box.Height() + horizontal_scrollbar_height);
867
868 padding_box = layout_box->PhysicalPaddingBoxRect();
869 padding_box.SetWidth(padding_box.Width() + vertical_scrollbar_width);
870 padding_box.SetHeight(padding_box.Height() + horizontal_scrollbar_height);
871
872 border_box = layout_box->PhysicalBorderBoxRect();
873
874 margin_box = PhysicalRect(border_box.X() - layout_box->MarginLeft(),
875 border_box.Y() - layout_box->MarginTop(),
876 border_box.Width() + layout_box->MarginWidth(),
877 border_box.Height() + layout_box->MarginHeight());
878 } else {
879 LayoutInline* layout_inline = ToLayoutInline(layout_object);
880
881 // LayoutInline's bounding box includes paddings and borders, excludes
882 // margins.
883 border_box = layout_inline->PhysicalLinesBoundingBox();
884 padding_box =
885 PhysicalRect(border_box.X() + layout_inline->BorderLeft(),
886 border_box.Y() + layout_inline->BorderTop(),
887 border_box.Width() - layout_inline->BorderLeft() -
888 layout_inline->BorderRight(),
889 border_box.Height() - layout_inline->BorderTop() -
890 layout_inline->BorderBottom());
891 content_box =
892 PhysicalRect(padding_box.X() + layout_inline->PaddingLeft(),
893 padding_box.Y() + layout_inline->PaddingTop(),
894 padding_box.Width() - layout_inline->PaddingLeft() -
895 layout_inline->PaddingRight(),
896 padding_box.Height() - layout_inline->PaddingTop() -
897 layout_inline->PaddingBottom());
898 // Ignore marginTop and marginBottom for inlines.
899 margin_box = PhysicalRect(
900 border_box.X() - layout_inline->MarginLeft(), border_box.Y(),
901 border_box.Width() + layout_inline->MarginWidth(), border_box.Height());
902 }
903
904 *content = layout_object->LocalRectToAbsoluteQuad(content_box);
905 *padding = layout_object->LocalRectToAbsoluteQuad(padding_box);
906 *border = layout_object->LocalRectToAbsoluteQuad(border_box);
907 *margin = layout_object->LocalRectToAbsoluteQuad(margin_box);
908
909 FrameQuadToViewport(containing_view, *content);
910 FrameQuadToViewport(containing_view, *padding);
911 FrameQuadToViewport(containing_view, *border);
912 FrameQuadToViewport(containing_view, *margin);
913
914 return true;
915 }
916
917 // static
DefaultConfig()918 InspectorHighlightConfig InspectorHighlight::DefaultConfig() {
919 InspectorHighlightConfig config;
920 config.content = Color(255, 0, 0, 0);
921 config.content_outline = Color(128, 0, 0, 0);
922 config.padding = Color(0, 255, 0, 0);
923 config.border = Color(0, 0, 255, 0);
924 config.margin = Color(255, 255, 255, 0);
925 config.event_target = Color(128, 128, 128, 0);
926 config.shape = Color(0, 0, 0, 0);
927 config.shape_margin = Color(128, 128, 128, 0);
928 config.show_info = true;
929 config.show_styles = false;
930 config.show_rulers = true;
931 config.show_extension_lines = true;
932 config.css_grid = Color(128, 128, 128, 0);
933 return config;
934 }
935
936 } // namespace blink
937