blob: 073a05654bdc788f424b1aa9ea928a0e49107278 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/views/accessibility/ax_aura_obj_cache.h"
#include "base/strings/utf_string_conversions.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/accessibility/ax_tree.h"
#include "ui/accessibility/ax_tree_serializer.h"
#include "ui/accessibility/ax_tree_source_checker.h"
#include "ui/aura/window.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/views/accessibility/ax_tree_source_views.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/test/widget_test.h"
namespace views {
namespace test {
namespace {
// This class can be used as a deleter for std::unique_ptr<Widget>
// to call function Widget::CloseNow automatically.
struct WidgetCloser {
inline void operator()(Widget* widget) const { widget->CloseNow(); }
};
using WidgetAutoclosePtr = std::unique_ptr<Widget, WidgetCloser>;
bool HasNodeWithName(ui::AXNode* node, const std::string& name) {
if (node->GetStringAttribute(ax::mojom::StringAttribute::kName) == name)
return true;
for (auto* child : node->children()) {
if (HasNodeWithName(child, name))
return true;
}
return false;
}
bool HasNodeWithName(const ui::AXTree& tree, const std::string& name) {
return HasNodeWithName(tree.root(), name);
}
class AXAuraObjCacheTest : public WidgetTest {
public:
AXAuraObjCacheTest() = default;
~AXAuraObjCacheTest() override = default;
};
TEST_F(AXAuraObjCacheTest, TestViewRemoval) {
AXAuraObjCache cache;
WidgetAutoclosePtr widget(CreateTopLevelPlatformWidget());
View* parent = new View();
widget->GetRootView()->AddChildView(parent);
View* child = new View();
parent->AddChildView(child);
AXAuraObjWrapper* ax_widget = cache.GetOrCreate(widget.get());
ASSERT_NE(nullptr, ax_widget);
AXAuraObjWrapper* ax_parent = cache.GetOrCreate(parent);
ASSERT_NE(nullptr, ax_parent);
AXAuraObjWrapper* ax_child = cache.GetOrCreate(child);
ASSERT_NE(nullptr, ax_child);
// Everything should have an ID, indicating it's in the cache.
ASSERT_GT(cache.GetID(widget.get()), 0);
ASSERT_GT(cache.GetID(parent), 0);
ASSERT_GT(cache.GetID(child), 0);
// Removing the parent view should remove both the parent and child
// from the cache, but leave the widget.
widget->GetRootView()->RemoveChildView(parent);
ASSERT_GT(cache.GetID(widget.get()), 0);
ASSERT_EQ(ui::AXNode::kInvalidAXID, cache.GetID(parent));
ASSERT_EQ(ui::AXNode::kInvalidAXID, cache.GetID(child));
// Explicitly delete |parent| to prevent a memory leak, since calling
// RemoveChildView() doesn't delete it.
delete parent;
}
TEST_F(AXAuraObjCacheTest, ValidTree) {
// Create a parent window.
auto parent_widget = std::make_unique<Widget>();
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
params.bounds = gfx::Rect(0, 0, 200, 200);
params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
parent_widget->Init(std::move(params));
parent_widget->GetNativeWindow()->SetTitle(
base::ASCIIToUTF16("ParentWindow"));
parent_widget->Show();
// Create a child window.
Widget* child_widget = new Widget(); // Owned by parent_widget.
params = CreateParams(Widget::InitParams::TYPE_BUBBLE);
params.parent = parent_widget->GetNativeWindow();
params.child = true;
params.bounds = gfx::Rect(100, 100, 200, 200);
params.ownership = views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET;
child_widget->Init(std::move(params));
child_widget->GetNativeWindow()->SetTitle(base::ASCIIToUTF16("ChildWindow"));
child_widget->Show();
// Create a child view.
auto* button = new LabelButton(nullptr, base::ASCIIToUTF16("ChildButton"));
button->SetSize(gfx::Size(20, 20));
child_widget->GetContentsView()->AddChildView(button);
// Use AXAuraObjCache to serialize the node tree.
AXAuraObjCache cache;
ui::AXTreeID tree_id = ui::AXTreeID::CreateNewAXTreeID();
AXTreeSourceViews tree_source(
cache.GetOrCreate(parent_widget->GetNativeWindow()), tree_id, &cache);
ui::AXTreeSerializer<AXAuraObjWrapper*, ui::AXNodeData, ui::AXTreeData>
serializer(&tree_source);
ui::AXTreeUpdate serialized_tree;
serializer.SerializeChanges(tree_source.GetRoot(), &serialized_tree);
// Verify tree is valid.
ui::AXTreeSourceChecker<AXAuraObjWrapper*, ui::AXNodeData, ui::AXTreeData>
checker(&tree_source);
std::string error_string;
EXPECT_TRUE(checker.CheckAndGetErrorString(&error_string)) << error_string;
ui::AXTree ax_tree(serialized_tree);
EXPECT_TRUE(HasNodeWithName(ax_tree, "ParentWindow"));
EXPECT_TRUE(HasNodeWithName(ax_tree, "ChildWindow"));
EXPECT_TRUE(HasNodeWithName(ax_tree, "ChildButton"));
}
} // namespace
} // namespace test
} // namespace views