blob: 063cd8c82501af587f593f296877e98544674c47 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// 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/test/values_test_util.h"
#include "base/values.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/core/accessibility/ax_context.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/html/html_element.h"
#include "third_party/blink/renderer/core/testing/dummy_page_holder.h"
#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
#include "third_party/blink/renderer/platform/testing/task_environment.h"
#include "third_party/inspector_protocol/crdtp/json.h"
#include "third_party/inspector_protocol/crdtp/span.h"
namespace blink {
namespace {
using base::test::ParseJson;
using testing::ByRef;
using testing::Eq;
void AssertValueEqualsJSON(const std::unique_ptr<protocol::Value>& actual_value,
const std::string& json_expected) {
std::string json_actual;
auto status_to_json = crdtp::json::ConvertCBORToJSON(
crdtp::SpanFrom(actual_value->Serialize()), &json_actual);
EXPECT_TRUE(status_to_json.ok());
base::Value parsed_json_actual = ParseJson(json_actual);
base::Value parsed_json_expected = ParseJson(json_expected);
EXPECT_THAT(parsed_json_actual, Eq(ByRef(parsed_json_expected)));
}
} // namespace
class InspectorHighlightTest : public testing::Test {
protected:
void SetUp() override;
Document& GetDocument() { return dummy_page_holder_->GetDocument(); }
private:
test::TaskEnvironment task_environment_;
std::unique_ptr<DummyPageHolder> dummy_page_holder_;
};
void InspectorHighlightTest::SetUp() {
dummy_page_holder_ = std::make_unique<DummyPageHolder>(gfx::Size(800, 600));
}
TEST_F(InspectorHighlightTest, BuildSnapContainerInfoNoSnapAreas) {
GetDocument().body()->setInnerHTML(R"HTML(
<div id="target">test</div>
)HTML");
GetDocument().View()->UpdateAllLifecyclePhasesForTest();
Element* target = GetDocument().getElementById(AtomicString("target"));
EXPECT_FALSE(BuildSnapContainerInfo(target));
}
TEST_F(InspectorHighlightTest, BuildSnapContainerInfoSnapAreas) {
GetDocument().body()->setInnerHTML(R"HTML(
<style>
#snap {
background-color: white;
scroll-snap-type: y mandatory;
overflow-x: hidden;
overflow-y: scroll;
width: 150px;
height: 150px;
}
#snap > div {
width: 75px;
height: 75px;
scroll-snap-align: center;
margin: 10px;
padding: 10px;
}
</style>
<div id="snap"><div>A</div><div>B</div></div>
)HTML");
GetDocument().View()->UpdateAllLifecyclePhasesForTest();
Element* container = GetDocument().getElementById(AtomicString("snap"));
auto info = BuildSnapContainerInfo(container);
EXPECT_TRUE(info);
EXPECT_EQ(2u, info->getArray("snapAreas")->size());
protocol::ErrorSupport errors;
std::string expected_container = R"JSON(
{
"snapport":["M",8,8,"L",158,8,"L",158,158,"L",8,158,"Z"],
"paddingBox":["M",8,8,"L",158,8,"L",158,158,"L",8,158,"Z"],
"snapAreas": [
{
"path":["M",18,18,"L",113,18,"L",113,113,"L",18,113,"Z"],
"borderBox":["M",18,18,"L",113,18,"L",113,113,"L",18,113,"Z"],
"alignBlock":"center"
},
{
"path":["M",18,123,"L",113,123,"L",113,218,"L",18,218,"Z"],
"borderBox":["M",18,123,"L",113,123,"L",113,218,"L",18,218,"Z"],
"alignBlock":"center"
}
]
}
)JSON";
AssertValueEqualsJSON(protocol::ValueConversions<protocol::Value>::fromValue(
info.get(), &errors),
expected_container);
}
TEST_F(InspectorHighlightTest, BuildSnapContainerInfoTopLevelSnapAreas) {
GetDocument().body()->setInnerHTML(R"HTML(
<style>
:root {
scroll-snap-type: y mandatory;
overflow-x: hidden;
overflow-y: scroll;
}
div {
width: 100%;
height: 100vh;
scroll-snap-align: start;
}
</style>
<div>A</div><div>B</div>
)HTML");
GetDocument().View()->UpdateAllLifecyclePhasesForTest();
Element* container = GetDocument().documentElement();
auto info = BuildSnapContainerInfo(container);
EXPECT_TRUE(info);
EXPECT_EQ(2u, info->getArray("snapAreas")->size());
protocol::ErrorSupport errors;
std::string expected_container = R"JSON(
{
"paddingBox": [ "M", 0, 0, "L", 800, 0, "L", 800, 600, "L", 0, 600, "Z" ],
"snapAreas": [ {
"alignBlock": "start",
"borderBox": [ "M", 8, 0, "L", 792, 0, "L", 792, 600, "L", 8, 600, "Z" ],
"path": [ "M", 8, 0, "L", 792, 0, "L", 792, 600, "L", 8, 600, "Z" ]
}, {
"alignBlock": "start",
"borderBox": [ "M", 8, 600, "L", 792, 600, "L", 792, 1200, "L", 8, 1200, "Z" ],
"path": [ "M", 8, 600, "L", 792, 600, "L", 792, 1200, "L", 8, 1200, "Z" ]
} ],
"snapport": [ "M", 0, 0, "L", 800, 0, "L", 800, 600, "L", 0, 600, "Z" ]
}
)JSON";
AssertValueEqualsJSON(protocol::ValueConversions<protocol::Value>::fromValue(
info.get(), &errors),
expected_container);
}
TEST_F(InspectorHighlightTest,
BuildContainerQueryContainerInfoWithoutDescendants) {
GetDocument().body()->setInnerHTML(R"HTML(
<style>
#container {
width: 400px;
height: 500px;
container-type: inline-size;
}
</style>
<div id="container"></div>
)HTML");
GetDocument().View()->UpdateAllLifecyclePhasesForTest();
Element* container = GetDocument().getElementById(AtomicString("container"));
auto info = BuildContainerQueryContainerInfo(
container, InspectorContainerQueryContainerHighlightConfig(), 1.0f);
EXPECT_TRUE(info);
protocol::ErrorSupport errors;
std::string expected_container = R"JSON(
{
"containerBorder":["M",8,8,"L",408,8,"L",408,508,"L",8,508,"Z"],
"containerQueryContainerHighlightConfig": {}
}
)JSON";
AssertValueEqualsJSON(protocol::ValueConversions<protocol::Value>::fromValue(
info.get(), &errors),
expected_container);
}
TEST_F(InspectorHighlightTest,
BuildContainerQueryContainerInfoWithDescendants) {
GetDocument().body()->setInnerHTML(R"HTML(
<style>
#container {
width: 400px;
height: 500px;
container-type: inline-size;
}
@container (min-width: 100px) {
.item {
width: 100px;
height: 100px;
}
}
</style>
<div id="container"><div class="item"></div></div>
)HTML");
GetDocument().View()->UpdateAllLifecyclePhasesForTest();
Element* container = GetDocument().getElementById(AtomicString("container"));
LineStyle line_style;
line_style.color = Color(1, 1, 1);
InspectorContainerQueryContainerHighlightConfig highlight_config;
highlight_config.descendant_border = line_style;
auto info =
BuildContainerQueryContainerInfo(container, highlight_config, 1.0f);
EXPECT_TRUE(info);
protocol::ErrorSupport errors;
std::string expected_container = R"JSON(
{
"containerBorder":["M",8,8,"L",408,8,"L",408,508,"L",8,508,"Z"],
"containerQueryContainerHighlightConfig": {
"descendantBorder": {
"color": "rgb(1, 1, 1)",
"pattern": ""
}
},
"queryingDescendants": [ {
"descendantBorder": [ "M", 8, 8, "L", 108, 8, "L", 108, 108, "L", 8, 108, "Z" ]
} ]
}
)JSON";
AssertValueEqualsJSON(protocol::ValueConversions<protocol::Value>::fromValue(
info.get(), &errors),
expected_container);
}
TEST_F(InspectorHighlightTest, BuildIsolatedElementInfo) {
GetDocument().body()->setInnerHTML(R"HTML(
<style>
#element {
width: 400px;
height: 500px;
}
</style>
<div id="element"></div>
)HTML");
GetDocument().View()->UpdateAllLifecyclePhasesForTest();
Element* element = GetDocument().getElementById(AtomicString("element"));
auto info = BuildIsolatedElementInfo(
*element, InspectorIsolationModeHighlightConfig(), 1.0f);
EXPECT_TRUE(info);
protocol::ErrorSupport errors;
std::string expected_isolated_element = R"JSON(
{
"bidirectionResizerBorder": [ "M", 408, 508, "L", 428, 508, "L", 428, 528, "L", 408, 528, "Z" ],
"currentHeight": 500,
"currentWidth": 400,
"currentX": 8,
"currentY": 8,
"heightResizerBorder": [ "M", 8, 508, "L", 408, 508, "L", 408, 528, "L", 8, 528, "Z" ],
"isolationModeHighlightConfig": {
"maskColor": "rgba(0, 0, 0, 0)",
"resizerColor": "rgba(0, 0, 0, 0)",
"resizerHandleColor": "rgba(0, 0, 0, 0)"
},
"widthResizerBorder": [ "M", 408, 8, "L", 428, 8, "L", 428, 508, "L", 408, 508, "Z" ]
}
)JSON";
AssertValueEqualsJSON(protocol::ValueConversions<protocol::Value>::fromValue(
info.get(), &errors),
expected_isolated_element);
}
static std::string GetBackgroundColorFromElementInfo(Element* element) {
EXPECT_TRUE(element);
AXContext ax_context(element->GetDocument(), ui::kAXModeBasic);
element->GetDocument().View()->UpdateAllLifecyclePhasesForTest();
auto info = BuildElementInfo(element);
EXPECT_TRUE(info);
AppendStyleInfo(element, info.get(), {}, {});
protocol::ErrorSupport errors;
auto actual_value = protocol::ValueConversions<protocol::Value>::fromValue(
info.get(), &errors);
EXPECT_TRUE(actual_value);
std::string json_actual;
auto status_to_json = crdtp::json::ConvertCBORToJSON(
crdtp::SpanFrom(actual_value->Serialize()), &json_actual);
EXPECT_TRUE(status_to_json.ok());
base::Value::Dict parsed_json_actual = ParseJson(json_actual).TakeDict();
auto* style = parsed_json_actual.FindDict("style");
EXPECT_TRUE(style);
auto* background_color = style->FindString("background-color-css-text");
if (!background_color) {
background_color = style->FindString("background-color");
}
EXPECT_TRUE(background_color);
return std::move(*background_color);
}
TEST_F(InspectorHighlightTest, BuildElementInfo_Colors) {
GetDocument().body()->setInnerHTML(R"HTML(
<style>
div {
width: 400px;
height: 500px;
}
#lab {
background-color: lab(100% 0 0);
}
#color {
background-color: color(display-p3 50% 50% 50%);
}
#hex {
background-color: #ff00ff;
}
#rgb {
background-color: rgb(128 128 128);
}
#var {
background-color: Var(--lab);
}
:root {
--lab: lab(20% -10 -10);
}
</style>
<div id="lab"></div>
<div id="color"></div>
<div id="hex"></div>
<div id="rgb"></div>
<div id="var"></div>
)HTML");
GetDocument().View()->UpdateAllLifecyclePhasesForTest();
EXPECT_THAT(GetBackgroundColorFromElementInfo(
GetDocument().getElementById(AtomicString("lab"))),
Eq("lab(100 0 0)"));
EXPECT_THAT(GetBackgroundColorFromElementInfo(
GetDocument().getElementById(AtomicString("color"))),
Eq("color(display-p3 0.5 0.5 0.5)"));
EXPECT_THAT(GetBackgroundColorFromElementInfo(
GetDocument().getElementById(AtomicString("hex"))),
Eq("#FF00FFFF"));
EXPECT_THAT(GetBackgroundColorFromElementInfo(
GetDocument().getElementById(AtomicString("rgb"))),
Eq("#808080FF"));
EXPECT_THAT(GetBackgroundColorFromElementInfo(
GetDocument().getElementById(AtomicString("var"))),
Eq("lab(20 -10 -10)"));
}
TEST_F(InspectorHighlightTest, GridLineNames) {
GetDocument().body()->setInnerHTML(R"HTML(
<style>
#grid {
display: grid;
grid-template-columns: [a] 1fr [b] 1fr [c] 1fr;
grid-template-rows: [d] 1fr [e] 1fr [f] 1fr;
}
#subgrid {
display: grid;
grid-column: 1 / 4;
grid-row: 1 / 4;
grid-template-columns: subgrid [a_sub] [b_sub] [c_sub];
grid-template-rows: subgrid [d_sub] [e_sub] [f_sub];
}
</style>
<div id="grid">
<div id="subgrid">
<div class="griditem"></div>
<div class="griditem"></div>
<div class="griditem"></div>
<div class="griditem"></div>
<div class="griditem"></div>
<div class="griditem"></div>
<div class="griditem"></div>
<div class="griditem"></div>
<div class="griditem"></div>
</div>
</div>
)HTML");
GetDocument().View()->UpdateAllLifecyclePhasesForTest();
Node* subgrid = GetDocument().getElementById(AtomicString("subgrid"));
EXPECT_TRUE(subgrid);
auto info =
InspectorGridHighlight(subgrid, InspectorHighlight::DefaultGridConfig());
EXPECT_TRUE(info);
auto CompareLineNames = [](protocol::ListValue* row_or_column_list,
WTF::Vector<WTF::String>& expected_names) -> void {
for (wtf_size_t i = 0; i < row_or_column_list->size(); ++i) {
protocol::DictionaryValue* current_value =
static_cast<protocol::DictionaryValue*>(row_or_column_list->at(i));
WTF::String string_value;
EXPECT_TRUE(current_value->getString("name", &string_value));
EXPECT_EQ(expected_names[i], string_value);
}
};
protocol::ListValue* row_info = info->getArray("rowLineNameOffsets");
EXPECT_EQ(row_info->size(), 6u);
WTF::Vector<WTF::String> expected_row_names = {"d", "e_sub", "e",
"f", "d_sub", "f_sub"};
CompareLineNames(row_info, expected_row_names);
protocol::ListValue* column_info = info->getArray("columnLineNameOffsets");
EXPECT_EQ(column_info->size(), 6u);
WTF::Vector<WTF::String> expected_column_names = {"b", "a_sub", "b_sub",
"c", "a", "c_sub"};
CompareLineNames(column_info, expected_column_names);
}
TEST_F(InspectorHighlightTest, GridAreaNames) {
GetDocument().body()->setInnerHTML(R"HTML(
<style>
#grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: 1fr 1fr 1fr;
grid-template-areas:
"a a a"
"b b b"
"c c c";
}
#subgrid {
display: grid;
grid-column: 1 / 4;
grid-row: 1 / 4;
grid-template-columns: subgrid;
grid-template-rows: subgrid;
grid-template-areas:
"d d d"
"e e e"
"f f f";
}
</style>
<div id="grid">
<div id="subgrid">
<div class="griditem"></div>
<div class="griditem"></div>
<div class="griditem"></div>
<div class="griditem"></div>
<div class="griditem"></div>
<div class="griditem"></div>
<div class="griditem"></div>
<div class="griditem"></div>
<div class="griditem"></div>
</div>
</div>
)HTML");
GetDocument().View()->UpdateAllLifecyclePhasesForTest();
auto CompareAreaNames = [](protocol::DictionaryValue* area_names,
WTF::Vector<WTF::String>& expected_names) -> void {
for (WTF::String& name : expected_names) {
EXPECT_TRUE(area_names->get(name));
}
};
Node* grid = GetDocument().getElementById(AtomicString("grid"));
EXPECT_TRUE(grid);
auto grid_info =
InspectorGridHighlight(grid, InspectorHighlight::DefaultGridConfig());
EXPECT_TRUE(grid_info);
protocol::DictionaryValue* grid_area_names =
grid_info->getObject("areaNames");
EXPECT_EQ(grid_area_names->size(), 3u);
WTF::Vector<WTF::String> expected_grid_area_names = {"a", "b", "c"};
CompareAreaNames(grid_area_names, expected_grid_area_names);
Node* subgrid = GetDocument().getElementById(AtomicString("subgrid"));
EXPECT_TRUE(subgrid);
auto subgrid_info =
InspectorGridHighlight(subgrid, InspectorHighlight::DefaultGridConfig());
EXPECT_TRUE(subgrid_info);
protocol::DictionaryValue* subgrid_area_names =
subgrid_info->getObject("areaNames");
EXPECT_EQ(subgrid_area_names->size(), 6u);
WTF::Vector<WTF::String> expected_subgrid_area_names = {"a", "b", "c",
"d", "e", "f"};
CompareAreaNames(subgrid_area_names, expected_subgrid_area_names);
}
} // namespace blink