Commit 233ceeb4 authored by Patrick Brosset's avatar Patrick Brosset Committed by Commit Bot

DevTools: Generate paths to draw flex lines and items in the flex overlay

The approach taken here is one where we attempt to determine the flex
lines without making changes to the flex implementation. The information
about flex lines is not kept by the implementation, and one way would be
to keep it when devtools need it.
However, it seems feasible to determine this from devtools code
directly, so this is what we're trying here.

The way this works is:
- iterate through items in the main direction
- if an item's margin box starts before the previous item's margin box
  ends, then it belongs to a new flex line
- we build lines in a way that they are always as big as necessary to
  contain all of their items in the cross direction, and we make them as
  big as the container in the main direction.

Front-end CL: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2507790

Bug: 1139949
Change-Id: I6a385124f35d503ebae7f31cf36ebbf42beede20
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2514073Reviewed-by: default avatarChristian Biesinger <cbiesinger@chromium.org>
Reviewed-by: default avatarAndrey Kosyakov <caseq@chromium.org>
Commit-Queue: Patrick Brosset <patrick.brosset@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#826253}
parent bc158344
......@@ -5636,6 +5636,10 @@ experimental domain Overlay
properties
# The style of the container border
optional LineStyle containerBorder
# The style of the separator between lines
optional LineStyle lineSeparator
# The style of the separator between items
optional LineStyle itemSeparator
# Style information for drawing a line.
type LineStyle extends object
......
......@@ -22,10 +22,12 @@
#include "third_party/blink/renderer/core/layout/adjust_for_absolute_zoom.h"
#include "third_party/blink/renderer/core/layout/geometry/physical_offset.h"
#include "third_party/blink/renderer/core/layout/layout_box.h"
#include "third_party/blink/renderer/core/layout/layout_flexible_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/layout_view.h"
#include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.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"
......@@ -351,23 +353,32 @@ std::unique_ptr<protocol::DictionaryValue> BuildTextNodeInfo(Text* text_node) {
return text_info;
}
void AppendLineStyleConfig(
const std::unique_ptr<LineStyle>& line_style,
std::unique_ptr<protocol::DictionaryValue>& parent_config,
String line_name) {
if (line_style && line_style->color != Color::kTransparent) {
std::unique_ptr<protocol::DictionaryValue> config =
protocol::DictionaryValue::create();
config->setString("color", line_style->color.Serialized());
config->setString("pattern", line_style->pattern);
parent_config->setValue(line_name, std::move(config));
}
}
std::unique_ptr<protocol::DictionaryValue>
BuildFlexContainerHighlightConfigInfo(
const InspectorFlexContainerHighlightConfig& flex_config) {
std::unique_ptr<protocol::DictionaryValue> flex_config_info =
protocol::DictionaryValue::create();
// Container border style
if (flex_config.container_border &&
flex_config.container_border->color != Color::kTransparent) {
std::unique_ptr<protocol::DictionaryValue> border_config =
protocol::DictionaryValue::create();
border_config->setString("color",
flex_config.container_border->color.Serialized());
border_config->setString("pattern", flex_config.container_border->pattern);
flex_config_info->setValue("containerBorder", std::move(border_config));
}
AppendLineStyleConfig(flex_config.container_border, flex_config_info,
"containerBorder");
AppendLineStyleConfig(flex_config.line_separator, flex_config_info,
"lineSeparator");
AppendLineStyleConfig(flex_config.item_separator, flex_config_info,
"itemSeparator");
return flex_config_info;
}
......@@ -814,6 +825,52 @@ Vector<String> GetAuthoredGridTrackSizes(const CSSValue* value,
return result;
}
bool IsHorizontalFlex(LayoutObject* layout_flex) {
return layout_flex->StyleRef().IsHorizontalWritingMode() !=
layout_flex->StyleRef().ResolvedIsColumnFlexDirection();
}
Vector<Vector<PhysicalRect>> GetFlexLinesAndItems(LayoutBox* layout_box,
bool is_horizontal) {
Vector<Vector<PhysicalRect>> flex_lines;
// Flex containers can't get fragmented yet, but this may change in the
// future.
for (const auto& fragment : layout_box->PhysicalFragments()) {
LayoutUnit progression;
for (const auto& child : fragment.Children()) {
const NGPhysicalFragment* child_fragment = child.get();
if (!child_fragment || child_fragment->IsOutOfFlowPositioned())
continue;
PhysicalSize fragment_size = child_fragment->Size();
PhysicalOffset fragment_offset = child.Offset();
const LayoutObject* object = child_fragment->GetLayoutObject();
const LayoutBox* box = ToLayoutBox(object);
PhysicalRect item_rect =
PhysicalRect(fragment_offset.left - box->MarginLeft(),
fragment_offset.top - box->MarginTop(),
fragment_size.width + box->MarginWidth(),
fragment_size.height + box->MarginHeight());
LayoutUnit item_start = is_horizontal ? item_rect.X() : item_rect.Y();
LayoutUnit item_end = is_horizontal ? item_rect.X() + item_rect.Width()
: item_rect.Y() + item_rect.Height();
if (flex_lines.IsEmpty() || item_start < progression) {
flex_lines.emplace_back();
}
flex_lines.back().push_back(item_rect);
progression = item_end;
}
}
return flex_lines;
}
std::unique_ptr<protocol::DictionaryValue> BuildFlexInfo(
Node* node,
const InspectorFlexContainerHighlightConfig&
......@@ -821,20 +878,45 @@ std::unique_ptr<protocol::DictionaryValue> BuildFlexInfo(
float scale) {
LocalFrameView* containing_view = node->GetDocument().View();
LayoutObject* layout_object = node->GetLayoutObject();
LayoutBox* layout_box = ToLayoutBox(layout_object);
DCHECK(layout_object);
bool is_horizontal = IsHorizontalFlex(layout_object);
std::unique_ptr<protocol::DictionaryValue> flex_info =
protocol::DictionaryValue::create();
// For now, we're only drawing a path around the container's content area.
PathBuilder container_border_builder;
LayoutBox* layout_box = ToLayoutBox(layout_object);
// Create the path for the flex container
PathBuilder container_builder;
PhysicalRect content_box = layout_box->PhysicalContentBoxRect();
FloatQuad content_quad = layout_object->LocalRectToAbsoluteQuad(content_box);
FrameQuadToViewport(containing_view, content_quad);
container_border_builder.AppendPath(QuadToPath(content_quad), scale);
flex_info->setValue("containerBorder", container_border_builder.Release());
container_builder.AppendPath(QuadToPath(content_quad), scale);
// Gather all flex items, sorted by flex line.
Vector<Vector<PhysicalRect>> flex_lines =
GetFlexLinesAndItems(layout_box, is_horizontal);
// Send the offset information for each item to the frontend.
std::unique_ptr<protocol::ListValue> lines_info =
protocol::ListValue::create();
for (auto line : flex_lines) {
std::unique_ptr<protocol::ListValue> items_info =
protocol::ListValue::create();
for (auto item_rect : line) {
FloatQuad item_margin_quad =
layout_object->LocalRectToAbsoluteQuad(item_rect);
FrameQuadToViewport(containing_view, item_margin_quad);
PathBuilder item_builder;
item_builder.AppendPath(QuadToPath(item_margin_quad), scale);
items_info->pushValue(item_builder.Release());
}
lines_info->pushValue(std::move(items_info));
}
flex_info->setValue("containerBorder", container_builder.Release());
flex_info->setArray("lines", std::move(lines_info));
flex_info->setBoolean("isHorizontalFlow", is_horizontal);
flex_info->setValue(
"flexContainerHighlightConfig",
BuildFlexContainerHighlightConfigInfo(flex_container_highlight_config));
......@@ -1737,6 +1819,10 @@ InspectorHighlight::DefaultFlexContainerConfig() {
InspectorFlexContainerHighlightConfig config;
config.container_border =
std::make_unique<LineStyle>(InspectorHighlight::DefaultLineStyle());
config.line_separator =
std::make_unique<LineStyle>(InspectorHighlight::DefaultLineStyle());
config.item_separator =
std::make_unique<LineStyle>(InspectorHighlight::DefaultLineStyle());
return config;
}
......
......@@ -73,6 +73,8 @@ struct CORE_EXPORT InspectorFlexContainerHighlightConfig {
InspectorFlexContainerHighlightConfig();
std::unique_ptr<LineStyle> container_border;
std::unique_ptr<LineStyle> line_separator;
std::unique_ptr<LineStyle> item_separator;
};
struct CORE_EXPORT InspectorHighlightConfig {
......
......@@ -1490,6 +1490,10 @@ InspectorOverlayAgent::ToFlexContainerHighlightConfig(
std::make_unique<InspectorFlexContainerHighlightConfig>();
highlight_config->container_border =
InspectorOverlayAgent::ToLineStyle(config->getContainerBorder(nullptr));
highlight_config->line_separator =
InspectorOverlayAgent::ToLineStyle(config->getLineSeparator(nullptr));
highlight_config->item_separator =
InspectorOverlayAgent::ToLineStyle(config->getItemSeparator(nullptr));
return highlight_config;
}
......
......@@ -112,10 +112,68 @@ flex-container{
108,
"Z"
],
"lines": [
[
[
"M",
8,
8,
"L",
174.671875,
8,
"L",
174.671875,
108,
"L",
8,
108,
"Z"
],
[
"M",
174.671875,
8,
"L",
341.34375,
8,
"L",
341.34375,
108,
"L",
174.671875,
108,
"Z"
],
[
"M",
341.34375,
8,
"L",
508.015625,
8,
"L",
508.015625,
108,
"L",
341.34375,
108,
"Z"
]
]
],
"isHorizontalFlow": true,
"flexContainerHighlightConfig": {
"containerBorder": {
"color": "rgba(255, 0, 0, 0)",
"pattern": "solid"
},
"lineSeparator": {
"color": "rgba(255, 0, 0, 0)",
"pattern": "solid"
},
"itemSeparator": {
"color": "rgba(255, 0, 0, 0)",
"pattern": "solid"
}
}
}
......
This test verifies the position and size of the highlighted lines and items in a multiline flex container.
flex-container{
"paths": [
{
"path": [
"M",
100,
100,
"L",
500,
100,
"L",
500,
500,
"L",
100,
500,
"Z"
],
"fillColor": "rgba(255, 0, 0, 0)",
"outlineColor": "rgba(128, 0, 0, 0)",
"name": "content"
},
{
"path": [
"M",
100,
100,
"L",
500,
100,
"L",
500,
500,
"L",
100,
500,
"Z"
],
"fillColor": "rgba(0, 255, 0, 0)",
"name": "padding"
},
{
"path": [
"M",
100,
100,
"L",
500,
100,
"L",
500,
500,
"L",
100,
500,
"Z"
],
"fillColor": "rgba(0, 0, 255, 0)",
"name": "border"
},
{
"path": [
"M",
100,
100,
"L",
500,
100,
"L",
500,
500,
"L",
100,
500,
"Z"
],
"fillColor": "rgba(255, 255, 255, 0)",
"name": "margin"
}
],
"showRulers": true,
"showExtensionLines": true,
"showAccessibilityInfo": true,
"colorFormat": "hex",
"elementInfo": {
"tagName": "div",
"idValue": "flex-container",
"nodeWidth": "400",
"nodeHeight": "400",
"isKeyboardFocusable": false,
"accessibleName": "",
"accessibleRole": "generic",
"layoutObjectName": "LayoutNGFlexibleBox",
"showAccessibilityInfo": true
},
"flexInfo": [
{
"containerBorder": [
"M",
100,
100,
"L",
500,
100,
"L",
500,
500,
"L",
100,
500,
"Z"
],
"lines": [
[
[
"M",
100,
100,
"L",
200,
100,
"L",
200,
200,
"L",
100,
200,
"Z"
],
[
"M",
250,
100,
"L",
350,
100,
"L",
350,
200,
"L",
250,
200,
"Z"
],
[
"M",
400,
100,
"L",
500,
100,
"L",
500,
200,
"L",
400,
200,
"Z"
]
],
[
[
"M",
100,
400,
"L",
200,
400,
"L",
200,
500,
"L",
100,
500,
"Z"
],
[
"M",
250,
400,
"L",
350,
400,
"L",
350,
500,
"L",
250,
500,
"Z"
],
[
"M",
400,
400,
"L",
500,
400,
"L",
500,
500,
"L",
400,
500,
"Z"
]
]
],
"isHorizontalFlow": true,
"flexContainerHighlightConfig": {
"containerBorder": {
"color": "rgba(255, 0, 0, 0)",
"pattern": "solid"
},
"lineSeparator": {
"color": "rgba(255, 0, 0, 0)",
"pattern": "solid"
},
"itemSeparator": {
"color": "rgba(255, 0, 0, 0)",
"pattern": "solid"
}
}
}
]
}
// Copyright 2020 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.
(async function() {
TestRunner.addResult(`This test verifies the position and size of the highlighted lines and items in a multiline flex container.\n`);
await TestRunner.loadModule('elements_test_runner');
await TestRunner.showPanel('elements');
await TestRunner.loadHTML(`
<style>
#flex-container {
position: absolute;
top: 100px;
left: 100px;
width: 400px;
height: 400px;
display: flex;
flex-wrap: wrap;
gap: 10px;
align-content: space-between;
justify-content: space-between;
}
.item {
width: 100px;
height: 100px;
}
</style>
<div id="flex-container">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
`);
function dumFlexHighlight(id) {
return new Promise(resolve => ElementsTestRunner.dumpInspectorHighlightJSON(id, resolve));
}
await dumFlexHighlight('flex-container');
TestRunner.completeTest();
})();
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment