// Copyright (c) 2016 The Chromium Embedded Framework 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 "include/base/cef_bind.h" #include "include/cef_accessibility_handler.h" #include "include/cef_parser.h" #include "include/cef_waitable_event.h" #include "include/wrapper/cef_closure_task.h" #include "tests/ceftests/test_handler.h" #include "tests/ceftests/test_util.h" #include "tests/gtest/include/gtest/gtest.h" namespace { const char kTestUrl[] = "https://tests/AccessibilityTestHandler"; const char kTipText[] = "Also known as User ID"; // default osr widget size const int kOsrWidth = 600; const int kOsrHeight = 400; // test type enum AccessibilityTestType { // Enabling Accessibility should trigger the AccessibilityHandler callback // with Accessibility tree details TEST_ENABLE_ACCESSIBILITY, // Disabling Accessibility should disable accessibility notification changes TEST_DISABLE_ACCESSIBILITY, // Focus change on element should trigger Accessibility focus event TEST_FOCUS_CHANGE, // Hide/Show etc should trigger Location Change callbacks TEST_LOCATION_CHANGE }; class AccessibilityTestHandler : public TestHandler, public CefRenderHandler, public CefAccessibilityHandler { public: AccessibilityTestHandler(const AccessibilityTestType& type) : test_type_(type), edit_box_id_(-1), accessibility_disabled_(false) {} CefRefPtr GetAccessibilityHandler() override { return this; } CefRefPtr GetRenderHandler() OVERRIDE { return this; } // Cef Renderer Handler Methods bool GetViewRect(CefRefPtr browser, CefRect& rect) OVERRIDE { rect = CefRect(0, 0, kOsrWidth, kOsrHeight); return true; } bool GetScreenInfo(CefRefPtr browser, CefScreenInfo& screen_info) override { screen_info.rect = CefRect(0, 0, kOsrWidth, kOsrHeight); screen_info.available_rect = screen_info.rect; return true; } void OnPaint(CefRefPtr browser, CefRenderHandler::PaintElementType type, const CefRenderHandler::RectList& dirtyRects, const void* buffer, int width, int height) OVERRIDE { // Do nothing. } // OSRTestHandler functions void CreateOSRBrowser(const CefString& url) { CefWindowInfo windowInfo; CefBrowserSettings settings; #if defined(OS_WIN) windowInfo.SetAsWindowless(GetDesktopWindow()); #elif defined(OS_MACOSX) windowInfo.SetAsWindowless(kNullWindowHandle); #elif defined(OS_LINUX) windowInfo.SetAsWindowless(kNullWindowHandle); #else #error "Unsupported platform" #endif CefBrowserHost::CreateBrowser(windowInfo, this, url, settings, NULL); } void RunTest() override { std::string html = "AccessibilityTest" ""; html += kTipText; html += "" ""; AddResource(kTestUrl, html, "text/html"); // Create the browser CreateOSRBrowser(kTestUrl); // Time out the test after a reasonable period of time. SetTestTimeout(5000); } void OnLoadEnd(CefRefPtr browser, CefRefPtr frame, int httpStatusCode) override { // Enable Accessibility browser->GetHost()->SetAccessibilityState(STATE_ENABLED); switch (test_type_) { case TEST_ENABLE_ACCESSIBILITY: { // This should trigger OnAccessibilityTreeChange // And update will be validated } break; case TEST_DISABLE_ACCESSIBILITY: { // Post a delayed task to disable Accessibility CefPostDelayedTask( TID_UI, base::Bind(&AccessibilityTestHandler::DisableAccessibility, this, browser), 200); } break; // Delayed task will posted later after we have initial details case TEST_FOCUS_CHANGE: { } break; case TEST_LOCATION_CHANGE: { } break; } } void OnAccessibilityTreeChange(CefRefPtr value) OVERRIDE { switch (test_type_) { case TEST_ENABLE_ACCESSIBILITY: { TestEnableAccessibilityUpdate(value); } break; case TEST_DISABLE_ACCESSIBILITY: { // Once Accessibility is disabled in the delayed Task // We should not reach here EXPECT_FALSE(accessibility_disabled_); } break; case TEST_LOCATION_CHANGE: { // find accessibility id of the edit box, before setting focus if (edit_box_id_ == -1) { EXPECT_TRUE(value.get()); CefRefPtr list = value->GetList(); EXPECT_TRUE(list.get()); EXPECT_GT(list->GetSize(), (size_t)0); // Get the first update dict and validate event type CefRefPtr dict = list->GetDictionary(0); // ignore other events if (dict->GetString("event_type").ToString() != "layoutComplete") break; SetEditBoxIdAndRect(dict->GetDictionary("update")); EXPECT_TRUE(edit_box_id_ != -1); // Post a delayed task to hide the span and trigger location change CefPostDelayedTask(TID_UI, base::Bind(&AccessibilityTestHandler::HideEditBox, this, GetBrowser()), 200); } } break; case TEST_FOCUS_CHANGE: { // find accessibility id of the edit box, before setting focus if (edit_box_id_ == -1) { EXPECT_TRUE(value.get()); CefRefPtr list = value->GetList(); EXPECT_TRUE(list.get()); EXPECT_GT(list->GetSize(), (size_t)0); // Get the first update dict and validate event type CefRefPtr dict = list->GetDictionary(0); // ignore other events if (dict->GetString("event_type").ToString() != "layoutComplete") break; // Now post a delayed task to trigger focus to edit box SetEditBoxIdAndRect(dict->GetDictionary("update")); EXPECT_TRUE(edit_box_id_ != -1); CefPostDelayedTask( TID_UI, base::Bind(&AccessibilityTestHandler::SetFocusOnEditBox, this, GetBrowser()), 200); } else { EXPECT_TRUE(value.get()); // Change has a valid non empty list EXPECT_TRUE(value->GetType() == VTYPE_LIST); CefRefPtr list = value->GetList(); EXPECT_TRUE(list.get()); EXPECT_GT(list->GetSize(), (size_t)0); // Get the first update dict and validate event type CefRefPtr dict = list->GetDictionary(0); // Validate Event type is Focus change EXPECT_STREQ("focus", dict->GetString("event_type").ToString().c_str()); // And Focus is set to expected element edit_box EXPECT_TRUE(edit_box_id_ == dict->GetInt("id")); // Now Post a delayed task to destroy the test giving // sufficient time for any accessibility updates to come through CefPostDelayedTask( TID_UI, base::Bind(&AccessibilityTestHandler::DestroyTest, this), 500); } } break; } } void OnAccessibilityLocationChange(CefRefPtr value) OVERRIDE { if (test_type_ == TEST_LOCATION_CHANGE) { EXPECT_TRUE(edit_box_id_ != -1); EXPECT_TRUE(value.get()); // Change has a valid list EXPECT_TRUE(value->GetType() == VTYPE_LIST); CefRefPtr list = value->GetList(); EXPECT_TRUE(list.get()); // Ignore empty events if (!list->GetSize()) return; // Hiding edit box should only change position of subsequent button EXPECT_EQ(list->GetSize(), (size_t)1); CefRefPtr dict = list->GetDictionary(0); EXPECT_TRUE(dict.get()); // New location of button should be same as older location of edit box // as that is hidden now CefRefPtr location = dict->GetDictionary("new_location"); EXPECT_TRUE(location.get()); CefRefPtr bounds = location->GetDictionary("bounds"); EXPECT_TRUE(bounds.get()); EXPECT_EQ(bounds->GetInt("x"), edit_box_rect_.x); EXPECT_EQ(bounds->GetInt("y"), edit_box_rect_.y); EXPECT_EQ(bounds->GetInt("width"), edit_box_rect_.width); EXPECT_EQ(bounds->GetInt("height"), edit_box_rect_.height); // Now Post a delayed task to destroy the test // giving sufficient time for any accessibility updates to come through CefPostDelayedTask( TID_UI, base::Bind(&AccessibilityTestHandler::DestroyTest, this), 500); } } private: void HideEditBox(CefRefPtr browser) { // Set focus on edit box // This should trigger Location update if enabled browser->GetMainFrame()->ExecuteJavaScript( "document.getElementById('editbox').style.display = 'none';", kTestUrl, 0); } void SetFocusOnEditBox(CefRefPtr browser) { // Set focus on edit box // This should trigger accessibility update if enabled browser->GetMainFrame()->ExecuteJavaScript( "document.getElementById('editbox').focus();", kTestUrl, 0); } void DisableAccessibility(CefRefPtr browser) { browser->GetHost()->SetAccessibilityState(STATE_DISABLED); accessibility_disabled_ = true; // Set focus on edit box SetFocusOnEditBox(browser); // Now Post a delayed task to destroy the test // giving sufficient time for any accessibility updates to come through CefPostDelayedTask( TID_UI, base::Bind(&AccessibilityTestHandler::DestroyTest, this), 500); } void TestEnableAccessibilityUpdate(CefRefPtr value) { // Validate enabling accessibility change returns valid accessibility tree. // Change has a valid non empty list. EXPECT_TRUE(value->GetType() == VTYPE_LIST); CefRefPtr list = value->GetList(); EXPECT_TRUE(list.get()); EXPECT_GT(list->GetSize(), (size_t)0); // Get the first update dict and validate event type. CefRefPtr dict = list->GetDictionary(0); EXPECT_STREQ("layoutComplete", dict->GetString("event_type").ToString().c_str()); // Get update and validate it has tree data CefRefPtr update = dict->GetDictionary("update"); EXPECT_TRUE(update.get()); EXPECT_TRUE(update->GetBool("has_tree_data")); CefRefPtr treeData = update->GetDictionary("tree_data"); // Validate title and Url EXPECT_STREQ("AccessibilityTest", treeData->GetString("title").ToString().c_str()); EXPECT_STREQ(kTestUrl, treeData->GetString("url").ToString().c_str()); // Validate node data CefRefPtr nodes = update->GetList("nodes"); EXPECT_TRUE(nodes.get()); EXPECT_GT(nodes->GetSize(), (size_t)0); // Update has a valid root CefRefPtr root; for (size_t index = 0; index < nodes->GetSize(); index++) { CefRefPtr node = nodes->GetDictionary(index); if (node->GetString("role").ToString() == "rootWebArea") { root = node; break; } } EXPECT_TRUE(root.get()); // One div containing the tree elements. CefRefPtr childIDs = root->GetList("child_ids"); EXPECT_TRUE(childIDs.get()); EXPECT_EQ(childIDs->GetSize(), (size_t)1); // A parent Group div containing the child. CefRefPtr group; for (size_t index = 0; index < nodes->GetSize(); index++) { CefRefPtr node = nodes->GetDictionary(index); if (node->GetString("role").ToString() == "genericContainer") { group = node; break; } } EXPECT_TRUE(group.get()); // Validate Group is child of root WebArea. EXPECT_EQ(group->GetInt("id"), childIDs->GetInt(0)); CefRefPtr parentdiv = group->GetList("child_ids"); EXPECT_TRUE(parentdiv.get()); EXPECT_EQ(parentdiv->GetSize(), (size_t)3); int tipId = parentdiv->GetInt(0); int editBoxId = parentdiv->GetInt(1); int buttonId = parentdiv->GetInt(2); // A parent Group div containing the child. CefRefPtr tip, editbox, button; for (size_t index = 0; index < nodes->GetSize(); index++) { CefRefPtr node = nodes->GetDictionary(index); if (node->GetInt("id") == tipId) { tip = node; } if (node->GetInt("id") == editBoxId) { editbox = node; } if (node->GetInt("id") == buttonId) { button = node; } } EXPECT_TRUE(tip.get()); EXPECT_STREQ("tooltip", tip->GetString("role").ToString().c_str()); // Validate tooltip color property is Red. CefRefPtr tipattr = tip->GetDictionary("attributes"); EXPECT_TRUE(tipattr.get()); EXPECT_STREQ("0xFFFF0000", tipattr->GetString("color").ToString().c_str()); EXPECT_TRUE(editbox.get()); EXPECT_STREQ("textField", editbox->GetString("role").ToString().c_str()); CefRefPtr editattr = editbox->GetDictionary("attributes"); // Validate ARIA Description tags for tipIdare associated with editbox. EXPECT_TRUE(editattr.get()); EXPECT_EQ(tipId, editattr->GetList("describedbyIds")->GetInt(0)); EXPECT_STREQ(kTipText, editattr->GetString("description").ToString().c_str()); EXPECT_TRUE(button.get()); EXPECT_STREQ("button", button->GetString("role").ToString().c_str()); // Now Post a delayed task to destroy the test // giving sufficient time for any accessibility updates to come through CefPostDelayedTask( TID_UI, base::Bind(&AccessibilityTestHandler::DestroyTest, this), 500); } // Find Edit box Id in accessibility tree. void SetEditBoxIdAndRect(CefRefPtr value) { EXPECT_TRUE(value.get()); // Validate node data. CefRefPtr nodes = value->GetList("nodes"); EXPECT_TRUE(nodes.get()); EXPECT_GT(nodes->GetSize(), (size_t)0); // Find accessibility id for the text field. for (size_t index = 0; index < nodes->GetSize(); index++) { CefRefPtr node = nodes->GetDictionary(index); if (node->GetString("role").ToString() == "textField") { edit_box_id_ = node->GetInt("id"); CefRefPtr loc = node->GetDictionary("location"); EXPECT_TRUE(loc.get()); edit_box_rect_.x = loc->GetInt("x"); edit_box_rect_.y = loc->GetInt("y"); edit_box_rect_.width = loc->GetInt("width"); edit_box_rect_.height = loc->GetInt("height"); break; } } } AccessibilityTestType test_type_; int edit_box_id_; CefRect edit_box_rect_; bool accessibility_disabled_; IMPLEMENT_REFCOUNTING(AccessibilityTestHandler); }; } // namespace TEST(AccessibilityTest, EnableAccessibility) { CefRefPtr handler = new AccessibilityTestHandler(TEST_ENABLE_ACCESSIBILITY); handler->ExecuteTest(); EXPECT_TRUE(true); ReleaseAndWaitForDestructor(handler); } TEST(AccessibilityTest, DisableAccessibility) { CefRefPtr handler = new AccessibilityTestHandler(TEST_DISABLE_ACCESSIBILITY); handler->ExecuteTest(); EXPECT_TRUE(true); ReleaseAndWaitForDestructor(handler); } TEST(AccessibilityTest, FocusChange) { CefRefPtr handler = new AccessibilityTestHandler(TEST_FOCUS_CHANGE); handler->ExecuteTest(); EXPECT_TRUE(true); ReleaseAndWaitForDestructor(handler); } TEST(AccessibilityTest, LocationChange) { CefRefPtr handler = new AccessibilityTestHandler(TEST_LOCATION_CHANGE); handler->ExecuteTest(); EXPECT_TRUE(true); ReleaseAndWaitForDestructor(handler); }