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_tree_source_views.h"
21 #include "ui/views/controls/button/label_button.h"
22 #include "ui/views/test/widget_test.h"
23 
24 namespace views {
25 namespace test {
26 namespace {
27 
28 // This class can be used as a deleter for std::unique_ptr<Widget>
29 // to call function Widget::CloseNow automatically.
30 struct WidgetCloser {
operator ()views::test::__anonc45fdac20111::WidgetCloser31   inline void operator()(Widget* widget) const { widget->CloseNow(); }
32 };
33 
34 using WidgetAutoclosePtr = std::unique_ptr<Widget, WidgetCloser>;
35 
HasNodeWithName(ui::AXNode * node,const std::string & name)36 bool HasNodeWithName(ui::AXNode* node, const std::string& name) {
37   if (node->GetStringAttribute(ax::mojom::StringAttribute::kName) == name)
38     return true;
39   for (auto* child : node->children()) {
40     if (HasNodeWithName(child, name))
41       return true;
42   }
43   return false;
44 }
45 
HasNodeWithName(const ui::AXTree & tree,const std::string & name)46 bool HasNodeWithName(const ui::AXTree& tree, const std::string& name) {
47   return HasNodeWithName(tree.root(), name);
48 }
49 
50 class AXAuraObjCacheTest : public WidgetTest {
51  public:
52   AXAuraObjCacheTest() = default;
53   ~AXAuraObjCacheTest() override = default;
54 };
55 
TEST_F(AXAuraObjCacheTest,TestViewRemoval)56 TEST_F(AXAuraObjCacheTest, TestViewRemoval) {
57   AXAuraObjCache cache;
58   WidgetAutoclosePtr widget(CreateTopLevelPlatformWidget());
59   View* parent = new View();
60   widget->GetRootView()->AddChildView(parent);
61   View* child = new View();
62   parent->AddChildView(child);
63 
64   AXAuraObjWrapper* ax_widget = cache.GetOrCreate(widget.get());
65   ASSERT_NE(nullptr, ax_widget);
66   AXAuraObjWrapper* ax_parent = cache.GetOrCreate(parent);
67   ASSERT_NE(nullptr, ax_parent);
68   AXAuraObjWrapper* ax_child = cache.GetOrCreate(child);
69   ASSERT_NE(nullptr, ax_child);
70 
71   // Everything should have an ID, indicating it's in the cache.
72   ASSERT_GT(cache.GetID(widget.get()), 0);
73   ASSERT_GT(cache.GetID(parent), 0);
74   ASSERT_GT(cache.GetID(child), 0);
75 
76   // Removing the parent view should remove both the parent and child
77   // from the cache, but leave the widget.
78   widget->GetRootView()->RemoveChildView(parent);
79   ASSERT_GT(cache.GetID(widget.get()), 0);
80   ASSERT_EQ(ui::AXNode::kInvalidAXID, cache.GetID(parent));
81   ASSERT_EQ(ui::AXNode::kInvalidAXID, cache.GetID(child));
82 
83   // Explicitly delete |parent| to prevent a memory leak, since calling
84   // RemoveChildView() doesn't delete it.
85   delete parent;
86 }
87 
TEST_F(AXAuraObjCacheTest,ValidTree)88 TEST_F(AXAuraObjCacheTest, ValidTree) {
89   // Create a parent window.
90   auto parent_widget = std::make_unique<Widget>();
91   Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
92   params.bounds = gfx::Rect(0, 0, 200, 200);
93   params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
94   parent_widget->Init(std::move(params));
95   parent_widget->GetNativeWindow()->SetTitle(
96       base::ASCIIToUTF16("ParentWindow"));
97   parent_widget->Show();
98 
99   // Create a child window.
100   Widget* child_widget = new Widget();  // Owned by parent_widget.
101   params = CreateParams(Widget::InitParams::TYPE_BUBBLE);
102   params.parent = parent_widget->GetNativeWindow();
103   params.child = true;
104   params.bounds = gfx::Rect(100, 100, 200, 200);
105   params.ownership = views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET;
106   child_widget->Init(std::move(params));
107   child_widget->GetNativeWindow()->SetTitle(base::ASCIIToUTF16("ChildWindow"));
108   child_widget->Show();
109 
110   // Create a child view.
111   auto* button = new LabelButton(nullptr, base::ASCIIToUTF16("ChildButton"));
112   button->SetSize(gfx::Size(20, 20));
113   child_widget->GetContentsView()->AddChildView(button);
114 
115   // Use AXAuraObjCache to serialize the node tree.
116   AXAuraObjCache cache;
117   ui::AXTreeID tree_id = ui::AXTreeID::CreateNewAXTreeID();
118   AXTreeSourceViews tree_source(
119       cache.GetOrCreate(parent_widget->GetNativeWindow()), tree_id, &cache);
120   ui::AXTreeSerializer<AXAuraObjWrapper*, ui::AXNodeData, ui::AXTreeData>
121       serializer(&tree_source);
122   ui::AXTreeUpdate serialized_tree;
123   serializer.SerializeChanges(tree_source.GetRoot(), &serialized_tree);
124 
125   // Verify tree is valid.
126   ui::AXTreeSourceChecker<AXAuraObjWrapper*, ui::AXNodeData, ui::AXTreeData>
127       checker(&tree_source);
128   std::string error_string;
129   EXPECT_TRUE(checker.CheckAndGetErrorString(&error_string)) << error_string;
130   ui::AXTree ax_tree(serialized_tree);
131   EXPECT_TRUE(HasNodeWithName(ax_tree, "ParentWindow"));
132   EXPECT_TRUE(HasNodeWithName(ax_tree, "ChildWindow"));
133   EXPECT_TRUE(HasNodeWithName(ax_tree, "ChildButton"));
134 }
135 
136 }  // namespace
137 }  // namespace test
138 }  // namespace views
139