blob: 75ca16f4c43eebd9506ac2d44a694a60ab0ec0d9 [file] [log] [blame]
Sharon Yangfbb9ba4a2019-11-18 23:59:561// Copyright 2019 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 <fuchsia/accessibility/semantics/cpp/fidl.h>
6#include <fuchsia/accessibility/semantics/cpp/fidl_test_base.h>
7#include <lib/sys/cpp/component_context.h>
Sharon Yang8b548ab92019-12-20 01:52:548#include <lib/ui/scenic/cpp/view_ref_pair.h>
Sharon Yangfbb9ba4a2019-11-18 23:59:569#include <zircon/types.h>
10
Sharon Yangaa2c9bd2019-11-21 17:59:2211#include "base/auto_reset.h"
Sharon Yangfbb9ba4a2019-11-18 23:59:5612#include "base/fuchsia/default_context.h"
13#include "base/fuchsia/scoped_service_binding.h"
14#include "base/fuchsia/service_directory_client.h"
15#include "base/logging.h"
16#include "base/test/bind_test_util.h"
17#include "content/public/browser/web_contents_observer.h"
18#include "fuchsia/base/frame_test_util.h"
Sharon Yangfbb9ba4a2019-11-18 23:59:5619#include "fuchsia/base/test_navigation_listener.h"
20#include "fuchsia/engine/browser/accessibility_bridge.h"
21#include "fuchsia/engine/browser/frame_impl.h"
22#include "fuchsia/engine/test/test_data.h"
23#include "fuchsia/engine/test/web_engine_browser_test.h"
24#include "net/test/embedded_test_server/embedded_test_server.h"
25#include "testing/gtest/include/gtest/gtest.h"
Sharon Yang8b548ab92019-12-20 01:52:5426#include "ui/gfx/switches.h"
27#include "ui/ozone/public/ozone_switches.h"
Sharon Yangfbb9ba4a2019-11-18 23:59:5628
29using fuchsia::accessibility::semantics::Node;
30using fuchsia::accessibility::semantics::SemanticListener;
31using fuchsia::accessibility::semantics::SemanticsManager;
32using fuchsia::accessibility::semantics::SemanticTree;
33
34namespace {
35
36const char kPage1Path[] = "/ax1.html";
37const char kPage2Path[] = "/batching.html";
38const char kPage1Title[] = "accessibility 1";
39const char kPage2Title[] = "lots of nodes!";
40const char kButtonName[] = "a button";
41const char kNodeName[] = "last node";
42const char kParagraphName[] = "a third paragraph";
Sharon Yangaa2c9bd2019-11-21 17:59:2243const size_t kPage1NodeCount = 9;
44const size_t kPage2NodeCount = 190;
Sharon Yangfbb9ba4a2019-11-18 23:59:5645
46class FakeSemanticTree
47 : public fuchsia::accessibility::semantics::testing::SemanticTree_TestBase {
48 public:
49 FakeSemanticTree() = default;
50 ~FakeSemanticTree() override = default;
51
52 // fuchsia::accessibility::semantics::SemanticTree implementation.
53 void UpdateSemanticNodes(std::vector<Node> nodes) final {
54 for (auto& node : nodes)
55 nodes_.push_back(std::move(node));
56 }
57
58 void DeleteSemanticNodes(std::vector<uint32_t> node_ids) final {
59 for (auto id : node_ids) {
60 for (uint i = 0; i < nodes_.size(); i++) {
61 if (nodes_.at(i).node_id() == id)
62 nodes_.erase(nodes_.begin() + i);
63 }
64 }
65 }
66
67 void CommitUpdates(CommitUpdatesCallback callback) final {
68 callback();
69 if (on_commit_updates_)
Sharon Yangaa2c9bd2019-11-21 17:59:2270 on_commit_updates_.Run();
Sharon Yangfbb9ba4a2019-11-18 23:59:5671 }
72
73 void NotImplemented_(const std::string& name) final {
74 NOTIMPLEMENTED() << name;
75 }
76
77 void RunUntilNodeCountAtLeast(size_t count) {
78 DCHECK(!on_commit_updates_);
Sharon Yangfbb9ba4a2019-11-18 23:59:5679 if (nodes_.size() >= count)
80 return;
81
Sharon Yangaa2c9bd2019-11-21 17:59:2282 base::RunLoop run_loop;
83 base::AutoReset<base::RepeatingClosure> auto_reset(
84 &on_commit_updates_,
85 base::BindLambdaForTesting([this, count, &run_loop]() {
86 if (nodes_.size() >= count) {
87 run_loop.Quit();
88 }
89 }));
90 run_loop.Run();
Sharon Yangfbb9ba4a2019-11-18 23:59:5691 }
92
93 bool HasNodeWithLabel(base::StringPiece name) {
94 for (auto& node : nodes_) {
95 if (node.has_attributes() && node.attributes().has_label() &&
96 node.attributes().label() == name) {
97 return true;
98 }
99 }
100 return false;
101 }
102
Sharon Yang8b548ab92019-12-20 01:52:54103 Node* GetNodeFromLabel(base::StringPiece name) {
104 for (auto& node : nodes_) {
105 if (node.has_attributes() && node.attributes().has_label() &&
106 node.attributes().label() == name) {
107 return &node;
108 }
109 }
110 return nullptr;
111 }
112
Sharon Yangfbb9ba4a2019-11-18 23:59:56113 private:
114 std::vector<Node> nodes_;
Sharon Yangaa2c9bd2019-11-21 17:59:22115 base::RepeatingClosure on_commit_updates_;
Sharon Yangfbb9ba4a2019-11-18 23:59:56116
117 DISALLOW_COPY_AND_ASSIGN(FakeSemanticTree);
118};
119
120class FakeSemanticsManager : public fuchsia::accessibility::semantics::testing::
121 SemanticsManager_TestBase {
122 public:
123 FakeSemanticsManager() : semantic_tree_binding_(&semantic_tree_) {}
124 ~FakeSemanticsManager() override = default;
125
126 bool is_view_registered() const { return view_ref_.reference.is_valid(); }
127 bool is_listener_valid() const { return static_cast<bool>(listener_); }
128 FakeSemanticTree* semantic_tree() { return &semantic_tree_; }
129
130 // Directly call the listener to simulate Fuchsia setting the semantics mode.
131 void SetSemanticsModeEnabled(bool is_enabled) {
132 listener_->OnSemanticsModeChanged(is_enabled, []() {});
133 }
134
Sharon Yang8b548ab92019-12-20 01:52:54135 // The value returned by hit testing is written to a class member. In the case
136 // Run() times out, the function continues so we don't want to write to a
137 // local variable.
138 uint32_t HitTestAtPointSync(fuchsia::math::PointF target_point) {
139 hit_test_result_.reset();
140 base::RunLoop run_loop;
141 listener_->HitTest(target_point,
142 [quit = run_loop.QuitClosure(),
143 this](fuchsia::accessibility::semantics::Hit hit) {
144 if (hit.has_node_id()) {
145 hit_test_result_ = hit.node_id();
146 }
147 quit.Run();
148 });
149 run_loop.Run();
150
151 return hit_test_result_.value();
152 }
153
Sharon Yangfbb9ba4a2019-11-18 23:59:56154 // fuchsia::accessibility::semantics::SemanticsManager implementation.
155 void RegisterViewForSemantics(
156 fuchsia::ui::views::ViewRef view_ref,
157 fidl::InterfaceHandle<SemanticListener> listener,
158 fidl::InterfaceRequest<SemanticTree> semantic_tree_request) final {
159 view_ref_ = std::move(view_ref);
160 listener_ = listener.Bind();
161 semantic_tree_binding_.Bind(std::move(semantic_tree_request));
162 }
163
164 void NotImplemented_(const std::string& name) final {
165 NOTIMPLEMENTED() << name;
166 }
167
168 private:
169 fuchsia::ui::views::ViewRef view_ref_;
170 fuchsia::accessibility::semantics::SemanticListenerPtr listener_;
171 FakeSemanticTree semantic_tree_;
172 fidl::Binding<SemanticTree> semantic_tree_binding_;
Sharon Yang8b548ab92019-12-20 01:52:54173 base::Optional<uint32_t> hit_test_result_;
Sharon Yangfbb9ba4a2019-11-18 23:59:56174
175 DISALLOW_COPY_AND_ASSIGN(FakeSemanticsManager);
176};
177
Sharon Yang8b548ab92019-12-20 01:52:54178fuchsia::math::PointF GetCenterOfBox(fuchsia::ui::gfx::BoundingBox box) {
179 fuchsia::math::PointF center;
180 center.x = (box.min.x + box.max.x) / 2;
181 center.y = (box.min.y + box.max.y) / 2;
182 return center;
183}
184
Sharon Yangfbb9ba4a2019-11-18 23:59:56185} // namespace
186
187class AccessibilityBridgeTest : public cr_fuchsia::WebEngineBrowserTest {
188 public:
189 AccessibilityBridgeTest() : semantics_manager_binding_(&semantics_manager_) {
190 cr_fuchsia::WebEngineBrowserTest::set_test_server_root(
191 base::FilePath(cr_fuchsia::kTestServerRoot));
192 }
193
194 ~AccessibilityBridgeTest() override = default;
195
Sharon Yang8b548ab92019-12-20 01:52:54196 void SetUp() override {
197 base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
198 command_line->AppendSwitchNative(switches::kOzonePlatform,
199 switches::kHeadless);
200 command_line->AppendSwitch(switches::kHeadless);
201 cr_fuchsia::WebEngineBrowserTest::SetUp();
202 }
203
Sharon Yangfbb9ba4a2019-11-18 23:59:56204 void SetUpOnMainThread() override {
205 fuchsia::accessibility::semantics::SemanticsManagerPtr
206 semantics_manager_ptr;
207 semantics_manager_binding_.Bind(semantics_manager_ptr.NewRequest());
208
209 frame_ptr_ =
210 cr_fuchsia::WebEngineBrowserTest::CreateFrame(&navigation_listener_);
211 frame_impl_ = context_impl()->GetFrameImplForTest(&frame_ptr_);
212 frame_impl_->set_semantics_manager_for_test(
213 std::move(semantics_manager_ptr));
Sharon Yang8b548ab92019-12-20 01:52:54214 frame_ptr_->EnableHeadlessRendering();
Sharon Yangfbb9ba4a2019-11-18 23:59:56215
Sharon Yangfbb9ba4a2019-11-18 23:59:56216 base::RunLoop().RunUntilIdle();
217 }
218
219 protected:
220 fuchsia::web::FramePtr frame_ptr_;
221 FrameImpl* frame_impl_;
222 FakeSemanticsManager semantics_manager_;
223 fidl::Binding<SemanticsManager> semantics_manager_binding_;
224 cr_fuchsia::TestNavigationListener navigation_listener_;
225
226 DISALLOW_COPY_AND_ASSIGN(AccessibilityBridgeTest);
227};
228
229// Test registration to the SemanticsManager and accessibility mode on
230// WebContents is set correctly.
231IN_PROC_BROWSER_TEST_F(AccessibilityBridgeTest, RegisterViewRef) {
232 // Check that setup is successful.
233 EXPECT_TRUE(semantics_manager_.is_view_registered());
234 EXPECT_TRUE(semantics_manager_.is_listener_valid());
235
236 // Change the accessibility mode on the Fuchsia side and check that it is
237 // propagated correctly.
238 EXPECT_FALSE(frame_impl_->web_contents_for_test()
239 ->IsWebContentsOnlyAccessibilityModeForTesting());
240 semantics_manager_.SetSemanticsModeEnabled(true);
241 base::RunLoop().RunUntilIdle();
242 EXPECT_TRUE(frame_impl_->web_contents_for_test()
243 ->IsWebContentsOnlyAccessibilityModeForTesting());
244}
245
246IN_PROC_BROWSER_TEST_F(AccessibilityBridgeTest, CorrectDataSent) {
247 fuchsia::web::NavigationControllerPtr controller;
248 frame_ptr_->GetNavigationController(controller.NewRequest());
249 ASSERT_TRUE(embedded_test_server()->Start());
Sharon Yangfbb9ba4a2019-11-18 23:59:56250 semantics_manager_.SetSemanticsModeEnabled(true);
Sharon Yang8b548ab92019-12-20 01:52:54251
252 GURL page_url1(embedded_test_server()->GetURL(kPage1Path));
Sharon Yangfbb9ba4a2019-11-18 23:59:56253 EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
Sharon Yang8b548ab92019-12-20 01:52:54254 controller.get(), fuchsia::web::LoadUrlParams(), page_url1.spec()));
255 navigation_listener_.RunUntilUrlAndTitleEquals(page_url1, kPage1Title);
Sharon Yangfbb9ba4a2019-11-18 23:59:56256
257 // Check that the data values are correct in the FakeSemanticTree.
258 // TODO(fxb/18796): Test more fields once Chrome to Fuchsia conversions are
259 // available.
260 semantics_manager_.semantic_tree()->RunUntilNodeCountAtLeast(kPage1NodeCount);
261 EXPECT_TRUE(
262 semantics_manager_.semantic_tree()->HasNodeWithLabel(kPage1Title));
263 EXPECT_TRUE(
264 semantics_manager_.semantic_tree()->HasNodeWithLabel(kButtonName));
265 EXPECT_TRUE(
266 semantics_manager_.semantic_tree()->HasNodeWithLabel(kParagraphName));
267}
268
269// Batching is performed when the number of nodes to send or delete exceeds the
270// maximum, as set on the Fuchsia side. Check that all nodes are received by the
271// Semantic Tree when batching is performed.
272IN_PROC_BROWSER_TEST_F(AccessibilityBridgeTest, DataSentWithBatching) {
273 fuchsia::web::NavigationControllerPtr controller;
274 frame_ptr_->GetNavigationController(controller.NewRequest());
275 ASSERT_TRUE(embedded_test_server()->Start());
Sharon Yangfbb9ba4a2019-11-18 23:59:56276 semantics_manager_.SetSemanticsModeEnabled(true);
Sharon Yang8b548ab92019-12-20 01:52:54277
278 GURL page_url2(embedded_test_server()->GetURL(kPage2Path));
Sharon Yangfbb9ba4a2019-11-18 23:59:56279 EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
Sharon Yang8b548ab92019-12-20 01:52:54280 controller.get(), fuchsia::web::LoadUrlParams(), page_url2.spec()));
281 navigation_listener_.RunUntilUrlAndTitleEquals(page_url2, kPage2Title);
Sharon Yangfbb9ba4a2019-11-18 23:59:56282
283 // Run until we expect more than a batch's worth of nodes to be present.
284 semantics_manager_.semantic_tree()->RunUntilNodeCountAtLeast(kPage2NodeCount);
285 EXPECT_TRUE(semantics_manager_.semantic_tree()->HasNodeWithLabel(kNodeName));
286}
287
288// Check that semantics information is correctly sent when navigating from page
289// to page.
290IN_PROC_BROWSER_TEST_F(AccessibilityBridgeTest, TestNavigation) {
291 fuchsia::web::NavigationControllerPtr controller;
292 frame_ptr_->GetNavigationController(controller.NewRequest());
293 ASSERT_TRUE(embedded_test_server()->Start());
Sharon Yangfbb9ba4a2019-11-18 23:59:56294 semantics_manager_.SetSemanticsModeEnabled(true);
Sharon Yang8b548ab92019-12-20 01:52:54295
296 GURL page_url1(embedded_test_server()->GetURL(kPage1Path));
Sharon Yangfbb9ba4a2019-11-18 23:59:56297 EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
Sharon Yang8b548ab92019-12-20 01:52:54298 controller.get(), fuchsia::web::LoadUrlParams(), page_url1.spec()));
299 navigation_listener_.RunUntilUrlAndTitleEquals(page_url1, kPage1Title);
Sharon Yangfbb9ba4a2019-11-18 23:59:56300
301 semantics_manager_.semantic_tree()->RunUntilNodeCountAtLeast(kPage1NodeCount);
302 EXPECT_TRUE(
303 semantics_manager_.semantic_tree()->HasNodeWithLabel(kPage1Title));
304 EXPECT_TRUE(
305 semantics_manager_.semantic_tree()->HasNodeWithLabel(kButtonName));
306 EXPECT_TRUE(
307 semantics_manager_.semantic_tree()->HasNodeWithLabel(kParagraphName));
308
Sharon Yang8b548ab92019-12-20 01:52:54309 GURL page_url2(embedded_test_server()->GetURL(kPage2Path));
Sharon Yangfbb9ba4a2019-11-18 23:59:56310 EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
Sharon Yang8b548ab92019-12-20 01:52:54311 controller.get(), fuchsia::web::LoadUrlParams(), page_url2.spec()));
Sharon Yangfbb9ba4a2019-11-18 23:59:56312
313 semantics_manager_.semantic_tree()->RunUntilNodeCountAtLeast(kPage2NodeCount);
Sharon Yangca616422019-12-18 23:52:39314 EXPECT_TRUE(
315 semantics_manager_.semantic_tree()->HasNodeWithLabel(kPage2Title));
Sharon Yangfbb9ba4a2019-11-18 23:59:56316 EXPECT_TRUE(semantics_manager_.semantic_tree()->HasNodeWithLabel(kNodeName));
Sharon Yangca616422019-12-18 23:52:39317
318 // Check that data from the first page has been deleted successfully.
319 EXPECT_FALSE(
320 semantics_manager_.semantic_tree()->HasNodeWithLabel(kButtonName));
321 EXPECT_FALSE(
322 semantics_manager_.semantic_tree()->HasNodeWithLabel(kParagraphName));
Sharon Yangfbb9ba4a2019-11-18 23:59:56323}
Sharon Yang8b548ab92019-12-20 01:52:54324
325// Checks that the correct node ID is returned when performing hit testing.
326IN_PROC_BROWSER_TEST_F(AccessibilityBridgeTest, HitTest) {
327 fuchsia::web::NavigationControllerPtr controller;
328 frame_ptr_->GetNavigationController(controller.NewRequest());
329 ASSERT_TRUE(embedded_test_server()->Start());
330 semantics_manager_.SetSemanticsModeEnabled(true);
331
332 GURL page_url1(embedded_test_server()->GetURL(kPage1Path));
333 EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
334 controller.get(), fuchsia::web::LoadUrlParams(), page_url1.spec()));
335 navigation_listener_.RunUntilUrlAndTitleEquals(page_url1, kPage1Title);
336 semantics_manager_.semantic_tree()->RunUntilNodeCountAtLeast(kPage1NodeCount);
337 EXPECT_TRUE(
338 semantics_manager_.semantic_tree()->HasNodeWithLabel(kParagraphName));
339
340 Node* hit_test_node =
341 semantics_manager_.semantic_tree()->GetNodeFromLabel(kParagraphName);
342
343 fuchsia::math::PointF target_point =
344 GetCenterOfBox(hit_test_node->location());
345
346 EXPECT_EQ(hit_test_node->node_id(),
347 semantics_manager_.HitTestAtPointSync(std::move(target_point)));
348
349 // Expect hit testing to return the root when the point given is out of
350 // bounds or there is no semantic node at that position.
351 target_point.x = -1;
352 target_point.y = -1;
353 EXPECT_EQ(0u, semantics_manager_.HitTestAtPointSync(std::move(target_point)));
354 target_point.x = 1;
355 target_point.y = 1;
356 EXPECT_EQ(0u, semantics_manager_.HitTestAtPointSync(std::move(target_point)));
357}