1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "ui/views/accessibility/ax_aura_obj_cache.h"
6
7 #include <string>
8 #include <utility>
9
10 #include "base/strings/utf_string_conversions.h"
11 #include "ui/accessibility/ax_enums.mojom.h"
12 #include "ui/accessibility/ax_node.h"
13 #include "ui/accessibility/ax_node_data.h"
14 #include "ui/accessibility/ax_tree.h"
15 #include "ui/accessibility/ax_tree_serializer.h"
16 #include "ui/accessibility/ax_tree_source_checker.h"
17 #include "ui/aura/window.h"
18 #include "ui/gfx/geometry/rect.h"
19 #include "ui/gfx/geometry/size.h"
20 #include "ui/views/accessibility/ax_aura_obj_wrapper.h"
21 #include "ui/views/accessibility/ax_tree_source_views.h"
22 #include "ui/views/accessibility/view_accessibility.h"
23 #include "ui/views/controls/button/label_button.h"
24 #include "ui/views/test/widget_test.h"
25
26 namespace views {
27 namespace test {
28 namespace {
29
30 // This class can be used as a deleter for std::unique_ptr<Widget>
31 // to call function Widget::CloseNow automatically.
32 struct WidgetCloser {
operator ()views::test::__anon966afd670111::WidgetCloser33 inline void operator()(Widget* widget) const { widget->CloseNow(); }
34 };
35
36 using WidgetAutoclosePtr = std::unique_ptr<Widget, WidgetCloser>;
37
HasNodeWithName(ui::AXNode * node,const std::string & name)38 bool HasNodeWithName(ui::AXNode* node, const std::string& name) {
39 if (node->GetStringAttribute(ax::mojom::StringAttribute::kName) == name)
40 return true;
41 for (auto* child : node->children()) {
42 if (HasNodeWithName(child, name))
43 return true;
44 }
45 return false;
46 }
47
HasNodeWithName(const ui::AXTree & tree,const std::string & name)48 bool HasNodeWithName(const ui::AXTree& tree, const std::string& name) {
49 return HasNodeWithName(tree.root(), name);
50 }
51
52 class AXAuraObjCacheTest : public WidgetTest {
53 public:
54 AXAuraObjCacheTest() = default;
55 ~AXAuraObjCacheTest() override = default;
56
GetData(AXAuraObjWrapper * wrapper)57 ui::AXNodeData GetData(AXAuraObjWrapper* wrapper) {
58 ui::AXNodeData data;
59 wrapper->Serialize(&data);
60 return data;
61 }
62 };
63
TEST_F(AXAuraObjCacheTest,TestViewRemoval)64 TEST_F(AXAuraObjCacheTest, TestViewRemoval) {
65 AXAuraObjCache cache;
66 WidgetAutoclosePtr widget(CreateTopLevelPlatformWidget());
67 View* parent = new View();
68 widget->GetRootView()->AddChildView(parent);
69 View* child = new View();
70 parent->AddChildView(child);
71
72 AXAuraObjWrapper* ax_widget = cache.GetOrCreate(widget.get());
73 ASSERT_NE(nullptr, ax_widget);
74 AXAuraObjWrapper* ax_parent = cache.GetOrCreate(parent);
75 ASSERT_NE(nullptr, ax_parent);
76 AXAuraObjWrapper* ax_child = cache.GetOrCreate(child);
77 ASSERT_NE(nullptr, ax_child);
78
79 // Everything should have an ID, indicating it's in the cache.
80 ASSERT_GT(cache.GetID(widget.get()), 0);
81 ASSERT_GT(cache.GetID(parent), 0);
82 ASSERT_GT(cache.GetID(child), 0);
83
84 // Removing the parent view should remove both the parent and child
85 // from the cache, but leave the widget.
86 widget->GetRootView()->RemoveChildView(parent);
87 ASSERT_GT(cache.GetID(widget.get()), 0);
88 ASSERT_EQ(ui::AXNode::kInvalidAXID, cache.GetID(parent));
89 ASSERT_EQ(ui::AXNode::kInvalidAXID, cache.GetID(child));
90
91 // Explicitly delete |parent| to prevent a memory leak, since calling
92 // RemoveChildView() doesn't delete it.
93 delete parent;
94 }
95
TEST_F(AXAuraObjCacheTest,ValidTree)96 TEST_F(AXAuraObjCacheTest, ValidTree) {
97 // Create a parent window.
98 auto parent_widget = std::make_unique<Widget>();
99 Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
100 params.bounds = gfx::Rect(0, 0, 200, 200);
101 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
102 parent_widget->Init(std::move(params));
103 parent_widget->GetNativeWindow()->SetTitle(
104 base::ASCIIToUTF16("ParentWindow"));
105 parent_widget->Show();
106
107 // Create a child window.
108 Widget* child_widget = new Widget(); // Owned by parent_widget.
109 params = CreateParams(Widget::InitParams::TYPE_BUBBLE);
110 params.parent = parent_widget->GetNativeWindow();
111 params.child = true;
112 params.bounds = gfx::Rect(100, 100, 200, 200);
113 params.ownership = views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET;
114 child_widget->Init(std::move(params));
115 child_widget->GetNativeWindow()->SetTitle(base::ASCIIToUTF16("ChildWindow"));
116 child_widget->Show();
117
118 // Create a child view.
119 auto* button = new LabelButton(Button::PressedCallback(),
120 base::ASCIIToUTF16("ChildButton"));
121 button->SetSize(gfx::Size(20, 20));
122 child_widget->GetContentsView()->AddChildView(button);
123
124 // Use AXAuraObjCache to serialize the node tree.
125 AXAuraObjCache cache;
126 ui::AXTreeID tree_id = ui::AXTreeID::CreateNewAXTreeID();
127 AXTreeSourceViews tree_source(
128 cache.GetOrCreate(parent_widget->GetNativeWindow()), tree_id, &cache);
129 ui::AXTreeSerializer<AXAuraObjWrapper*, ui::AXNodeData, ui::AXTreeData>
130 serializer(&tree_source);
131 ui::AXTreeUpdate serialized_tree;
132 serializer.SerializeChanges(tree_source.GetRoot(), &serialized_tree);
133
134 // Verify tree is valid.
135 ui::AXTreeSourceChecker<AXAuraObjWrapper*, ui::AXNodeData, ui::AXTreeData>
136 checker(&tree_source);
137 std::string error_string;
138 EXPECT_TRUE(checker.CheckAndGetErrorString(&error_string)) << error_string;
139 ui::AXTree ax_tree(serialized_tree);
140 EXPECT_TRUE(HasNodeWithName(ax_tree, "ParentWindow"));
141 EXPECT_TRUE(HasNodeWithName(ax_tree, "ChildWindow"));
142 EXPECT_TRUE(HasNodeWithName(ax_tree, "ChildButton"));
143 }
144
TEST_F(AXAuraObjCacheTest,GetFocusIsUnignoredAncestor)145 TEST_F(AXAuraObjCacheTest, GetFocusIsUnignoredAncestor) {
146 AXAuraObjCache cache;
147 auto widget = std::make_unique<Widget>();
148 Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
149 params.bounds = gfx::Rect(0, 0, 200, 200);
150 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
151 params.activatable = views::Widget::InitParams::ACTIVATABLE_YES;
152 widget->Init(std::move(params));
153 widget->Show();
154
155 // Note that AXAuraObjCache::GetFocusedView has some logic to force focus on
156 // the first child of the client view when one cannot be found from the
157 // FocusManager.
158 auto* client = widget->non_client_view()->client_view();
159 ASSERT_NE(nullptr, client);
160 auto* client_child = client->children().front();
161 ASSERT_NE(nullptr, client_child);
162 client_child->GetViewAccessibility().OverrideRole(ax::mojom::Role::kDialog);
163
164 View* parent = new View();
165 widget->GetRootView()->AddChildView(parent);
166 parent->GetViewAccessibility().OverrideRole(ax::mojom::Role::kTextField);
167 parent->SetFocusBehavior(View::FocusBehavior::ALWAYS);
168
169 View* child = new View();
170 parent->AddChildView(child);
171 child->GetViewAccessibility().OverrideRole(ax::mojom::Role::kGroup);
172 child->SetFocusBehavior(View::FocusBehavior::ALWAYS);
173
174 auto* ax_widget = cache.GetOrCreate(widget.get());
175 ASSERT_NE(nullptr, ax_widget);
176 auto* ax_client_child = cache.GetOrCreate(client_child);
177 ASSERT_NE(nullptr, ax_client_child);
178 auto* ax_parent = cache.GetOrCreate(parent);
179 ASSERT_NE(nullptr, ax_parent);
180 auto* ax_child = cache.GetOrCreate(child);
181 ASSERT_NE(nullptr, ax_child);
182
183 ASSERT_EQ(nullptr, cache.GetFocus());
184 cache.OnRootWindowObjCreated(widget->GetNativeWindow());
185 ASSERT_EQ(ax::mojom::Role::kDialog, GetData(cache.GetFocus()).role);
186 ASSERT_EQ(ax_client_child, cache.GetFocus());
187
188 parent->RequestFocus();
189 ASSERT_EQ(ax::mojom::Role::kTextField, GetData(cache.GetFocus()).role);
190 ASSERT_EQ(ax_parent, cache.GetFocus());
191
192 child->RequestFocus();
193 ASSERT_EQ(ax::mojom::Role::kGroup, GetData(cache.GetFocus()).role);
194 ASSERT_EQ(ax_child, cache.GetFocus());
195
196 child->GetViewAccessibility().OverrideIsIgnored(true);
197 ASSERT_EQ(ax::mojom::Role::kTextField, GetData(cache.GetFocus()).role);
198 ASSERT_EQ(ax_parent, cache.GetFocus());
199
200 parent->GetViewAccessibility().OverrideIsIgnored(true);
201 ASSERT_EQ(ax::mojom::Role::kWindow, GetData(cache.GetFocus()).role);
202 ASSERT_EQ(cache.GetOrCreate(widget->GetRootView()), cache.GetFocus());
203
204 cache.OnRootWindowObjDestroyed(widget->GetNativeWindow());
205 }
206
207 } // namespace
208 } // namespace test
209 } // namespace views
210