• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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