| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "third_party/blink/renderer/core/inspector/inspector_highlight.h" |
| |
| #include "base/macros.h" |
| #include "third_party/blink/renderer/core/css/css_color_value.h" |
| #include "third_party/blink/renderer/core/css/css_computed_style_declaration.h" |
| #include "third_party/blink/renderer/core/css/css_property_names.h" |
| #include "third_party/blink/renderer/core/dom/pseudo_element.h" |
| #include "third_party/blink/renderer/core/frame/local_frame_view.h" |
| #include "third_party/blink/renderer/core/frame/visual_viewport.h" |
| #include "third_party/blink/renderer/core/geometry/dom_rect.h" |
| #include "third_party/blink/renderer/core/layout/adjust_for_absolute_zoom.h" |
| #include "third_party/blink/renderer/core/layout/layout_box.h" |
| #include "third_party/blink/renderer/core/layout/layout_grid.h" |
| #include "third_party/blink/renderer/core/layout/layout_inline.h" |
| #include "third_party/blink/renderer/core/layout/layout_object.h" |
| #include "third_party/blink/renderer/core/layout/shapes/shape_outside_info.h" |
| #include "third_party/blink/renderer/core/page/chrome_client.h" |
| #include "third_party/blink/renderer/core/page/page.h" |
| #include "third_party/blink/renderer/core/style/computed_style_constants.h" |
| #include "third_party/blink/renderer/platform/graphics/path.h" |
| #include "third_party/blink/renderer/platform/web_test_support.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| class PathBuilder { |
| STACK_ALLOCATED(); |
| |
| public: |
| PathBuilder() : path_(protocol::ListValue::create()) {} |
| virtual ~PathBuilder() = default; |
| |
| std::unique_ptr<protocol::ListValue> Release() { return std::move(path_); } |
| |
| void AppendPath(const Path& path, float scale) { |
| Path transform_path(path); |
| transform_path.Transform(AffineTransform().Scale(scale)); |
| transform_path.Apply(this, &PathBuilder::AppendPathElement); |
| } |
| |
| protected: |
| virtual FloatPoint TranslatePoint(const FloatPoint& point) { return point; } |
| |
| private: |
| static void AppendPathElement(void* path_builder, |
| const PathElement* path_element) { |
| static_cast<PathBuilder*>(path_builder)->AppendPathElement(path_element); |
| } |
| |
| void AppendPathElement(const PathElement*); |
| void AppendPathCommandAndPoints(const char* command, |
| const FloatPoint points[], |
| size_t length); |
| |
| std::unique_ptr<protocol::ListValue> path_; |
| DISALLOW_COPY_AND_ASSIGN(PathBuilder); |
| }; |
| |
| void PathBuilder::AppendPathCommandAndPoints(const char* command, |
| const FloatPoint points[], |
| size_t length) { |
| path_->pushValue(protocol::StringValue::create(command)); |
| for (size_t i = 0; i < length; i++) { |
| FloatPoint point = TranslatePoint(points[i]); |
| path_->pushValue(protocol::FundamentalValue::create(point.X())); |
| path_->pushValue(protocol::FundamentalValue::create(point.Y())); |
| } |
| } |
| |
| void PathBuilder::AppendPathElement(const PathElement* path_element) { |
| switch (path_element->type) { |
| // The points member will contain 1 value. |
| case kPathElementMoveToPoint: |
| AppendPathCommandAndPoints("M", path_element->points, 1); |
| break; |
| // The points member will contain 1 value. |
| case kPathElementAddLineToPoint: |
| AppendPathCommandAndPoints("L", path_element->points, 1); |
| break; |
| // The points member will contain 3 values. |
| case kPathElementAddCurveToPoint: |
| AppendPathCommandAndPoints("C", path_element->points, 3); |
| break; |
| // The points member will contain 2 values. |
| case kPathElementAddQuadCurveToPoint: |
| AppendPathCommandAndPoints("Q", path_element->points, 2); |
| break; |
| // The points member will contain no values. |
| case kPathElementCloseSubpath: |
| AppendPathCommandAndPoints("Z", nullptr, 0); |
| break; |
| } |
| } |
| |
| class ShapePathBuilder : public PathBuilder { |
| public: |
| ShapePathBuilder(LocalFrameView& view, |
| LayoutObject& layout_object, |
| const ShapeOutsideInfo& shape_outside_info) |
| : view_(&view), |
| layout_object_(layout_object), |
| shape_outside_info_(shape_outside_info) {} |
| |
| static std::unique_ptr<protocol::ListValue> BuildPath( |
| LocalFrameView& view, |
| LayoutObject& layout_object, |
| const ShapeOutsideInfo& shape_outside_info, |
| const Path& path, |
| float scale) { |
| ShapePathBuilder builder(view, layout_object, shape_outside_info); |
| builder.AppendPath(path, scale); |
| return builder.Release(); |
| } |
| |
| protected: |
| FloatPoint TranslatePoint(const FloatPoint& point) override { |
| FloatPoint layout_object_point = |
| shape_outside_info_.ShapeToLayoutObjectPoint(point); |
| return FloatPoint(view_->FrameToViewport( |
| RoundedIntPoint(layout_object_.LocalToAbsolute(layout_object_point)))); |
| } |
| |
| private: |
| Member<LocalFrameView> view_; |
| LayoutObject& layout_object_; |
| const ShapeOutsideInfo& shape_outside_info_; |
| }; |
| |
| std::unique_ptr<protocol::Array<double>> BuildArrayForQuad( |
| const FloatQuad& quad) { |
| std::unique_ptr<protocol::Array<double>> array = |
| protocol::Array<double>::create(); |
| array->addItem(quad.P1().X()); |
| array->addItem(quad.P1().Y()); |
| array->addItem(quad.P2().X()); |
| array->addItem(quad.P2().Y()); |
| array->addItem(quad.P3().X()); |
| array->addItem(quad.P3().Y()); |
| array->addItem(quad.P4().X()); |
| array->addItem(quad.P4().Y()); |
| return array; |
| } |
| |
| Path QuadToPath(const FloatQuad& quad) { |
| Path quad_path; |
| quad_path.MoveTo(quad.P1()); |
| quad_path.AddLineTo(quad.P2()); |
| quad_path.AddLineTo(quad.P3()); |
| quad_path.AddLineTo(quad.P4()); |
| quad_path.CloseSubpath(); |
| return quad_path; |
| } |
| |
| FloatPoint FramePointToViewport(const LocalFrameView* view, |
| FloatPoint point_in_frame) { |
| FloatPoint point_in_root_frame = view->ConvertToRootFrame(point_in_frame); |
| return view->GetPage()->GetVisualViewport().RootFrameToViewport( |
| point_in_root_frame); |
| } |
| |
| void FrameQuadToViewport(const LocalFrameView* view, FloatQuad& quad) { |
| quad.SetP1(FramePointToViewport(view, quad.P1())); |
| quad.SetP2(FramePointToViewport(view, quad.P2())); |
| quad.SetP3(FramePointToViewport(view, quad.P3())); |
| quad.SetP4(FramePointToViewport(view, quad.P4())); |
| } |
| |
| const ShapeOutsideInfo* ShapeOutsideInfoForNode(Node* node, |
| Shape::DisplayPaths* paths, |
| FloatQuad* bounds) { |
| LayoutObject* layout_object = node->GetLayoutObject(); |
| if (!layout_object || !layout_object->IsBox() || |
| !ToLayoutBox(layout_object)->GetShapeOutsideInfo()) |
| return nullptr; |
| |
| LocalFrameView* containing_view = node->GetDocument().View(); |
| LayoutBox* layout_box = ToLayoutBox(layout_object); |
| const ShapeOutsideInfo* shape_outside_info = |
| layout_box->GetShapeOutsideInfo(); |
| |
| shape_outside_info->ComputedShape().BuildDisplayPaths(*paths); |
| |
| LayoutRect shape_bounds = |
| shape_outside_info->ComputedShapePhysicalBoundingBox(); |
| *bounds = layout_box->LocalToAbsoluteQuad(FloatRect(shape_bounds)); |
| FrameQuadToViewport(containing_view, *bounds); |
| |
| return shape_outside_info; |
| } |
| |
| String ToHEXA(const Color& color) { |
| return String::Format("#%02X%02X%02X%02X", color.Red(), color.Green(), |
| color.Blue(), color.Alpha()); |
| } |
| |
| void AppendStyleInfo(Node* node, |
| protocol::DictionaryValue* element_info, |
| const InspectorHighlightContrastInfo& node_contrast) { |
| std::unique_ptr<protocol::DictionaryValue> computed_style = |
| protocol::DictionaryValue::create(); |
| CSSStyleDeclaration* style = CSSComputedStyleDeclaration::Create(node, true); |
| Vector<AtomicString> properties; |
| |
| // For text nodes, we can show color & font properties. |
| bool has_text_children = false; |
| for (Node* child = node->firstChild(); !has_text_children && child; |
| child = child->nextSibling()) { |
| has_text_children = child->IsTextNode(); |
| } |
| if (has_text_children) { |
| properties.push_back("color"); |
| properties.push_back("font-family"); |
| properties.push_back("font-size"); |
| properties.push_back("line-height"); |
| } |
| |
| properties.push_back("padding"); |
| properties.push_back("margin"); |
| properties.push_back("background-color"); |
| |
| for (size_t i = 0; i < properties.size(); ++i) { |
| const CSSValue* value = style->GetPropertyCSSValueInternal(properties[i]); |
| if (!value) |
| continue; |
| if (value->IsColorValue()) { |
| Color color = static_cast<const cssvalue::CSSColorValue*>(value)->Value(); |
| computed_style->setString(properties[i], ToHEXA(color)); |
| } else { |
| computed_style->setString(properties[i], value->CssText()); |
| } |
| } |
| element_info->setValue("style", std::move(computed_style)); |
| |
| if (!node_contrast.font_size.IsEmpty()) { |
| std::unique_ptr<protocol::DictionaryValue> contrast = |
| protocol::DictionaryValue::create(); |
| contrast->setString("fontSize", node_contrast.font_size); |
| contrast->setString("fontWeight", node_contrast.font_weight); |
| contrast->setString("backgroundColor", |
| ToHEXA(node_contrast.background_color)); |
| element_info->setValue("contrast", std::move(contrast)); |
| } |
| } |
| |
| std::unique_ptr<protocol::DictionaryValue> BuildElementInfo(Element* element) { |
| std::unique_ptr<protocol::DictionaryValue> element_info = |
| protocol::DictionaryValue::create(); |
| Element* real_element = element; |
| PseudoElement* pseudo_element = nullptr; |
| if (element->IsPseudoElement()) { |
| pseudo_element = ToPseudoElement(element); |
| real_element = element->ParentOrShadowHostElement(); |
| } |
| bool is_xhtml = real_element->GetDocument().IsXHTMLDocument(); |
| element_info->setString( |
| "tagName", is_xhtml ? real_element->nodeName() |
| : real_element->nodeName().DeprecatedLower()); |
| element_info->setString("idValue", real_element->GetIdAttribute()); |
| StringBuilder class_names; |
| if (real_element->HasClass() && real_element->IsStyledElement()) { |
| HashSet<AtomicString> used_class_names; |
| const SpaceSplitString& class_names_string = real_element->ClassNames(); |
| wtf_size_t class_name_count = class_names_string.size(); |
| for (wtf_size_t i = 0; i < class_name_count; ++i) { |
| const AtomicString& class_name = class_names_string[i]; |
| if (!used_class_names.insert(class_name).is_new_entry) |
| continue; |
| class_names.Append('.'); |
| class_names.Append(class_name); |
| } |
| } |
| if (pseudo_element) { |
| if (pseudo_element->GetPseudoId() == kPseudoIdBefore) |
| class_names.Append("::before"); |
| else if (pseudo_element->GetPseudoId() == kPseudoIdAfter) |
| class_names.Append("::after"); |
| } |
| if (!class_names.IsEmpty()) |
| element_info->setString("className", class_names.ToString()); |
| |
| LayoutObject* layout_object = element->GetLayoutObject(); |
| LocalFrameView* containing_view = element->GetDocument().View(); |
| if (!layout_object || !containing_view) |
| return element_info; |
| |
| // layoutObject the getBoundingClientRect() data in the tooltip |
| // to be consistent with the rulers (see http://crbug.com/262338). |
| DOMRect* bounding_box = element->getBoundingClientRect(); |
| element_info->setString("nodeWidth", String::Number(bounding_box->width())); |
| element_info->setString("nodeHeight", String::Number(bounding_box->height())); |
| return element_info; |
| } |
| |
| std::unique_ptr<protocol::DictionaryValue> BuildTextNodeInfo(Text* text_node) { |
| std::unique_ptr<protocol::DictionaryValue> text_info = |
| protocol::DictionaryValue::create(); |
| LayoutObject* layout_object = text_node->GetLayoutObject(); |
| if (!layout_object || !layout_object->IsText()) |
| return text_info; |
| LayoutRect bounding_box = ToLayoutText(layout_object)->VisualOverflowRect(); |
| text_info->setString("nodeWidth", bounding_box.Width().ToString()); |
| text_info->setString("nodeHeight", bounding_box.Height().ToString()); |
| text_info->setString("tagName", "#text"); |
| return text_info; |
| } |
| |
| std::unique_ptr<protocol::Value> BuildGapAndPositions( |
| double origin, |
| LayoutUnit gap, |
| const Vector<LayoutUnit>& positions, |
| float scale) { |
| std::unique_ptr<protocol::DictionaryValue> result = |
| protocol::DictionaryValue::create(); |
| result->setDouble("origin", floor(origin * scale)); |
| result->setDouble("gap", round(gap * scale)); |
| |
| std::unique_ptr<protocol::ListValue> spans = protocol::ListValue::create(); |
| for (const LayoutUnit& position : positions) { |
| spans->pushValue( |
| protocol::FundamentalValue::create(round(position * scale))); |
| } |
| result->setValue("positions", std::move(spans)); |
| |
| return result; |
| } |
| |
| std::unique_ptr<protocol::DictionaryValue> BuildGridInfo( |
| LayoutGrid* layout_grid, |
| FloatPoint origin, |
| Color color, |
| float scale, |
| bool isPrimary) { |
| std::unique_ptr<protocol::DictionaryValue> grid_info = |
| protocol::DictionaryValue::create(); |
| |
| grid_info->setValue( |
| "rows", BuildGapAndPositions(origin.Y(), |
| layout_grid->GridGap(kForRows) + |
| layout_grid->GridItemOffset(kForRows), |
| layout_grid->RowPositions(), scale)); |
| grid_info->setValue( |
| "columns", |
| BuildGapAndPositions(origin.X(), |
| layout_grid->GridGap(kForColumns) + |
| layout_grid->GridItemOffset(kForColumns), |
| layout_grid->ColumnPositions(), scale)); |
| grid_info->setString("color", color.Serialized()); |
| grid_info->setBoolean("isPrimaryGrid", isPrimary); |
| return grid_info; |
| } |
| |
| void CollectQuadsRecursive(Node* node, Vector<FloatQuad>& out_quads) { |
| LayoutObject* layout_object = node->GetLayoutObject(); |
| if (!layout_object) |
| return; |
| |
| // For inline elements, absoluteQuads will return a line box based on the |
| // line-height and font metrics, which is technically incorrect as replaced |
| // elements like images should use their intristic height and expand the |
| // linebox as needed. To get an appropriate quads we descend |
| // into the children and have them add their boxes. |
| if (layout_object->IsLayoutInline()) { |
| for (Node* child = LayoutTreeBuilderTraversal::FirstChild(*node); child; |
| child = LayoutTreeBuilderTraversal::NextSibling(*child)) |
| CollectQuadsRecursive(child, out_quads); |
| } else { |
| layout_object->AbsoluteQuads(out_quads); |
| } |
| } |
| |
| void CollectQuads(Node* node, Vector<FloatQuad>& out_quads) { |
| CollectQuadsRecursive(node, out_quads); |
| LocalFrameView* containing_view = |
| node->GetLayoutObject() ? node->GetLayoutObject()->GetFrameView() |
| : nullptr; |
| if (containing_view) { |
| for (FloatQuad& quad : out_quads) |
| FrameQuadToViewport(containing_view, quad); |
| } |
| } |
| |
| } // namespace |
| |
| InspectorHighlight::InspectorHighlight(float scale) |
| : highlight_paths_(protocol::ListValue::create()), |
| show_rulers_(false), |
| show_extension_lines_(false), |
| scale_(scale) {} |
| |
| InspectorHighlightConfig::InspectorHighlightConfig() |
| : show_info(false), |
| show_styles(false), |
| show_rulers(false), |
| show_extension_lines(false) {} |
| |
| InspectorHighlight::InspectorHighlight( |
| Node* node, |
| const InspectorHighlightConfig& highlight_config, |
| const InspectorHighlightContrastInfo& node_contrast, |
| bool append_element_info) |
| : highlight_paths_(protocol::ListValue::create()), |
| show_rulers_(highlight_config.show_rulers), |
| show_extension_lines_(highlight_config.show_extension_lines), |
| scale_(1.f) { |
| LocalFrameView* frame_view = node->GetDocument().View(); |
| if (frame_view) |
| scale_ = 1.f / frame_view->GetChromeClient()->WindowToViewportScalar(1.f); |
| AppendPathsForShapeOutside(node, highlight_config); |
| AppendNodeHighlight(node, highlight_config); |
| if (append_element_info && node->IsElementNode()) |
| element_info_ = BuildElementInfo(ToElement(node)); |
| else if (append_element_info && node->IsTextNode()) |
| element_info_ = BuildTextNodeInfo(ToText(node)); |
| if (element_info_ && highlight_config.show_styles) |
| AppendStyleInfo(node, element_info_.get(), node_contrast); |
| } |
| |
| InspectorHighlight::~InspectorHighlight() = default; |
| |
| void InspectorHighlight::AppendQuad(const FloatQuad& quad, |
| const Color& fill_color, |
| const Color& outline_color, |
| const String& name) { |
| Path path = QuadToPath(quad); |
| PathBuilder builder; |
| builder.AppendPath(path, scale_); |
| AppendPath(builder.Release(), fill_color, outline_color, name); |
| } |
| |
| void InspectorHighlight::AppendPath(std::unique_ptr<protocol::ListValue> path, |
| const Color& fill_color, |
| const Color& outline_color, |
| const String& name) { |
| std::unique_ptr<protocol::DictionaryValue> object = |
| protocol::DictionaryValue::create(); |
| object->setValue("path", std::move(path)); |
| object->setString("fillColor", fill_color.Serialized()); |
| if (outline_color != Color::kTransparent) |
| object->setString("outlineColor", outline_color.Serialized()); |
| if (!name.IsEmpty()) |
| object->setString("name", name); |
| highlight_paths_->pushValue(std::move(object)); |
| } |
| |
| void InspectorHighlight::AppendEventTargetQuads( |
| Node* event_target_node, |
| const InspectorHighlightConfig& highlight_config) { |
| if (event_target_node->GetLayoutObject()) { |
| FloatQuad border, unused; |
| if (BuildNodeQuads(event_target_node, &unused, &unused, &border, &unused)) |
| AppendQuad(border, highlight_config.event_target); |
| } |
| } |
| |
| void InspectorHighlight::AppendPathsForShapeOutside( |
| Node* node, |
| const InspectorHighlightConfig& config) { |
| Shape::DisplayPaths paths; |
| FloatQuad bounds_quad; |
| |
| const ShapeOutsideInfo* shape_outside_info = |
| ShapeOutsideInfoForNode(node, &paths, &bounds_quad); |
| if (!shape_outside_info) |
| return; |
| |
| if (!paths.shape.length()) { |
| AppendQuad(bounds_quad, config.shape); |
| return; |
| } |
| |
| AppendPath(ShapePathBuilder::BuildPath( |
| *node->GetDocument().View(), *node->GetLayoutObject(), |
| *shape_outside_info, paths.shape, scale_), |
| config.shape, Color::kTransparent); |
| if (paths.margin_shape.length()) |
| AppendPath(ShapePathBuilder::BuildPath( |
| *node->GetDocument().View(), *node->GetLayoutObject(), |
| *shape_outside_info, paths.margin_shape, scale_), |
| config.shape_margin, Color::kTransparent); |
| } |
| |
| void InspectorHighlight::AppendNodeHighlight( |
| Node* node, |
| const InspectorHighlightConfig& highlight_config) { |
| LayoutObject* layout_object = node->GetLayoutObject(); |
| if (!layout_object) |
| return; |
| |
| // Just for testing, invert the content color for nodes rendered by LayoutNG. |
| // TODO(layout-dev): Stop munging the color before NG ships. crbug.com/869866 |
| Color content_color = |
| layout_object->IsLayoutNGObject() && !WebTestSupport::IsRunningWebTest() |
| ? Color(highlight_config.content.Rgb() ^ 0x00ffffff) |
| : highlight_config.content; |
| |
| Vector<FloatQuad> svg_quads; |
| if (BuildSVGQuads(node, svg_quads)) { |
| for (wtf_size_t i = 0; i < svg_quads.size(); ++i) { |
| AppendQuad(svg_quads[i], content_color, highlight_config.content_outline); |
| } |
| return; |
| } |
| |
| FloatQuad content, padding, border, margin; |
| if (!BuildNodeQuads(node, &content, &padding, &border, &margin)) |
| return; |
| AppendQuad(content, content_color, highlight_config.content_outline, |
| "content"); |
| AppendQuad(padding, highlight_config.padding, Color::kTransparent, "padding"); |
| AppendQuad(border, highlight_config.border, Color::kTransparent, "border"); |
| AppendQuad(margin, highlight_config.margin, Color::kTransparent, "margin"); |
| |
| if (highlight_config.css_grid == Color::kTransparent) |
| return; |
| grid_info_ = protocol::ListValue::create(); |
| if (layout_object->IsLayoutGrid()) { |
| grid_info_->pushValue(BuildGridInfo(ToLayoutGrid(layout_object), |
| border.P1(), highlight_config.css_grid, |
| scale_, true)); |
| } |
| LayoutObject* parent = layout_object->Parent(); |
| if (!parent || !parent->IsLayoutGrid()) |
| return; |
| if (!BuildNodeQuads(parent->GetNode(), &content, &padding, &border, &margin)) |
| return; |
| grid_info_->pushValue(BuildGridInfo(ToLayoutGrid(parent), border.P1(), |
| highlight_config.css_grid, scale_, |
| false)); |
| } |
| |
| std::unique_ptr<protocol::DictionaryValue> InspectorHighlight::AsProtocolValue() |
| const { |
| std::unique_ptr<protocol::DictionaryValue> object = |
| protocol::DictionaryValue::create(); |
| object->setValue("paths", highlight_paths_->clone()); |
| object->setBoolean("showRulers", show_rulers_); |
| object->setBoolean("showExtensionLines", show_extension_lines_); |
| if (element_info_) |
| object->setValue("elementInfo", element_info_->clone()); |
| if (grid_info_ && grid_info_->size() > 0) |
| object->setValue("gridInfo", grid_info_->clone()); |
| return object; |
| } |
| |
| // static |
| bool InspectorHighlight::GetBoxModel( |
| Node* node, |
| std::unique_ptr<protocol::DOM::BoxModel>* model) { |
| node->GetDocument().EnsurePaintLocationDataValidForNode(node); |
| LayoutObject* layout_object = node->GetLayoutObject(); |
| LocalFrameView* view = node->GetDocument().View(); |
| if (!layout_object || !view) |
| return false; |
| |
| FloatQuad content, padding, border, margin; |
| Vector<FloatQuad> svg_quads; |
| if (BuildSVGQuads(node, svg_quads)) { |
| if (!svg_quads.size()) |
| return false; |
| content = svg_quads[0]; |
| padding = svg_quads[0]; |
| border = svg_quads[0]; |
| margin = svg_quads[0]; |
| } else if (!BuildNodeQuads(node, &content, &padding, &border, &margin)) { |
| return false; |
| } |
| |
| AdjustForAbsoluteZoom::AdjustFloatQuad(content, *layout_object); |
| AdjustForAbsoluteZoom::AdjustFloatQuad(padding, *layout_object); |
| AdjustForAbsoluteZoom::AdjustFloatQuad(border, *layout_object); |
| AdjustForAbsoluteZoom::AdjustFloatQuad(margin, *layout_object); |
| |
| float scale = 1 / view->GetPage()->GetVisualViewport().Scale(); |
| content.Scale(scale, scale); |
| padding.Scale(scale, scale); |
| border.Scale(scale, scale); |
| margin.Scale(scale, scale); |
| |
| IntRect bounding_box = |
| view->ConvertToRootFrame(layout_object->AbsoluteBoundingBoxRect()); |
| LayoutBoxModelObject* model_object = |
| layout_object->IsBoxModelObject() ? ToLayoutBoxModelObject(layout_object) |
| : nullptr; |
| |
| *model = |
| protocol::DOM::BoxModel::create() |
| .setContent(BuildArrayForQuad(content)) |
| .setPadding(BuildArrayForQuad(padding)) |
| .setBorder(BuildArrayForQuad(border)) |
| .setMargin(BuildArrayForQuad(margin)) |
| .setWidth(model_object ? AdjustForAbsoluteZoom::AdjustInt( |
| model_object->PixelSnappedOffsetWidth( |
| model_object->OffsetParent()), |
| model_object) |
| : bounding_box.Width()) |
| .setHeight(model_object ? AdjustForAbsoluteZoom::AdjustInt( |
| model_object->PixelSnappedOffsetHeight( |
| model_object->OffsetParent()), |
| model_object) |
| : bounding_box.Height()) |
| .build(); |
| |
| Shape::DisplayPaths paths; |
| FloatQuad bounds_quad; |
| protocol::ErrorSupport errors; |
| if (const ShapeOutsideInfo* shape_outside_info = |
| ShapeOutsideInfoForNode(node, &paths, &bounds_quad)) { |
| (*model)->setShapeOutside( |
| protocol::DOM::ShapeOutsideInfo::create() |
| .setBounds(BuildArrayForQuad(bounds_quad)) |
| .setShape(protocol::Array<protocol::Value>::fromValue( |
| ShapePathBuilder::BuildPath(*view, *layout_object, |
| *shape_outside_info, paths.shape, |
| 1.f) |
| .get(), |
| &errors)) |
| .setMarginShape(protocol::Array<protocol::Value>::fromValue( |
| ShapePathBuilder::BuildPath(*view, *layout_object, |
| *shape_outside_info, |
| paths.margin_shape, 1.f) |
| .get(), |
| &errors)) |
| .build()); |
| } |
| |
| return true; |
| } |
| |
| // static |
| bool InspectorHighlight::BuildSVGQuads(Node* node, Vector<FloatQuad>& quads) { |
| LayoutObject* layout_object = node->GetLayoutObject(); |
| if (!layout_object) |
| return false; |
| if (!layout_object->GetNode() || !layout_object->GetNode()->IsSVGElement() || |
| layout_object->IsSVGRoot()) |
| return false; |
| CollectQuads(node, quads); |
| return true; |
| } |
| |
| // static |
| bool InspectorHighlight::GetContentQuads( |
| Node* node, |
| std::unique_ptr<protocol::Array<protocol::Array<double>>>* result) { |
| LayoutObject* layout_object = node->GetLayoutObject(); |
| LocalFrameView* view = node->GetDocument().View(); |
| if (!layout_object || !view) |
| return false; |
| Vector<FloatQuad> quads; |
| CollectQuads(node, quads); |
| float scale = 1 / view->GetPage()->GetVisualViewport().Scale(); |
| for (FloatQuad& quad : quads) { |
| AdjustForAbsoluteZoom::AdjustFloatQuad(quad, *layout_object); |
| quad.Scale(scale, scale); |
| } |
| |
| result->reset(new protocol::Array<protocol::Array<double>>()); |
| for (FloatQuad& quad : quads) |
| (*result)->addItem(BuildArrayForQuad(quad)); |
| return true; |
| } |
| |
| bool InspectorHighlight::BuildNodeQuads(Node* node, |
| FloatQuad* content, |
| FloatQuad* padding, |
| FloatQuad* border, |
| FloatQuad* margin) { |
| LayoutObject* layout_object = node->GetLayoutObject(); |
| if (!layout_object) |
| return false; |
| |
| LocalFrameView* containing_view = layout_object->GetFrameView(); |
| if (!containing_view) |
| return false; |
| if (!layout_object->IsBox() && !layout_object->IsLayoutInline() && |
| !layout_object->IsText()) |
| return false; |
| |
| LayoutRect content_box; |
| LayoutRect padding_box; |
| LayoutRect border_box; |
| LayoutRect margin_box; |
| |
| if (layout_object->IsText()) { |
| LayoutText* layout_text = ToLayoutText(layout_object); |
| LayoutRect text_rect = layout_text->VisualOverflowRect(); |
| content_box = text_rect; |
| padding_box = text_rect; |
| border_box = text_rect; |
| margin_box = text_rect; |
| } else if (layout_object->IsBox()) { |
| LayoutBox* layout_box = ToLayoutBox(layout_object); |
| |
| // LayoutBox returns the "pure" content area box, exclusive of the |
| // scrollbars (if present), which also count towards the content area in |
| // CSS. |
| const int vertical_scrollbar_width = layout_box->VerticalScrollbarWidth(); |
| const int horizontal_scrollbar_height = |
| layout_box->HorizontalScrollbarHeight(); |
| content_box = layout_box->PhysicalContentBoxRect(); |
| content_box.SetWidth(content_box.Width() + vertical_scrollbar_width); |
| content_box.SetHeight(content_box.Height() + horizontal_scrollbar_height); |
| |
| padding_box = layout_box->PhysicalPaddingBoxRect(); |
| padding_box.SetWidth(padding_box.Width() + vertical_scrollbar_width); |
| padding_box.SetHeight(padding_box.Height() + horizontal_scrollbar_height); |
| |
| border_box = layout_box->BorderBoxRect(); |
| |
| margin_box = LayoutRect(border_box.X() - layout_box->MarginLeft(), |
| border_box.Y() - layout_box->MarginTop(), |
| border_box.Width() + layout_box->MarginWidth(), |
| border_box.Height() + layout_box->MarginHeight()); |
| } else { |
| LayoutInline* layout_inline = ToLayoutInline(layout_object); |
| |
| // LayoutInline's bounding box includes paddings and borders, excludes |
| // margins. |
| border_box = LayoutRect(layout_inline->LinesBoundingBox()); |
| padding_box = LayoutRect(border_box.X() + layout_inline->BorderLeft(), |
| border_box.Y() + layout_inline->BorderTop(), |
| border_box.Width() - layout_inline->BorderLeft() - |
| layout_inline->BorderRight(), |
| border_box.Height() - layout_inline->BorderTop() - |
| layout_inline->BorderBottom()); |
| content_box = |
| LayoutRect(padding_box.X() + layout_inline->PaddingLeft(), |
| padding_box.Y() + layout_inline->PaddingTop(), |
| padding_box.Width() - layout_inline->PaddingLeft() - |
| layout_inline->PaddingRight(), |
| padding_box.Height() - layout_inline->PaddingTop() - |
| layout_inline->PaddingBottom()); |
| // Ignore marginTop and marginBottom for inlines. |
| margin_box = LayoutRect( |
| border_box.X() - layout_inline->MarginLeft(), border_box.Y(), |
| border_box.Width() + layout_inline->MarginWidth(), border_box.Height()); |
| } |
| |
| *content = layout_object->LocalToAbsoluteQuad(FloatRect(content_box)); |
| *padding = layout_object->LocalToAbsoluteQuad(FloatRect(padding_box)); |
| *border = layout_object->LocalToAbsoluteQuad(FloatRect(border_box)); |
| *margin = layout_object->LocalToAbsoluteQuad(FloatRect(margin_box)); |
| |
| FrameQuadToViewport(containing_view, *content); |
| FrameQuadToViewport(containing_view, *padding); |
| FrameQuadToViewport(containing_view, *border); |
| FrameQuadToViewport(containing_view, *margin); |
| |
| return true; |
| } |
| |
| // static |
| InspectorHighlightConfig InspectorHighlight::DefaultConfig() { |
| InspectorHighlightConfig config; |
| config.content = Color(255, 0, 0, 0); |
| config.content_outline = Color(128, 0, 0, 0); |
| config.padding = Color(0, 255, 0, 0); |
| config.border = Color(0, 0, 255, 0); |
| config.margin = Color(255, 255, 255, 0); |
| config.event_target = Color(128, 128, 128, 0); |
| config.shape = Color(0, 0, 0, 0); |
| config.shape_margin = Color(128, 128, 128, 0); |
| config.show_info = true; |
| config.show_styles = false; |
| config.show_rulers = true; |
| config.show_extension_lines = true; |
| config.css_grid = Color(128, 128, 128, 0); |
| return config; |
| } |
| |
| } // namespace blink |