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