blob: 71856076afa97422b21a49e5d66063e55cdd6f85 [file] [log] [blame]
// Copyright 2018 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 "ash/ws/ax_ash_window_utils.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "ash/test/ash_test_helper.h"
#include "ash/wm/desks/desks_util.h"
#include "base/macros.h"
#include "base/strings/utf_string_conversions.h"
#include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node.h"
#include "ui/accessibility/ax_tree.h"
#include "ui/accessibility/ax_tree_id.h"
#include "ui/accessibility/ax_tree_serializer.h"
#include "ui/accessibility/ax_tree_source_checker.h"
#include "ui/accessibility/ax_tree_update.h"
#include "ui/aura/env.h"
#include "ui/aura/mus/window_mus.h"
#include "ui/aura/mus/window_tree_client.h"
#include "ui/aura/mus/window_tree_host_mus.h"
#include "ui/aura/mus/window_tree_host_mus_init_params.h"
#include "ui/aura/test/mus/change_completion_waiter.h"
#include "ui/aura/window.h"
#include "ui/base/ui_base_features.h"
#include "ui/views/accessibility/ax_aura_obj_cache.h"
#include "ui/views/accessibility/ax_root_obj_wrapper.h"
#include "ui/views/accessibility/ax_tree_source_views.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/mus/mus_client.h"
#include "ui/views/widget/widget.h"
using views::AXAuraObjCache;
using views::AXAuraObjWrapper;
using views::AXTreeSourceViews;
using views::MusClient;
using views::Textfield;
using views::View;
using views::Widget;
using AuraAXTreeSerializer = ui::
AXTreeSerializer<views::AXAuraObjWrapper*, ui::AXNodeData, ui::AXTreeData>;
using AuraAXTreeSourceChecker =
ui::AXTreeSourceChecker<views::AXAuraObjWrapper*,
ui::AXNodeData,
ui::AXTreeData>;
namespace ash {
namespace {
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 HasNodeWithValue(ui::AXNode* node, const std::string& value) {
if (node->GetStringAttribute(ax::mojom::StringAttribute::kValue) == value)
return true;
for (auto* child : node->children()) {
if (HasNodeWithValue(child, value))
return true;
}
return false;
}
class AXAshWindowUtilsTest : public SingleProcessMashTestBase {
public:
AXAshWindowUtilsTest() = default;
~AXAshWindowUtilsTest() override = default;
// AshTestBase:
void SetUp() override {
SingleProcessMashTestBase::SetUp();
// Create a widget with a child view.
widget_ = std::make_unique<Widget>();
Widget::InitParams params;
params.name = "Test Widget";
params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.bounds = gfx::Rect(0, 0, 200, 200);
params.native_widget =
MusClient::Get()->CreateNativeWidget(params, widget_.get());
widget_->Init(params);
widget_->Show();
textfield_ = new Textfield();
textfield_->SetText(base::ASCIIToUTF16("Test Textfield"));
widget_->GetContentsView()->AddChildView(textfield_);
// Flush all messages from the WindowTreeClient to ensure the window service
// has finished Widget creation.
aura::test::WaitForAllChangesToComplete();
}
void TearDown() override {
widget_.reset();
SingleProcessMashTestBase::TearDown();
}
protected:
std::unique_ptr<Widget> widget_;
Textfield* textfield_ = nullptr; // Owned by views hierarchy.
DISALLOW_COPY_AND_ASSIGN(AXAshWindowUtilsTest);
};
TEST_F(AXAshWindowUtilsTest, WalkUpToAshRoot) {
AXAshWindowUtils utils;
// Start with a window in the client.
aura::Window* window = widget_->GetNativeWindow();
EXPECT_EQ(aura::Env::Mode::MUS, window->env()->mode());
// Walk up to the root node.
while (true) {
aura::Window* parent = utils.GetParent(window);
if (!parent)
break;
window = parent;
}
// Walking up "jumped the fence" into ash windows.
EXPECT_EQ(aura::Env::Mode::LOCAL, window->env()->mode());
EXPECT_EQ(Shell::GetPrimaryRootWindow()->GetName(), window->GetName());
}
TEST_F(AXAshWindowUtilsTest, WalkDownToClientWindow) {
AXAshWindowUtils utils;
// Start with the widget's container.
aura::Window* container = Shell::GetContainer(
Shell::GetPrimaryRootWindow(), desks_util::GetActiveDeskContainerId());
EXPECT_EQ(aura::Env::Mode::LOCAL, container->env()->mode());
// Walking down "jumps the fence" into the client window.
std::vector<aura::Window*> children = utils.GetChildren(container);
ASSERT_EQ(1u, children.size());
aura::Window* child = children[0];
EXPECT_EQ(aura::Env::Mode::MUS, child->env()->mode());
EXPECT_EQ(widget_.get(), utils.GetWidgetForNativeView(child));
}
// Verify integration of AXAshWindowUtils with the accessibility subsystem
// serialization code.
TEST_F(AXAshWindowUtilsTest, SerializeNodeTree) {
// Build a desktop tree serializer similar to AutomationManagerAura.
AXAuraObjCache cache;
cache.OnRootWindowObjCreated(Shell::GetPrimaryRootWindow());
AXRootObjWrapper root_wrapper(nullptr /* delegate */, &cache);
AXTreeSourceViews ax_tree_source(&root_wrapper,
ui::AXTreeID::CreateNewAXTreeID(), &cache);
AuraAXTreeSerializer ax_serializer(&ax_tree_source);
// Initial tree is valid.
AuraAXTreeSourceChecker checker(&ax_tree_source);
ASSERT_TRUE(checker.Check());
// Simulate initial serialization.
ui::AXTreeUpdate initial_update;
ax_serializer.SerializeChanges(ax_tree_source.GetRoot(), &initial_update);
// Tree includes system UI.
ui::AXTree ax_tree(initial_update);
EXPECT_TRUE(HasNodeWithName(ax_tree.root(), "Shelf"));
// Remove the child view and re-add it, which should fire some events.
widget_->GetContentsView()->RemoveAllChildViews(false /* delete_children */);
widget_->GetContentsView()->AddChildView(textfield_);
// Serialize walking up from the textfield.
ui::AXTreeUpdate label_update;
AXAuraObjWrapper* wrapper = cache.GetOrCreate(textfield_);
ASSERT_TRUE(ax_serializer.SerializeChanges(wrapper, &label_update));
ASSERT_TRUE(ax_tree.Unserialize(label_update));
// Tree still includes system UI.
EXPECT_TRUE(HasNodeWithName(ax_tree.root(), "Shelf"));
// The serializer also "jumped the fence" from the ash tree to the mus-client
// tree, so the tree has nodes from inside the Widget.
EXPECT_TRUE(HasNodeWithValue(ax_tree.root(), "Test Textfield"));
// AXAuraObjCache can reach into a client Widget to find a focused view.
textfield_->RequestFocus();
EXPECT_TRUE(textfield_->HasFocus());
AXAuraObjWrapper* textfield_wrapper = cache.GetOrCreate(textfield_);
EXPECT_EQ(textfield_wrapper, cache.GetFocus());
}
TEST_F(AXAshWindowUtilsTest, IsRootWindow) {
AXAshWindowUtils utils;
// From the client's perspective a widget's root window is a root.
aura::Window* widget_root = widget_->GetNativeWindow()->GetRootWindow();
EXPECT_TRUE(widget_root->IsRootWindow());
// Simulate an embedded remote client window.
aura::WindowTreeHostMus window_tree_host(aura::CreateInitParamsForTopLevel(
views::MusClient::Get()->window_tree_client()));
window_tree_host.InitHost();
window_tree_host.SetBounds(gfx::Rect(0, 0, 100, 200),
viz::LocalSurfaceIdAllocation());
aura::Window* embed_root = window_tree_host.window();
// From the client's perspective the embed is a root.
EXPECT_TRUE(embed_root->IsRootWindow());
// Accessibility serialization only considers display roots to be roots.
EXPECT_TRUE(utils.IsRootWindow(Shell::GetPrimaryRootWindow()));
EXPECT_FALSE(utils.IsRootWindow(embed_root));
EXPECT_FALSE(utils.IsRootWindow(widget_root));
}
} // namespace
} // namespace ash