DevTools: Include subframe grids in layout inspection
Firefox's grid inspector builds its subgrid tree from two protocol signals: isSubgrid on each GridActor form, and Walker.getParentGridNode() for each subgrid container. Teach the walker to answer the parent grid lookup so Firefox can attach subgrids below their parent grid in the Layout panel.
This commit is contained in:
parent
ad541dca7f
commit
fb96cdd7a1
5 changed files with 99 additions and 9 deletions
|
|
@ -208,6 +208,26 @@ void WalkerActor::handle_message(Message const& message)
|
|||
return;
|
||||
}
|
||||
|
||||
if (message.type == "getParentGridNode"sv) {
|
||||
auto node = get_required_parameter<String>(message, "node"sv);
|
||||
if (!node.has_value())
|
||||
return;
|
||||
|
||||
auto dom_node = WalkerActor::dom_node_for(*this, *node);
|
||||
if (!dom_node.has_value()) {
|
||||
send_unknown_actor_error(message, *node);
|
||||
return;
|
||||
}
|
||||
|
||||
JsonValue parent_grid_node;
|
||||
if (auto parent_grid = parent_grid_node_for_node(dom_node->node); parent_grid.has_value())
|
||||
parent_grid_node = serialize_node(parent_grid.value());
|
||||
|
||||
response.set("node"sv, move(parent_grid_node));
|
||||
send_response(message, move(response));
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.type == "innerHTML"sv) {
|
||||
auto node = get_required_parameter<String>(message, "node"sv);
|
||||
if (!node.has_value())
|
||||
|
|
@ -679,6 +699,24 @@ Optional<JsonObject const&> WalkerActor::next_sibling_for_node(JsonObject const&
|
|||
return sibling_for_node(*parent.value(), node, Direction::Next);
|
||||
}
|
||||
|
||||
Optional<JsonObject const&> WalkerActor::parent_grid_node_for_node(JsonObject const& node) const
|
||||
{
|
||||
auto parent = m_dom_node_to_parent_map.get(&node);
|
||||
while (parent.has_value() && parent.value()) {
|
||||
auto display = parent.value()->get_string("display"sv);
|
||||
if (!display.has_value())
|
||||
return {};
|
||||
if (display->contains("grid"sv))
|
||||
return *parent.value();
|
||||
if (*display != "contents"sv)
|
||||
return {};
|
||||
|
||||
parent = m_dom_node_to_parent_map.get(parent.value());
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Optional<JsonObject const&> WalkerActor::remove_node(JsonObject const& node)
|
||||
{
|
||||
auto maybe_parent = m_dom_node_to_parent_map.get(&node);
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ private:
|
|||
|
||||
Optional<JsonObject const&> previous_sibling_for_node(JsonObject const& node);
|
||||
Optional<JsonObject const&> next_sibling_for_node(JsonObject const& node);
|
||||
Optional<JsonObject const&> parent_grid_node_for_node(JsonObject const& node) const;
|
||||
Optional<JsonObject const&> remove_node(JsonObject const& node);
|
||||
|
||||
void new_dom_node_mutation(WebView::Mutation);
|
||||
|
|
|
|||
|
|
@ -2422,6 +2422,7 @@ void GridFormattingContext::save_grid_layout_data(CSS::GridTrackSizeList&& colum
|
|||
{
|
||||
auto data = make<GridLayoutData>();
|
||||
data->direction = grid_container().computed_values().direction();
|
||||
data->is_subgrid = is_subgridded_axis(GridDimension::Column) || is_subgridded_axis(GridDimension::Row);
|
||||
data->writing_mode = grid_container().computed_values().writing_mode();
|
||||
data->resolved_grid_template_columns = CSS::GridTrackSizeListStyleValue::create(move(columns));
|
||||
data->resolved_grid_template_rows = CSS::GridTrackSizeListStyleValue::create(move(rows));
|
||||
|
|
|
|||
|
|
@ -45,6 +45,8 @@
|
|||
#include <LibWeb/HTML/BroadcastChannel.h>
|
||||
#include <LibWeb/HTML/BrowsingContext.h>
|
||||
#include <LibWeb/HTML/HTMLInputElement.h>
|
||||
#include <LibWeb/HTML/Navigable.h>
|
||||
#include <LibWeb/HTML/NavigableContainer.h>
|
||||
#include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
|
||||
#include <LibWeb/HTML/SelectedFile.h>
|
||||
#include <LibWeb/HTML/Storage.h>
|
||||
|
|
@ -761,6 +763,33 @@ static Optional<JsonObject> grid_layout_for_node(Web::DOM::Node const& node)
|
|||
return layout;
|
||||
}
|
||||
|
||||
static void append_grid_layouts_for_node_and_frame_descendants(Web::DOM::Node& root_node, JsonArray& grid_layouts)
|
||||
{
|
||||
root_node.for_each_in_inclusive_subtree([&](Web::DOM::Node& node) {
|
||||
if (auto grid_layout = grid_layout_for_node(node); grid_layout.has_value())
|
||||
grid_layouts.must_append(grid_layout.release_value());
|
||||
|
||||
auto* navigable_container = as_if<Web::HTML::NavigableContainer>(node);
|
||||
if (!navigable_container)
|
||||
return Web::TraversalDecision::Continue;
|
||||
|
||||
auto content_navigable = navigable_container->content_navigable();
|
||||
if (!content_navigable)
|
||||
return Web::TraversalDecision::Continue;
|
||||
|
||||
auto content_document = content_navigable->active_document();
|
||||
if (!content_document)
|
||||
return Web::TraversalDecision::Continue;
|
||||
|
||||
if (!content_document->origin().is_same_origin_domain(navigable_container->document().origin()))
|
||||
return Web::TraversalDecision::Continue;
|
||||
|
||||
content_document->update_layout(Web::DOM::UpdateLayoutReason::InspectGridLayout);
|
||||
append_grid_layouts_for_node_and_frame_descendants(*content_document, grid_layouts);
|
||||
return Web::TraversalDecision::Continue;
|
||||
});
|
||||
}
|
||||
|
||||
void ConnectionFromClient::inspect_grid_layouts(u64 page_id, Web::UniqueNodeID root_node_id)
|
||||
{
|
||||
auto page = this->page(page_id);
|
||||
|
|
@ -776,11 +805,7 @@ void ConnectionFromClient::inspect_grid_layouts(u64 page_id, Web::UniqueNodeID r
|
|||
root_node->document().update_layout(Web::DOM::UpdateLayoutReason::InspectGridLayout);
|
||||
|
||||
JsonArray grid_layouts;
|
||||
root_node->for_each_in_inclusive_subtree([&](Web::DOM::Node& node) {
|
||||
if (auto grid_layout = grid_layout_for_node(node); grid_layout.has_value())
|
||||
grid_layouts.must_append(grid_layout.release_value());
|
||||
return Web::TraversalDecision::Continue;
|
||||
});
|
||||
append_grid_layouts_for_node_and_frame_descendants(*root_node, grid_layouts);
|
||||
|
||||
async_did_inspect_grid_layouts(page_id, grid_layouts.serialized());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ static JsonObject make_dom_tree()
|
|||
JsonObject target = make_node(4, "element"sv, "DIV"sv);
|
||||
target.set("visible"sv, true);
|
||||
target.set("scrollable"sv, true);
|
||||
target.set("display"sv, "grid"sv);
|
||||
|
||||
JsonObject target_attributes;
|
||||
target_attributes.set("id"sv, "target"sv);
|
||||
|
|
@ -76,11 +77,16 @@ static JsonObject make_dom_tree()
|
|||
before.set("parent-id"sv, 4);
|
||||
before.set("pseudo-element"sv, to_underlying(Web::CSS::PseudoElement::Before));
|
||||
|
||||
JsonObject subgrid = make_node(9, "element"sv, "SECTION"sv);
|
||||
subgrid.set("visible"sv, true);
|
||||
subgrid.set("display"sv, "grid"sv);
|
||||
|
||||
JsonArray target_children;
|
||||
target_children.must_append(move(text));
|
||||
target_children.must_append(move(comment));
|
||||
target_children.must_append(move(whitespace));
|
||||
target_children.must_append(move(before));
|
||||
target_children.must_append(move(subgrid));
|
||||
target.set("children"sv, move(target_children));
|
||||
|
||||
JsonObject sibling = make_node(8, "element"sv, "SPAN"sv);
|
||||
|
|
@ -212,7 +218,7 @@ static JsonObject make_grid_dimension()
|
|||
return dimension;
|
||||
}
|
||||
|
||||
static JsonObject make_grid_layout(Web::UniqueNodeID container_node_id, StringView area_name)
|
||||
static JsonObject make_grid_layout(Web::UniqueNodeID container_node_id, StringView area_name, bool is_subgrid = false)
|
||||
{
|
||||
JsonObject area;
|
||||
area.set("columnEnd"sv, 2);
|
||||
|
|
@ -237,7 +243,7 @@ static JsonObject make_grid_layout(Web::UniqueNodeID container_node_id, StringVi
|
|||
layout.set("containerNodeId"sv, container_node_id.value());
|
||||
layout.set("direction"sv, "ltr"sv);
|
||||
layout.set("gridFragments"sv, move(fragments));
|
||||
layout.set("isSubgrid"sv, false);
|
||||
layout.set("isSubgrid"sv, is_subgrid);
|
||||
layout.set("writingMode"sv, "horizontal-tb"sv);
|
||||
return layout;
|
||||
}
|
||||
|
|
@ -246,6 +252,7 @@ static JsonArray make_grid_layouts()
|
|||
{
|
||||
JsonArray grids;
|
||||
grids.must_append(make_grid_layout(Web::UniqueNodeID { 4 }, "content"sv));
|
||||
grids.must_append(make_grid_layout(Web::UniqueNodeID { 9 }, "subgrid"sv, true));
|
||||
grids.must_append(make_grid_layout(Web::UniqueNodeID { 8 }, "subframe"sv));
|
||||
return grids;
|
||||
}
|
||||
|
|
@ -979,6 +986,7 @@ TEST_CASE(inspector_walker_highlighter_layout_and_editing)
|
|||
EXPECT_EQ(children_response.get_array("nodes"sv)->size(), 1u);
|
||||
|
||||
auto div_actor = query_selector(client, walker_actor, root_node_actor, "div"sv);
|
||||
auto section_actor = query_selector(client, walker_actor, root_node_actor, "section"sv);
|
||||
auto span_actor = query_selector(client, walker_actor, root_node_actor, "span"sv);
|
||||
JsonObject previous_sibling;
|
||||
previous_sibling.set("to"sv, walker_actor);
|
||||
|
|
@ -1082,7 +1090,7 @@ TEST_CASE(inspector_walker_highlighter_layout_and_editing)
|
|||
auto grids = client.request(move(get_grids)).get_array("grids"sv).release_value();
|
||||
EXPECT_EQ(session->delegate.inspect_grid_layouts_call_count, 1u);
|
||||
EXPECT_EQ(session->delegate.last_grid_root_node.value(), 1u);
|
||||
EXPECT_EQ(grids.size(), 2u);
|
||||
EXPECT_EQ(grids.size(), 3u);
|
||||
|
||||
auto const& content_grid = grids.at(0).as_object();
|
||||
EXPECT(!content_grid.has("containerNodeId"sv));
|
||||
|
|
@ -1095,7 +1103,24 @@ TEST_CASE(inspector_walker_highlighter_layout_and_editing)
|
|||
EXPECT_EQ(content_grid_fragment.get_array("areas"sv)->at(0).as_object().get_string("name"sv).value(), "content"sv);
|
||||
EXPECT_EQ(content_grid_fragment.get_object("cols"sv)->get_array("lines"sv)->at(0).as_object().get_integer<i32>("negativeNumber"sv).value(), -2);
|
||||
|
||||
auto const& subframe_grid = grids.at(1).as_object();
|
||||
auto const& subgrid = grids.at(1).as_object();
|
||||
EXPECT_EQ(subgrid.get_string("containerNodeActorID"sv).value(), section_actor);
|
||||
EXPECT_EQ(subgrid.get_bool("isSubgrid"sv).value(), true);
|
||||
EXPECT_EQ(subgrid.get_array("gridFragments"sv)->at(0).as_object().get_array("areas"sv)->at(0).as_object().get_string("name"sv).value(), "subgrid"sv);
|
||||
|
||||
JsonObject get_parent_grid_node;
|
||||
get_parent_grid_node.set("to"sv, walker_actor);
|
||||
get_parent_grid_node.set("type"sv, "getParentGridNode"sv);
|
||||
get_parent_grid_node.set("node"sv, section_actor);
|
||||
EXPECT_EQ(client.request(move(get_parent_grid_node)).get_object("node"sv)->get_string("actor"sv).value(), div_actor);
|
||||
|
||||
JsonObject get_missing_parent_grid_node;
|
||||
get_missing_parent_grid_node.set("to"sv, walker_actor);
|
||||
get_missing_parent_grid_node.set("type"sv, "getParentGridNode"sv);
|
||||
get_missing_parent_grid_node.set("node"sv, div_actor);
|
||||
EXPECT(client.request(move(get_missing_parent_grid_node)).get("node"sv).value().is_null());
|
||||
|
||||
auto const& subframe_grid = grids.at(2).as_object();
|
||||
EXPECT_EQ(subframe_grid.get_string("containerNodeActorID"sv).value(), span_actor);
|
||||
EXPECT_EQ(subframe_grid.get_array("gridFragments"sv)->at(0).as_object().get_array("areas"sv)->at(0).as_object().get_string("name"sv).value(), "subframe"sv);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue