1 // Copyright (c) 2016 The Chromium Embedded Framework Authors. All rights
2 // reserved. Use of this source code is governed by a BSD-style license that
3 // can be found in the LICENSE file.
4
5 #include "include/base/cef_callback.h"
6 #include "include/cef_accessibility_handler.h"
7 #include "include/cef_parser.h"
8 #include "include/cef_waitable_event.h"
9 #include "include/wrapper/cef_closure_task.h"
10 #include "tests/ceftests/test_handler.h"
11 #include "tests/ceftests/test_util.h"
12 #include "tests/gtest/include/gtest/gtest.h"
13
14 namespace {
15
16 const char kTestUrl[] = "https://tests/AccessibilityTestHandler";
17 const char kTipText[] = "Also known as User ID";
18
19 // Default OSR widget size.
20 const int kOsrWidth = 600;
21 const int kOsrHeight = 400;
22
23 // Test type.
24 enum AccessibilityTestType {
25 // Enabling Accessibility should trigger the AccessibilityHandler callback
26 // with Accessibility tree details
27 TEST_ENABLE,
28 // Disabling Accessibility should disable accessibility notification changes
29 TEST_DISABLE,
30 // Focus change on element should trigger Accessibility focus event
31 TEST_FOCUS_CHANGE,
32 // Hide/Show etc should trigger Location Change callbacks
33 TEST_LOCATION_CHANGE
34 };
35
36 class AccessibilityTestHandler : public TestHandler,
37 public CefRenderHandler,
38 public CefAccessibilityHandler {
39 public:
AccessibilityTestHandler(const AccessibilityTestType & type)40 AccessibilityTestHandler(const AccessibilityTestType& type)
41 : test_type_(type), edit_box_id_(-1), accessibility_disabled_(false) {}
42
GetAccessibilityHandler()43 CefRefPtr<CefAccessibilityHandler> GetAccessibilityHandler() override {
44 return this;
45 }
46
GetRenderHandler()47 CefRefPtr<CefRenderHandler> GetRenderHandler() override { return this; }
48
49 // Cef Renderer Handler Methods
GetViewRect(CefRefPtr<CefBrowser> browser,CefRect & rect)50 void GetViewRect(CefRefPtr<CefBrowser> browser, CefRect& rect) override {
51 rect = CefRect(0, 0, kOsrWidth, kOsrHeight);
52 }
53
GetScreenInfo(CefRefPtr<CefBrowser> browser,CefScreenInfo & screen_info)54 bool GetScreenInfo(CefRefPtr<CefBrowser> browser,
55 CefScreenInfo& screen_info) override {
56 screen_info.rect = CefRect(0, 0, kOsrWidth, kOsrHeight);
57 screen_info.available_rect = screen_info.rect;
58 return true;
59 }
60
OnPaint(CefRefPtr<CefBrowser> browser,CefRenderHandler::PaintElementType type,const CefRenderHandler::RectList & dirtyRects,const void * buffer,int width,int height)61 void OnPaint(CefRefPtr<CefBrowser> browser,
62 CefRenderHandler::PaintElementType type,
63 const CefRenderHandler::RectList& dirtyRects,
64 const void* buffer,
65 int width,
66 int height) override {
67 // Do nothing.
68 }
69
RunTest()70 void RunTest() override {
71 std::string html =
72 "<html><head><title>AccessibilityTest</title></head>"
73 "<body><span id='tipspan' role='tooltip' style='color:red;"
74 "margin:20px'>";
75 html += kTipText;
76 html +=
77 "</span>"
78 "<input id='editbox' type='text' aria-describedby='tipspan' "
79 "value='editbox' size='25px'/><input id='button' type='button' "
80 "value='button' style='margin:20px'/></body></html>";
81 AddResource(kTestUrl, html, "text/html");
82
83 // Create the browser
84 CreateOSRBrowser(kTestUrl);
85
86 // Time out the test after a reasonable period of time.
87 SetTestTimeout(5000);
88 }
89
OnLoadEnd(CefRefPtr<CefBrowser> browser,CefRefPtr<CefFrame> frame,int httpStatusCode)90 void OnLoadEnd(CefRefPtr<CefBrowser> browser,
91 CefRefPtr<CefFrame> frame,
92 int httpStatusCode) override {
93 // Enable Accessibility
94 browser->GetHost()->SetAccessibilityState(STATE_ENABLED);
95 switch (test_type_) {
96 case TEST_ENABLE: {
97 // This should trigger OnAccessibilityTreeChange
98 // And update will be validated
99 } break;
100 case TEST_DISABLE: {
101 // Post a delayed task to disable Accessibility
102 CefPostDelayedTask(
103 TID_UI,
104 base::BindOnce(&AccessibilityTestHandler::DisableAccessibility,
105 this, browser),
106 200);
107 } break;
108 // Delayed task will posted later after we have initial details
109 case TEST_FOCUS_CHANGE: {
110 } break;
111 case TEST_LOCATION_CHANGE: {
112 } break;
113 }
114 }
115
OnAccessibilityTreeChange(CefRefPtr<CefValue> value)116 void OnAccessibilityTreeChange(CefRefPtr<CefValue> value) override {
117 switch (test_type_) {
118 case TEST_ENABLE: {
119 TestEnableAccessibilityUpdate(value);
120 } break;
121 case TEST_DISABLE: {
122 // Once Accessibility is disabled in the delayed Task
123 // We should not reach here
124 EXPECT_FALSE(accessibility_disabled_);
125 } break;
126 case TEST_LOCATION_CHANGE: {
127 // find accessibility id of the edit box, before setting focus
128 if (edit_box_id_ == -1) {
129 CefRefPtr<CefDictionaryValue> update, event;
130 GetFirstUpdateAndEvent(value, update, event);
131 EXPECT_TRUE(update.get());
132
133 // Ignore other events.
134 if (!event.get() ||
135 event->GetString("event_type") != "loadComplete") {
136 break;
137 }
138
139 SetEditBoxIdAndRect(update);
140 EXPECT_NE(edit_box_id_, -1);
141 // Post a delayed task to hide the span and trigger location change
142 CefPostDelayedTask(
143 TID_UI,
144 base::BindOnce(&AccessibilityTestHandler::HideEditBox, this,
145 GetBrowser()),
146 200);
147 }
148 } break;
149 case TEST_FOCUS_CHANGE: {
150 // find accessibility id of the edit box, before setting focus
151 if (edit_box_id_ == -1) {
152 CefRefPtr<CefDictionaryValue> update, event;
153 GetFirstUpdateAndEvent(value, update, event);
154 EXPECT_TRUE(update.get());
155
156 // Ignore other events.
157 if (!event.get() ||
158 event->GetString("event_type") != "loadComplete") {
159 break;
160 }
161
162 // Now post a delayed task to trigger focus to edit box
163 SetEditBoxIdAndRect(update);
164 EXPECT_NE(edit_box_id_, -1);
165
166 CefPostDelayedTask(
167 TID_UI,
168 base::BindOnce(&AccessibilityTestHandler::SetFocusOnEditBox, this,
169 GetBrowser()),
170 200);
171 } else {
172 // Retrieve the "focus" event.
173 CefRefPtr<CefDictionaryValue> event;
174 if (!GetFirstMatchingEvent(value, "focus", event))
175 return;
176 EXPECT_TRUE(event.get());
177
178 // Verify that focus is set to expected element edit_box.
179 EXPECT_EQ(edit_box_id_, event->GetInt("id"));
180
181 // Now Post a delayed task to destroy the test giving
182 // sufficient time for any accessibility updates to come through
183 CefPostDelayedTask(
184 TID_UI,
185 base::BindOnce(&AccessibilityTestHandler::DestroyTest, this),
186 500);
187 }
188 } break;
189 }
190 }
191
OnAccessibilityLocationChange(CefRefPtr<CefValue> value)192 void OnAccessibilityLocationChange(CefRefPtr<CefValue> value) override {
193 if (test_type_ == TEST_LOCATION_CHANGE) {
194 EXPECT_NE(edit_box_id_, -1);
195 EXPECT_TRUE(value.get());
196
197 // Change has a valid list
198 EXPECT_EQ(VTYPE_LIST, value->GetType());
199 CefRefPtr<CefListValue> list = value->GetList();
200 EXPECT_TRUE(list.get());
201
202 got_accessibility_location_change_.yes();
203 }
204
205 if (got_hide_edit_box_) {
206 // Now destroy the test.
207 CefPostTask(TID_UI,
208 base::BindOnce(&AccessibilityTestHandler::DestroyTest, this));
209 }
210 }
211
212 private:
CreateOSRBrowser(const CefString & url)213 void CreateOSRBrowser(const CefString& url) {
214 CefWindowInfo windowInfo;
215 CefBrowserSettings settings;
216
217 #if defined(OS_WIN)
218 windowInfo.SetAsWindowless(GetDesktopWindow());
219 #elif defined(OS_MAC)
220 windowInfo.SetAsWindowless(kNullWindowHandle);
221 #elif defined(OS_LINUX)
222 windowInfo.SetAsWindowless(kNullWindowHandle);
223 #else
224 #error "Unsupported platform"
225 #endif
226 CefBrowserHost::CreateBrowser(windowInfo, this, url, settings, nullptr,
227 nullptr);
228 }
229
HideEditBox(CefRefPtr<CefBrowser> browser)230 void HideEditBox(CefRefPtr<CefBrowser> browser) {
231 // Hide the edit box.
232 // This should trigger Location update if enabled
233 browser->GetMainFrame()->ExecuteJavaScript(
234 "document.getElementById('editbox').style.display = 'none';", kTestUrl,
235 0);
236
237 got_hide_edit_box_.yes();
238 }
239
SetFocusOnEditBox(CefRefPtr<CefBrowser> browser)240 void SetFocusOnEditBox(CefRefPtr<CefBrowser> browser) {
241 // Set focus on edit box
242 // This should trigger accessibility update if enabled
243 browser->GetMainFrame()->ExecuteJavaScript(
244 "document.getElementById('editbox').focus();", kTestUrl, 0);
245 }
246
DisableAccessibility(CefRefPtr<CefBrowser> browser)247 void DisableAccessibility(CefRefPtr<CefBrowser> browser) {
248 browser->GetHost()->SetAccessibilityState(STATE_DISABLED);
249 accessibility_disabled_ = true;
250 // Set focus on edit box
251 SetFocusOnEditBox(browser);
252
253 // Now Post a delayed task to destroy the test
254 // giving sufficient time for any accessibility updates to come through
255 CefPostDelayedTask(
256 TID_UI, base::BindOnce(&AccessibilityTestHandler::DestroyTest, this),
257 500);
258 }
259
GetUpdateList(CefRefPtr<CefValue> value)260 static CefRefPtr<CefListValue> GetUpdateList(CefRefPtr<CefValue> value) {
261 EXPECT_TRUE(value.get());
262 EXPECT_EQ(value->GetType(), VTYPE_DICTIONARY);
263 CefRefPtr<CefDictionaryValue> topLevel = value->GetDictionary();
264 EXPECT_TRUE(topLevel.get());
265
266 return topLevel->GetList("updates");
267 }
268
GetUpdateListSize(CefRefPtr<CefValue> value)269 static size_t GetUpdateListSize(CefRefPtr<CefValue> value) {
270 CefRefPtr<CefListValue> updates = GetUpdateList(value);
271 if (updates)
272 return updates->GetSize();
273 return 0U;
274 }
275
GetUpdateValue(CefRefPtr<CefValue> value,size_t index)276 static CefRefPtr<CefDictionaryValue> GetUpdateValue(CefRefPtr<CefValue> value,
277 size_t index) {
278 CefRefPtr<CefListValue> updates = GetUpdateList(value);
279 if (!updates)
280 return nullptr;
281 EXPECT_LT(index, updates->GetSize());
282 CefRefPtr<CefDictionaryValue> update = updates->GetDictionary(index);
283 EXPECT_TRUE(update);
284 return update;
285 }
286
GetEventList(CefRefPtr<CefValue> value)287 static CefRefPtr<CefListValue> GetEventList(CefRefPtr<CefValue> value) {
288 EXPECT_TRUE(value.get());
289 EXPECT_EQ(value->GetType(), VTYPE_DICTIONARY);
290 CefRefPtr<CefDictionaryValue> topLevel = value->GetDictionary();
291 EXPECT_TRUE(topLevel.get());
292
293 return topLevel->GetList("events");
294 }
295
GetEventListSize(CefRefPtr<CefValue> value)296 static size_t GetEventListSize(CefRefPtr<CefValue> value) {
297 CefRefPtr<CefListValue> events = GetEventList(value);
298 if (events)
299 return events->GetSize();
300 return 0U;
301 }
302
GetEventValue(CefRefPtr<CefValue> value,size_t index)303 static CefRefPtr<CefDictionaryValue> GetEventValue(CefRefPtr<CefValue> value,
304 size_t index) {
305 CefRefPtr<CefListValue> events = GetEventList(value);
306 if (!events)
307 return nullptr;
308 EXPECT_LT(index, events->GetSize());
309 CefRefPtr<CefDictionaryValue> event = events->GetDictionary(index);
310 EXPECT_TRUE(event);
311 return event;
312 }
313
GetFirstUpdateAndEvent(CefRefPtr<CefValue> value,CefRefPtr<CefDictionaryValue> & update,CefRefPtr<CefDictionaryValue> & event)314 static void GetFirstUpdateAndEvent(CefRefPtr<CefValue> value,
315 CefRefPtr<CefDictionaryValue>& update,
316 CefRefPtr<CefDictionaryValue>& event) {
317 if (GetUpdateListSize(value) > 0U)
318 update = GetUpdateValue(value, 0U);
319 if (GetEventListSize(value) > 0U)
320 event = GetEventValue(value, 0U);
321 }
322
GetFirstMatchingEvent(CefRefPtr<CefValue> value,const std::string & event_type,CefRefPtr<CefDictionaryValue> & event)323 static bool GetFirstMatchingEvent(CefRefPtr<CefValue> value,
324 const std::string& event_type,
325 CefRefPtr<CefDictionaryValue>& event) {
326 // Return the first event that matches the requested |event_type|.
327 const size_t event_size = GetEventListSize(value);
328 for (size_t i = 0; i < event_size; ++i) {
329 CefRefPtr<CefDictionaryValue> cur_event = GetEventValue(value, i);
330 const std::string& cur_event_type = cur_event->GetString("event_type");
331 if (cur_event_type == event_type) {
332 event = cur_event;
333 return true;
334 }
335 }
336 return false;
337 }
338
TestEnableAccessibilityUpdate(CefRefPtr<CefValue> value)339 void TestEnableAccessibilityUpdate(CefRefPtr<CefValue> value) {
340 CefRefPtr<CefDictionaryValue> update, event;
341 GetFirstUpdateAndEvent(value, update, event);
342 EXPECT_TRUE(update.get());
343
344 // Ignore other events.
345 if (!event.get() || event->GetString("event_type") != "loadComplete") {
346 return;
347 }
348
349 // Get update and validate it has tree data
350 EXPECT_TRUE(update->GetBool("has_tree_data"));
351 CefRefPtr<CefDictionaryValue> treeData = update->GetDictionary("tree_data");
352
353 // Validate title and Url
354 EXPECT_STREQ("AccessibilityTest",
355 treeData->GetString("title").ToString().c_str());
356 EXPECT_STREQ(kTestUrl, treeData->GetString("url").ToString().c_str());
357
358 // Validate node data
359 CefRefPtr<CefListValue> nodes = update->GetList("nodes");
360 EXPECT_TRUE(nodes.get());
361 EXPECT_GT(nodes->GetSize(), 0U);
362
363 // Update has a valid root
364 CefRefPtr<CefDictionaryValue> root;
365 for (size_t index = 0; index < nodes->GetSize(); index++) {
366 CefRefPtr<CefDictionaryValue> node = nodes->GetDictionary(index);
367 if (node->GetString("role").ToString() == "rootWebArea") {
368 root = node;
369 break;
370 }
371 }
372 EXPECT_TRUE(root.get());
373
374 // One div containing the tree elements.
375 CefRefPtr<CefListValue> childIDs = root->GetList("child_ids");
376 EXPECT_TRUE(childIDs.get());
377 EXPECT_EQ(1U, childIDs->GetSize());
378
379 // Now Post a delayed task to destroy the test
380 // giving sufficient time for any accessibility updates to come through
381 CefPostDelayedTask(
382 TID_UI, base::BindOnce(&AccessibilityTestHandler::DestroyTest, this),
383 500);
384 }
385
386 // Find Edit box Id in accessibility tree.
SetEditBoxIdAndRect(CefRefPtr<CefDictionaryValue> value)387 void SetEditBoxIdAndRect(CefRefPtr<CefDictionaryValue> value) {
388 EXPECT_TRUE(value.get());
389 // Validate node data.
390 CefRefPtr<CefListValue> nodes = value->GetList("nodes");
391 EXPECT_TRUE(nodes.get());
392 EXPECT_GT(nodes->GetSize(), 0U);
393
394 // Find accessibility id for the text field.
395 for (size_t index = 0; index < nodes->GetSize(); index++) {
396 CefRefPtr<CefDictionaryValue> node = nodes->GetDictionary(index);
397 if (node->GetString("role").ToString() == "textField") {
398 edit_box_id_ = node->GetInt("id");
399 CefRefPtr<CefDictionaryValue> loc = node->GetDictionary("location");
400 EXPECT_TRUE(loc.get());
401 EXPECT_GT(loc->GetDouble("x"), 0);
402 EXPECT_GT(loc->GetDouble("y"), 0);
403 EXPECT_GT(loc->GetDouble("width"), 0);
404 EXPECT_GT(loc->GetDouble("height"), 0);
405 break;
406 }
407 }
408 }
409
DestroyTest()410 void DestroyTest() override {
411 if (test_type_ == TEST_LOCATION_CHANGE) {
412 EXPECT_GT(edit_box_id_, 0);
413 EXPECT_TRUE(got_hide_edit_box_);
414 EXPECT_TRUE(got_accessibility_location_change_);
415 }
416 TestHandler::DestroyTest();
417 }
418
419 AccessibilityTestType test_type_;
420 int edit_box_id_;
421 bool accessibility_disabled_;
422 TrackCallback got_hide_edit_box_;
423 TrackCallback got_accessibility_location_change_;
424
425 IMPLEMENT_REFCOUNTING(AccessibilityTestHandler);
426 };
427
428 } // namespace
429
TEST(OSRTest,AccessibilityEnable)430 TEST(OSRTest, AccessibilityEnable) {
431 CefRefPtr<AccessibilityTestHandler> handler =
432 new AccessibilityTestHandler(TEST_ENABLE);
433 handler->ExecuteTest();
434 ReleaseAndWaitForDestructor(handler);
435 }
436
TEST(OSRTest,AccessibilityDisable)437 TEST(OSRTest, AccessibilityDisable) {
438 CefRefPtr<AccessibilityTestHandler> handler =
439 new AccessibilityTestHandler(TEST_DISABLE);
440 handler->ExecuteTest();
441 ReleaseAndWaitForDestructor(handler);
442 }
443
TEST(OSRTest,AccessibilityFocusChange)444 TEST(OSRTest, AccessibilityFocusChange) {
445 CefRefPtr<AccessibilityTestHandler> handler =
446 new AccessibilityTestHandler(TEST_FOCUS_CHANGE);
447 handler->ExecuteTest();
448 ReleaseAndWaitForDestructor(handler);
449 }
450
TEST(OSRTest,AccessibilityLocationChange)451 TEST(OSRTest, AccessibilityLocationChange) {
452 CefRefPtr<AccessibilityTestHandler> handler =
453 new AccessibilityTestHandler(TEST_LOCATION_CHANGE);
454 handler->ExecuteTest();
455 ReleaseAndWaitForDestructor(handler);
456 }
457