diff options
author | Jonah <jonahshafran@gmail.com> | 2022-12-28 17:26:23 -0600 |
---|---|---|
committer | Sam Atkins <atkinssj@gmail.com> | 2023-01-07 10:51:53 +0000 |
commit | e361025cfb4857c13fc4082f349ad169ba4a9231 (patch) | |
tree | 1691b6f58e042455a8b200b5b1bbcbf5f125b6e8 /Userland | |
parent | 367b1634fd4a0c3764d1f618b8045a5d15096a07 (diff) | |
download | serenity-e361025cfb4857c13fc4082f349ad169ba4a9231.zip |
Browser: Add Accessibility Tab to Inspector Widget
This tab allows you to view the accessibility tree like you do
the DOM tree. The implementation limited to the role currently,
once we add the name and description calculation algorithm, those
will be displayed here as well. Selections are also not currently
supported.
Diffstat (limited to 'Userland')
-rw-r--r-- | Userland/Applications/Browser/InspectorWidget.cpp | 13 | ||||
-rw-r--r-- | Userland/Applications/Browser/InspectorWidget.h | 2 | ||||
-rw-r--r-- | Userland/Applications/Browser/Tab.cpp | 10 | ||||
-rw-r--r-- | Userland/Libraries/LibWebView/AccessibilityTreeModel.cpp | 117 | ||||
-rw-r--r-- | Userland/Libraries/LibWebView/AccessibilityTreeModel.h | 61 | ||||
-rw-r--r-- | Userland/Libraries/LibWebView/CMakeLists.txt | 1 |
6 files changed, 203 insertions, 1 deletions
diff --git a/Userland/Applications/Browser/InspectorWidget.cpp b/Userland/Applications/Browser/InspectorWidget.cpp index 773eec9b06..b578804b3a 100644 --- a/Userland/Applications/Browser/InspectorWidget.cpp +++ b/Userland/Applications/Browser/InspectorWidget.cpp @@ -15,6 +15,7 @@ #include <LibGUI/TreeView.h> #include <LibWeb/DOM/Document.h> #include <LibWeb/DOM/Element.h> +#include <LibWebView/AccessibilityTreeModel.h> #include <LibWebView/DOMTreeModel.h> #include <LibWebView/OutOfProcessWebView.h> #include <LibWebView/StylePropertiesModel.h> @@ -91,6 +92,11 @@ InspectorWidget::InspectorWidget() set_selection(index); }; + auto& accessibility_tree_container = top_tab_widget.add_tab<GUI::Widget>("Accessibility"); + accessibility_tree_container.set_layout<GUI::VerticalBoxLayout>(); + accessibility_tree_container.layout()->set_margins({ 4, 4, 4, 4 }); + m_accessibility_tree_view = accessibility_tree_container.add<GUI::TreeView>(); + auto& bottom_tab_widget = splitter.add<GUI::TabWidget>(); auto& computed_style_table_container = bottom_tab_widget.add_tab<GUI::Widget>("Computed"); @@ -212,4 +218,11 @@ void InspectorWidget::clear_style_json() m_element_size_view->set_node_content_height(0); } +void InspectorWidget::set_accessibility_json(StringView json) +{ + m_accessibility_tree_view->set_model(WebView::AccessibilityTreeModel::create(json, *m_accessibility_tree_view)); + + // TODO: Support selections from accessibility tab +} + } diff --git a/Userland/Applications/Browser/InspectorWidget.h b/Userland/Applications/Browser/InspectorWidget.h index 16c85e39fb..095b2a4f0a 100644 --- a/Userland/Applications/Browser/InspectorWidget.h +++ b/Userland/Applications/Browser/InspectorWidget.h @@ -43,6 +43,7 @@ public: 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_accessibility_json(StringView); void set_selection(Selection); void select_default_node(); @@ -59,6 +60,7 @@ private: RefPtr<WebView::OutOfProcessWebView> m_web_view; RefPtr<GUI::TreeView> m_dom_tree_view; + RefPtr<GUI::TreeView> m_accessibility_tree_view; RefPtr<GUI::TableView> m_computed_style_table_view; RefPtr<GUI::TableView> m_resolved_style_table_view; RefPtr<GUI::TableView> m_custom_properties_table_view; diff --git a/Userland/Applications/Browser/Tab.cpp b/Userland/Applications/Browser/Tab.cpp index 9c989b20c4..e9ba11f7a0 100644 --- a/Userland/Applications/Browser/Tab.cpp +++ b/Userland/Applications/Browser/Tab.cpp @@ -244,8 +244,10 @@ Tab::Tab(BrowserWindow& window) update_status(); - if (m_dom_inspector_widget) + if (m_dom_inspector_widget) { m_web_content_view->inspect_dom_tree(); + m_web_content_view->inspect_accessibility_tree(); + } }; view().on_navigate_back = [this]() { @@ -421,6 +423,11 @@ Tab::Tab(BrowserWindow& window) m_dom_inspector_widget->set_dom_node_properties_json({ node_id }, specified, computed, custom_properties, node_box_sizing); }; + view().on_get_accessibility_tree = [this](auto& accessibility_tree) { + if (m_dom_inspector_widget) + m_dom_inspector_widget->set_accessibility_json(accessibility_tree); + }; + view().on_js_console_new_message = [this](auto message_index) { if (m_console_widget) m_console_widget->notify_about_new_console_message(message_index); @@ -666,6 +673,7 @@ void Tab::show_inspector_window(Browser::Tab::InspectorTarget inspector_target) m_dom_inspector_widget = window->set_main_widget<InspectorWidget>().release_value_but_fixme_should_propagate_errors(); m_dom_inspector_widget->set_web_view(*m_web_content_view); m_web_content_view->inspect_dom_tree(); + m_web_content_view->inspect_accessibility_tree(); } if (inspector_target == InspectorTarget::HoveredElement) { diff --git a/Userland/Libraries/LibWebView/AccessibilityTreeModel.cpp b/Userland/Libraries/LibWebView/AccessibilityTreeModel.cpp new file mode 100644 index 0000000000..ca0302ae1d --- /dev/null +++ b/Userland/Libraries/LibWebView/AccessibilityTreeModel.cpp @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2022, Jonah Shafran <jonahshafran@gmail.com> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <LibWebView/AccessibilityTreeModel.h> + +namespace WebView { + +AccessibilityTreeModel::AccessibilityTreeModel(JsonObject accessibility_tree, GUI::TreeView* tree_view) + : m_tree_view(tree_view) + , m_accessibility_tree(move(accessibility_tree)) +{ + map_accessibility_nodes_to_parent(nullptr, &m_accessibility_tree); +} + +AccessibilityTreeModel::~AccessibilityTreeModel() = default; + +GUI::ModelIndex AccessibilityTreeModel::index(int row, int column, GUI::ModelIndex const& parent) const +{ + if (!parent.is_valid()) { + return create_index(row, column, &m_accessibility_tree); + } + + auto const& parent_node = *static_cast<JsonObject const*>(parent.internal_data()); + auto const* children = get_children(parent_node); + if (!children) + return create_index(row, column, &m_accessibility_tree); + + auto const& child_node = children->at(row).as_object(); + return create_index(row, column, &child_node); +} + +GUI::ModelIndex AccessibilityTreeModel::parent_index(GUI::ModelIndex const& index) const +{ + if (!index.is_valid()) + return {}; + + auto const& node = *static_cast<JsonObject const*>(index.internal_data()); + + auto const* parent_node = get_parent(node); + if (!parent_node) + return {}; + + // If the parent is the root document, we know it has index 0, 0 + if (parent_node == &m_accessibility_tree) { + return create_index(0, 0, parent_node); + } + + // Otherwise, we need to find the grandparent, to find the index of parent within that + auto const* grandparent_node = get_parent(*parent_node); + VERIFY(grandparent_node); + + auto const* grandparent_children = get_children(*grandparent_node); + if (!grandparent_children) + return {}; + + for (size_t grandparent_child_index = 0; grandparent_child_index < grandparent_children->size(); ++grandparent_child_index) { + auto const& child = grandparent_children->at(grandparent_child_index).as_object(); + if (&child == parent_node) + return create_index(grandparent_child_index, 0, parent_node); + } + + return {}; +} + +int AccessibilityTreeModel::row_count(GUI::ModelIndex const& index) const +{ + if (!index.is_valid()) + return 1; + + auto const& node = *static_cast<JsonObject const*>(index.internal_data()); + auto const* children = get_children(node); + return children ? children->size() : 0; +} + +int AccessibilityTreeModel::column_count(const GUI::ModelIndex&) const +{ + return 1; +} + +GUI::Variant AccessibilityTreeModel::data(GUI::ModelIndex const& index, GUI::ModelRole role) const +{ + auto const& node = *static_cast<JsonObject const*>(index.internal_data()); + auto type = node.get("type"sv).as_string_or("unknown"sv); + + if (role == GUI::ModelRole::Display) { + if (type == "text") + return node.get("text"sv).as_string(); + + auto node_role = node.get("role"sv).as_string(); + if (type != "element") + return node_role; + + StringBuilder builder; + builder.append(node_role.to_lowercase()); + return builder.to_deprecated_string(); + } + return {}; +} + +void AccessibilityTreeModel::map_accessibility_nodes_to_parent(JsonObject const* parent, JsonObject const* node) +{ + m_accessibility_node_to_parent_map.set(node, parent); + + auto const* children = get_children(*node); + if (!children) + return; + + children->for_each([&](auto const& child) { + auto const& child_node = child.as_object(); + map_accessibility_nodes_to_parent(node, &child_node); + }); +} + +} diff --git a/Userland/Libraries/LibWebView/AccessibilityTreeModel.h b/Userland/Libraries/LibWebView/AccessibilityTreeModel.h new file mode 100644 index 0000000000..7cac5c85b1 --- /dev/null +++ b/Userland/Libraries/LibWebView/AccessibilityTreeModel.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2022, Jonah Shafran <jonahshafran@gmail.com> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <AK/JsonObject.h> +#include <LibGUI/Model.h> +#include <LibWeb/CSS/Selector.h> + +namespace WebView { + +class AccessibilityTreeModel final : public GUI::Model { +public: + static NonnullRefPtr<AccessibilityTreeModel> create(StringView accessibility_tree, GUI::TreeView& tree_view) + { + auto json_or_error = JsonValue::from_string(accessibility_tree).release_value_but_fixme_should_propagate_errors(); + return adopt_ref(*new AccessibilityTreeModel(json_or_error.as_object(), &tree_view)); + } + + static NonnullRefPtr<AccessibilityTreeModel> create(StringView accessibility_tree) + { + auto json_or_error = JsonValue::from_string(accessibility_tree).release_value_but_fixme_should_propagate_errors(); + return adopt_ref(*new AccessibilityTreeModel(json_or_error.as_object(), nullptr)); + } + + virtual ~AccessibilityTreeModel() override; + + virtual int row_count(GUI::ModelIndex const& = GUI::ModelIndex()) const override; + virtual int column_count(GUI::ModelIndex const& = GUI::ModelIndex()) const override; + virtual GUI::Variant data(GUI::ModelIndex const&, GUI::ModelRole) const override; + virtual GUI::ModelIndex index(int row, int column, GUI::ModelIndex const& parent = GUI::ModelIndex()) const override; + virtual GUI::ModelIndex parent_index(GUI::ModelIndex const&) const override; + +private: + AccessibilityTreeModel(JsonObject, GUI::TreeView*); + + ALWAYS_INLINE JsonObject const* get_parent(JsonObject const& o) const + { + auto parent_node = m_accessibility_node_to_parent_map.get(&o); + VERIFY(parent_node.has_value()); + return *parent_node; + } + + ALWAYS_INLINE static JsonArray const* get_children(JsonObject const& o) + { + if (auto const* maybe_children = o.get_ptr("children"sv); maybe_children) + return &maybe_children->as_array(); + return nullptr; + } + + void map_accessibility_nodes_to_parent(JsonObject const* parent, JsonObject const* child); + + GUI::TreeView* m_tree_view { nullptr }; + JsonObject m_accessibility_tree; + HashMap<JsonObject const*, JsonObject const*> m_accessibility_node_to_parent_map; +}; + +} diff --git a/Userland/Libraries/LibWebView/CMakeLists.txt b/Userland/Libraries/LibWebView/CMakeLists.txt index ee25b4463e..707bb835ee 100644 --- a/Userland/Libraries/LibWebView/CMakeLists.txt +++ b/Userland/Libraries/LibWebView/CMakeLists.txt @@ -1,4 +1,5 @@ set(SOURCES + AccessibilityTreeModel.cpp DOMTreeModel.cpp OutOfProcessWebView.cpp RequestServerAdapter.cpp |