diff --git a/Userland/Applications/Browser/InspectorWidget.cpp b/Userland/Applications/Browser/InspectorWidget.cpp index e46ca66064..756418a848 100644 --- a/Userland/Applications/Browser/InspectorWidget.cpp +++ b/Userland/Applications/Browser/InspectorWidget.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -67,6 +68,7 @@ void InspectorWidget::set_selection(GUI::ModelIndex const index) auto inspected_node_properties = maybe_inspected_node_properties.release_value(); load_style_json(inspected_node_properties.computed_style_json, inspected_node_properties.resolved_style_json, inspected_node_properties.custom_properties_json); update_node_box_model(inspected_node_properties.node_box_sizing_json); + update_aria_properties_state_model(inspected_node_properties.aria_properties_state_json); } else { clear_style_json(); clear_node_box_model(); @@ -117,6 +119,10 @@ InspectorWidget::InspectorWidget() m_element_size_view = box_model_widget.add(); m_element_size_view->set_should_hide_unnecessary_scrollbars(true); + auto& aria_properties_state_widget = bottom_tab_widget.add_tab("ARIA"_string.release_value_but_fixme_should_propagate_errors()); + aria_properties_state_widget.set_layout(4); + m_aria_properties_state_view = aria_properties_state_widget.add(); + m_dom_tree_view->set_focus(true); } @@ -146,7 +152,7 @@ void InspectorWidget::clear_dom_json() m_dom_loaded = false; } -void InspectorWidget::set_dom_node_properties_json(Selection selection, StringView computed_values_json, StringView resolved_values_json, StringView custom_properties_json, StringView node_box_sizing_json) +void InspectorWidget::set_dom_node_properties_json(Selection selection, StringView computed_values_json, StringView resolved_values_json, StringView custom_properties_json, StringView node_box_sizing_json, StringView aria_properties_state_json) { if (selection != m_selection) { dbgln("Got data for the wrong node id! Wanted ({}), got ({})", m_selection.to_string(), selection.to_string()); @@ -155,6 +161,7 @@ void InspectorWidget::set_dom_node_properties_json(Selection selection, StringVi load_style_json(computed_values_json, resolved_values_json, custom_properties_json); update_node_box_model(node_box_sizing_json); + update_aria_properties_state_model(aria_properties_state_json); } void InspectorWidget::load_style_json(StringView computed_values_json, StringView resolved_values_json, StringView custom_properties_json) @@ -196,6 +203,12 @@ void InspectorWidget::update_node_box_model(StringView node_box_sizing_json) m_element_size_view->set_box_model(m_node_box_sizing); } +void InspectorWidget::update_aria_properties_state_model(StringView aria_properties_state_json) +{ + m_aria_properties_state_view->set_model(WebView::AriaPropertiesStateModel::create(aria_properties_state_json).release_value_but_fixme_should_propagate_errors()); + m_aria_properties_state_view->set_searchable(true); +} + void InspectorWidget::clear_node_box_model() { m_node_box_sizing = Web::Layout::BoxModelMetrics {}; @@ -209,6 +222,7 @@ void InspectorWidget::clear_style_json() m_computed_style_table_view->set_model(nullptr); m_resolved_style_table_view->set_model(nullptr); m_custom_properties_table_view->set_model(nullptr); + m_aria_properties_state_view->set_model(nullptr); m_element_size_view->set_box_model({}); m_element_size_view->set_node_content_width(0); diff --git a/Userland/Applications/Browser/InspectorWidget.h b/Userland/Applications/Browser/InspectorWidget.h index 9405f7a855..7a4efc8da2 100644 --- a/Userland/Applications/Browser/InspectorWidget.h +++ b/Userland/Applications/Browser/InspectorWidget.h @@ -44,7 +44,7 @@ public: void set_web_view(NonnullRefPtr web_view) { m_web_view = web_view; } void set_dom_json(StringView); void clear_dom_json(); - void set_dom_node_properties_json(Selection, StringView computed_values_json, StringView resolved_values_json, StringView custom_properties_json, StringView node_box_sizing_json); + void set_dom_node_properties_json(Selection, StringView computed_values_json, StringView resolved_values_json, StringView custom_properties_json, StringView node_box_sizing_json, StringView aria_properties_state_json); void set_accessibility_json(StringView); void set_selection(Selection); @@ -56,6 +56,7 @@ private: void set_selection(GUI::ModelIndex); void load_style_json(StringView computed_values_json, StringView resolved_values_json, StringView custom_properties_json); void update_node_box_model(StringView node_box_sizing_json); + void update_aria_properties_state_model(StringView aria_properties_state_json); void clear_style_json(); void clear_node_box_model(); @@ -66,6 +67,7 @@ private: RefPtr m_computed_style_table_view; RefPtr m_resolved_style_table_view; RefPtr m_custom_properties_table_view; + RefPtr m_aria_properties_state_view; RefPtr m_element_size_view; Web::Layout::BoxModelMetrics m_node_box_sizing; diff --git a/Userland/Applications/Browser/Tab.cpp b/Userland/Applications/Browser/Tab.cpp index e4b2a98be8..fced89f5ad 100644 --- a/Userland/Applications/Browser/Tab.cpp +++ b/Userland/Applications/Browser/Tab.cpp @@ -519,8 +519,8 @@ Tab::Tab(BrowserWindow& window) m_dom_inspector_widget->set_dom_json(dom_tree); }; - view().on_get_dom_node_properties = [this](auto node_id, auto& specified, auto& computed, auto& custom_properties, auto& node_box_sizing) { - m_dom_inspector_widget->set_dom_node_properties_json({ node_id }, specified, computed, custom_properties, node_box_sizing); + view().on_get_dom_node_properties = [this](auto node_id, auto& specified, auto& computed, auto& custom_properties, auto& node_box_sizing, auto& aria_properties_state) { + m_dom_inspector_widget->set_dom_node_properties_json({ node_id }, specified, computed, custom_properties, node_box_sizing, aria_properties_state); }; view().on_get_accessibility_tree = [this](auto& accessibility_tree) { @@ -821,7 +821,7 @@ void Tab::show_inspector_window(Browser::Tab::InspectorTarget inspector_target) if (!m_dom_inspector_widget) { auto window = GUI::Window::construct(&this->window()); window->set_window_mode(GUI::WindowMode::Modeless); - window->resize(300, 500); + window->resize(325, 500); window->set_title("Inspector"); window->set_icon(g_icon_bag.inspector_object); window->on_close = [&]() { diff --git a/Userland/Libraries/LibWebView/AriaPropertiesStateModel.cpp b/Userland/Libraries/LibWebView/AriaPropertiesStateModel.cpp new file mode 100644 index 0000000000..cbe86c3f7b --- /dev/null +++ b/Userland/Libraries/LibWebView/AriaPropertiesStateModel.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2023, Jonah Shafran + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "AriaPropertiesStateModel.h" + +namespace WebView { + +AriaPropertiesStateModel::AriaPropertiesStateModel(JsonObject properties_state) + : m_properties_state(move(properties_state)) +{ + m_properties_state.for_each_member([&](auto property_name, JsonValue const& values) { + Value value; + value.name = property_name; + value.value = ""; + m_values.append(value); + values.as_object().for_each_member([&](auto property_name, auto& property_value) { + Value value; + value.name = property_name; + value.value = property_value.to_deprecated_string(); + m_values.append(value); + }); + }); +} + +AriaPropertiesStateModel::~AriaPropertiesStateModel() = default; + +int AriaPropertiesStateModel::row_count(GUI::ModelIndex const&) const +{ + return m_values.size(); +} + +ErrorOr AriaPropertiesStateModel::column_name(int column_index) const +{ + switch (column_index) { + case Column::PropertyName: + return "Name"_short_string; + case Column::PropertyValue: + return "Value"_short_string; + default: + return Error::from_string_view("Unexpected column index"sv); + } +} + +GUI::Variant AriaPropertiesStateModel::data(GUI::ModelIndex const& index, GUI::ModelRole role) const +{ + auto& value = m_values[index.row()]; + if (role == GUI::ModelRole::Display) { + if (index.column() == Column::PropertyName) + return value.name; + if (index.column() == Column::PropertyValue) + return value.value; + } + + return {}; +} + +Vector AriaPropertiesStateModel::matches(AK::StringView searching, unsigned int flags, GUI::ModelIndex const& parent) +{ + if (m_values.is_empty()) + return {}; + Vector found_indices; + for (auto it = m_values.begin(); !it.is_end(); ++it) { + GUI::ModelIndex index = this->index(it.index(), Column::PropertyName, parent); + if (!string_matches(data(index, GUI::ModelRole::Display).as_string(), searching, flags)) + continue; + + found_indices.append(index); + if (flags & FirstMatchOnly) + break; + } + return found_indices; +} + +} diff --git a/Userland/Libraries/LibWebView/AriaPropertiesStateModel.h b/Userland/Libraries/LibWebView/AriaPropertiesStateModel.h new file mode 100644 index 0000000000..d964bbbaad --- /dev/null +++ b/Userland/Libraries/LibWebView/AriaPropertiesStateModel.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2023, Jonah Shafran + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace WebView { + +class AriaPropertiesStateModel final : public GUI::Model { +public: + enum Column { + PropertyName, + PropertyValue, + __Count + }; + + static ErrorOr> create(StringView properties_state) + { + auto json_or_error = TRY(JsonValue::from_string(properties_state)); + return adopt_nonnull_ref_or_enomem(new AriaPropertiesStateModel(json_or_error.as_object())); + } + + virtual ~AriaPropertiesStateModel() override; + + virtual int row_count(GUI::ModelIndex const& = GUI::ModelIndex()) const override; + virtual int column_count(GUI::ModelIndex const& = GUI::ModelIndex()) const override { return Column::__Count; } + virtual ErrorOr column_name(int) const override; + virtual GUI::Variant data(GUI::ModelIndex const&, GUI::ModelRole) const override; + virtual bool is_searchable() const override { return true; } + virtual Vector matches(StringView, unsigned flags, GUI::ModelIndex const&) override; + +private: + explicit AriaPropertiesStateModel(JsonObject); + + JsonObject m_properties_state; + + struct Value { + DeprecatedString name; + DeprecatedString value; + }; + Vector m_values; +}; + +} diff --git a/Userland/Libraries/LibWebView/CMakeLists.txt b/Userland/Libraries/LibWebView/CMakeLists.txt index a5801519e3..63dec1c93c 100644 --- a/Userland/Libraries/LibWebView/CMakeLists.txt +++ b/Userland/Libraries/LibWebView/CMakeLists.txt @@ -1,5 +1,6 @@ set(SOURCES AccessibilityTreeModel.cpp + AriaPropertiesStateModel.cpp DOMTreeModel.cpp OutOfProcessWebView.cpp RequestServerAdapter.cpp diff --git a/Userland/Libraries/LibWebView/ViewImplementation.cpp b/Userland/Libraries/LibWebView/ViewImplementation.cpp index 10901a10fc..1510f16a15 100644 --- a/Userland/Libraries/LibWebView/ViewImplementation.cpp +++ b/Userland/Libraries/LibWebView/ViewImplementation.cpp @@ -118,6 +118,7 @@ ErrorOr ViewImplementation::inspect_dom_n .resolved_style_json = TRY(String::from_deprecated_string(response.take_resolved_style())), .custom_properties_json = TRY(String::from_deprecated_string(response.take_custom_properties())), .node_box_sizing_json = TRY(String::from_deprecated_string(response.take_node_box_sizing())), + .aria_properties_state_json = TRY(String::from_deprecated_string(response.take_aria_properties_state())), }; } diff --git a/Userland/Libraries/LibWebView/ViewImplementation.h b/Userland/Libraries/LibWebView/ViewImplementation.h index c326007931..71654a1b0d 100644 --- a/Userland/Libraries/LibWebView/ViewImplementation.h +++ b/Userland/Libraries/LibWebView/ViewImplementation.h @@ -44,6 +44,7 @@ public: String resolved_style_json; String custom_properties_json; String node_box_sizing_json; + String aria_properties_state_json; }; void set_url(Badge, AK::URL url) { m_url = move(url); } @@ -110,7 +111,7 @@ public: Function on_favicon_change; Function on_get_source; Function on_get_dom_tree; - Function on_get_dom_node_properties; + Function on_get_dom_node_properties; Function on_get_accessibility_tree; Function on_js_console_new_message; Function const& message_types, Vector const& messages)> on_get_js_console_messages; diff --git a/Userland/Libraries/LibWebView/WebContentClient.cpp b/Userland/Libraries/LibWebView/WebContentClient.cpp index 2cc9bfd575..45315b79b9 100644 --- a/Userland/Libraries/LibWebView/WebContentClient.cpp +++ b/Userland/Libraries/LibWebView/WebContentClient.cpp @@ -194,10 +194,10 @@ void WebContentClient::did_get_dom_tree(DeprecatedString const& dom_tree) m_view.on_get_dom_tree(dom_tree); } -void WebContentClient::did_get_dom_node_properties(i32 node_id, DeprecatedString const& computed_style, DeprecatedString const& resolved_style, DeprecatedString const& custom_properties, DeprecatedString const& node_box_sizing) +void WebContentClient::did_get_dom_node_properties(i32 node_id, DeprecatedString const& computed_style, DeprecatedString const& resolved_style, DeprecatedString const& custom_properties, DeprecatedString const& node_box_sizing, DeprecatedString const& aria_properties_state) { if (m_view.on_get_dom_node_properties) - m_view.on_get_dom_node_properties(node_id, computed_style, resolved_style, custom_properties, node_box_sizing); + m_view.on_get_dom_node_properties(node_id, computed_style, resolved_style, custom_properties, node_box_sizing, aria_properties_state); } void WebContentClient::did_get_accessibility_tree(DeprecatedString const& accessibility_tree) diff --git a/Userland/Libraries/LibWebView/WebContentClient.h b/Userland/Libraries/LibWebView/WebContentClient.h index b10aca679d..7019467fc3 100644 --- a/Userland/Libraries/LibWebView/WebContentClient.h +++ b/Userland/Libraries/LibWebView/WebContentClient.h @@ -55,7 +55,7 @@ private: virtual void did_request_media_context_menu(Gfx::IntPoint, DeprecatedString const&, unsigned, Web::Page::MediaContextMenu const&) override; virtual void did_get_source(AK::URL const&, DeprecatedString const&) override; virtual void did_get_dom_tree(DeprecatedString const&) override; - virtual void did_get_dom_node_properties(i32 node_id, DeprecatedString const& computed_style, DeprecatedString const& resolved_style, DeprecatedString const& custom_properties, DeprecatedString const& node_box_sizing) override; + virtual void did_get_dom_node_properties(i32 node_id, DeprecatedString const& computed_style, DeprecatedString const& resolved_style, DeprecatedString const& custom_properties, DeprecatedString const& node_box_sizing, DeprecatedString const& aria_properties_stae) override; virtual void did_get_accessibility_tree(DeprecatedString const&) override; virtual void did_output_js_console_message(i32 message_index) override; virtual void did_get_js_console_messages(i32 start_index, Vector const& message_types, Vector const& messages) override; diff --git a/Userland/Services/WebContent/ConnectionFromClient.cpp b/Userland/Services/WebContent/ConnectionFromClient.cpp index 1f6fd1dba1..fc2fee1c37 100644 --- a/Userland/Services/WebContent/ConnectionFromClient.cpp +++ b/Userland/Services/WebContent/ConnectionFromClient.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -452,7 +453,7 @@ Messages::WebContentServer::InspectDomNodeResponse ConnectionFromClient::inspect Web::DOM::Node* node = Web::DOM::Node::from_id(node_id); // Note: Nodes without layout (aka non-visible nodes, don't have style computed) if (!node || !node->layout_node()) { - return { false, "", "", "", "" }; + return { false, "", "", "", "", "" }; } node->document().set_inspected_node(node, pseudo_element); @@ -460,7 +461,7 @@ Messages::WebContentServer::InspectDomNodeResponse ConnectionFromClient::inspect if (node->is_element()) { auto& element = verify_cast(*node); if (!element.computed_css_values()) - return { false, "", "", "", "" }; + return { false, "", "", "", "", "" }; auto serialize_json = [](Web::CSS::StyleProperties const& properties) -> DeprecatedString { StringBuilder builder; @@ -527,10 +528,25 @@ Messages::WebContentServer::InspectDomNodeResponse ConnectionFromClient::inspect return builder.to_deprecated_string(); }; + auto serialize_aria_properties_state_json = [](Web::DOM::Element const& element) -> DeprecatedString { + auto role_name = element.role_or_default(); + if (!role_name.has_value()) { + return ""; + } + auto aria_data = MUST(Web::ARIA::AriaData::build_data(element)); + auto role = MUST(Web::ARIA::RoleType::build_role_object(role_name.value(), element.is_focusable(), *aria_data)); + + StringBuilder builder; + auto serializer = MUST(JsonObjectSerializer<>::try_create(builder)); + MUST(role->serialize_as_json(serializer)); + MUST(serializer.finish()); + return builder.to_deprecated_string(); + }; + if (pseudo_element.has_value()) { auto pseudo_element_node = element.get_pseudo_element_node(pseudo_element.value()); if (!pseudo_element_node) - return { false, "", "", "", "" }; + return { false, "", "", "", "", "" }; // FIXME: Pseudo-elements only exist as Layout::Nodes, which don't have style information // in a format we can use. So, we run the StyleComputer again to get the specified @@ -540,17 +556,18 @@ Messages::WebContentServer::InspectDomNodeResponse ConnectionFromClient::inspect DeprecatedString resolved_values = "{}"; DeprecatedString custom_properties_json = serialize_custom_properties_json(element, pseudo_element); DeprecatedString node_box_sizing_json = serialize_node_box_sizing_json(pseudo_element_node.ptr()); - return { true, computed_values, resolved_values, custom_properties_json, node_box_sizing_json }; + return { true, computed_values, resolved_values, custom_properties_json, node_box_sizing_json, "" }; } DeprecatedString computed_values = serialize_json(*element.computed_css_values()); DeprecatedString resolved_values_json = serialize_json(element.resolved_css_values()); DeprecatedString custom_properties_json = serialize_custom_properties_json(element, {}); DeprecatedString node_box_sizing_json = serialize_node_box_sizing_json(element.layout_node()); - return { true, computed_values, resolved_values_json, custom_properties_json, node_box_sizing_json }; + DeprecatedString aria_properties_state_json = serialize_aria_properties_state_json(element); + return { true, computed_values, resolved_values_json, custom_properties_json, node_box_sizing_json, aria_properties_state_json }; } - return { false, "", "", "", "" }; + return { false, "", "", "", "", "" }; } Messages::WebContentServer::GetHoveredNodeIdResponse ConnectionFromClient::get_hovered_node_id() diff --git a/Userland/Services/WebContent/WebContentClient.ipc b/Userland/Services/WebContent/WebContentClient.ipc index 0806227a1f..77bec13927 100644 --- a/Userland/Services/WebContent/WebContentClient.ipc +++ b/Userland/Services/WebContent/WebContentClient.ipc @@ -40,7 +40,7 @@ endpoint WebContentClient did_request_dismiss_dialog() =| did_get_source(URL url, DeprecatedString source) =| did_get_dom_tree(DeprecatedString dom_tree) =| - did_get_dom_node_properties(i32 node_id, DeprecatedString computed_style, DeprecatedString resolved_style, DeprecatedString custom_properties, DeprecatedString node_box_sizing_json) =| + did_get_dom_node_properties(i32 node_id, DeprecatedString computed_style, DeprecatedString resolved_style, DeprecatedString custom_properties, DeprecatedString node_box_sizing_json, DeprecatedString aria_properties_state) =| did_get_accessibility_tree(DeprecatedString accessibility_tree) =| did_change_favicon(Gfx::ShareableBitmap favicon) =| did_request_all_cookies(URL url) => (Vector cookies) diff --git a/Userland/Services/WebContent/WebContentServer.ipc b/Userland/Services/WebContent/WebContentServer.ipc index 3659368287..072b887a29 100644 --- a/Userland/Services/WebContent/WebContentServer.ipc +++ b/Userland/Services/WebContent/WebContentServer.ipc @@ -39,7 +39,7 @@ endpoint WebContentServer debug_request(DeprecatedString request, DeprecatedString argument) =| get_source() =| inspect_dom_tree() =| - inspect_dom_node(i32 node_id, Optional pseudo_element) => (bool has_style, DeprecatedString computed_style, DeprecatedString resolved_style, DeprecatedString custom_properties, DeprecatedString node_box_sizing) + inspect_dom_node(i32 node_id, Optional pseudo_element) => (bool has_style, DeprecatedString computed_style, DeprecatedString resolved_style, DeprecatedString custom_properties, DeprecatedString node_box_sizing, DeprecatedString aria_properties_state) inspect_accessibility_tree() =| get_hovered_node_id() => (i32 node_id) js_console_input(DeprecatedString js_source) =|