506 lines
17 KiB
C++
506 lines
17 KiB
C++
// 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,
|
|
// Disabling Accessibility should disable accessibility notification changes
|
|
TEST_DISABLE,
|
|
// 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<CefAccessibilityHandler> GetAccessibilityHandler() override {
|
|
return this;
|
|
}
|
|
|
|
CefRefPtr<CefRenderHandler> GetRenderHandler() override { return this; }
|
|
|
|
// Cef Renderer Handler Methods
|
|
void GetViewRect(CefRefPtr<CefBrowser> browser, CefRect& rect) override {
|
|
rect = CefRect(0, 0, kOsrWidth, kOsrHeight);
|
|
}
|
|
|
|
bool GetScreenInfo(CefRefPtr<CefBrowser> 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<CefBrowser> browser,
|
|
CefRenderHandler::PaintElementType type,
|
|
const CefRenderHandler::RectList& dirtyRects,
|
|
const void* buffer,
|
|
int width,
|
|
int height) override {
|
|
// Do nothing.
|
|
}
|
|
|
|
void RunTest() override {
|
|
std::string html =
|
|
"<html><head><title>AccessibilityTest</title></head>"
|
|
"<body><span id='tipspan' role='tooltip' style='color:red;"
|
|
"margin:20px'>";
|
|
html += kTipText;
|
|
html +=
|
|
"</span>"
|
|
"<input id='editbox' type='text' aria-describedby='tipspan' "
|
|
"value='editbox' size='25px'/><input id='button' type='button' "
|
|
"value='button' style='margin:20px'/></body></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<CefBrowser> browser,
|
|
CefRefPtr<CefFrame> frame,
|
|
int httpStatusCode) override {
|
|
// Enable Accessibility
|
|
browser->GetHost()->SetAccessibilityState(STATE_ENABLED);
|
|
switch (test_type_) {
|
|
case TEST_ENABLE: {
|
|
// This should trigger OnAccessibilityTreeChange
|
|
// And update will be validated
|
|
} break;
|
|
case TEST_DISABLE: {
|
|
// 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<CefValue> value) override {
|
|
switch (test_type_) {
|
|
case TEST_ENABLE: {
|
|
TestEnableAccessibilityUpdate(value);
|
|
} break;
|
|
case TEST_DISABLE: {
|
|
// 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) {
|
|
CefRefPtr<CefDictionaryValue> update, event;
|
|
GetFirstUpdateAndEvent(value, update, event);
|
|
EXPECT_TRUE(update.get());
|
|
|
|
// Ignore other events.
|
|
if (!event.get() ||
|
|
event->GetString("event_type") != "layoutComplete") {
|
|
break;
|
|
}
|
|
|
|
SetEditBoxIdAndRect(update);
|
|
EXPECT_NE(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) {
|
|
CefRefPtr<CefDictionaryValue> update, event;
|
|
GetFirstUpdateAndEvent(value, update, event);
|
|
EXPECT_TRUE(update.get());
|
|
|
|
// Ignore other events.
|
|
if (!event.get() ||
|
|
event->GetString("event_type") != "layoutComplete") {
|
|
break;
|
|
}
|
|
|
|
// Now post a delayed task to trigger focus to edit box
|
|
SetEditBoxIdAndRect(update);
|
|
EXPECT_NE(edit_box_id_, -1);
|
|
|
|
CefPostDelayedTask(
|
|
TID_UI,
|
|
base::Bind(&AccessibilityTestHandler::SetFocusOnEditBox, this,
|
|
GetBrowser()),
|
|
200);
|
|
} else {
|
|
// Retrieve the "focus" event.
|
|
CefRefPtr<CefDictionaryValue> event;
|
|
if (!GetFirstMatchingEvent(value, "focus", event))
|
|
return;
|
|
EXPECT_TRUE(event.get());
|
|
|
|
// Verify that focus is set to expected element edit_box.
|
|
EXPECT_EQ(edit_box_id_, event->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<CefValue> value) override {
|
|
if (test_type_ == TEST_LOCATION_CHANGE) {
|
|
EXPECT_NE(edit_box_id_, -1);
|
|
EXPECT_TRUE(value.get());
|
|
|
|
// Change has a valid list
|
|
EXPECT_EQ(VTYPE_LIST, value->GetType());
|
|
CefRefPtr<CefListValue> list = value->GetList();
|
|
EXPECT_TRUE(list.get());
|
|
|
|
got_accessibility_location_change_.yes();
|
|
}
|
|
|
|
if (got_hide_edit_box_) {
|
|
// Now destroy the test.
|
|
CefPostTask(TID_UI,
|
|
base::Bind(&AccessibilityTestHandler::DestroyTest, this));
|
|
}
|
|
}
|
|
|
|
private:
|
|
void CreateOSRBrowser(const CefString& url) {
|
|
CefWindowInfo windowInfo;
|
|
CefBrowserSettings settings;
|
|
|
|
#if defined(OS_WIN)
|
|
windowInfo.SetAsWindowless(GetDesktopWindow());
|
|
#elif defined(OS_MAC)
|
|
windowInfo.SetAsWindowless(kNullWindowHandle);
|
|
#elif defined(OS_LINUX)
|
|
windowInfo.SetAsWindowless(kNullWindowHandle);
|
|
#else
|
|
#error "Unsupported platform"
|
|
#endif
|
|
CefBrowserHost::CreateBrowser(windowInfo, this, url, settings, nullptr,
|
|
nullptr);
|
|
}
|
|
|
|
void HideEditBox(CefRefPtr<CefBrowser> browser) {
|
|
// Hide the edit box.
|
|
// This should trigger Location update if enabled
|
|
browser->GetMainFrame()->ExecuteJavaScript(
|
|
"document.getElementById('editbox').style.display = 'none';", kTestUrl,
|
|
0);
|
|
|
|
got_hide_edit_box_.yes();
|
|
}
|
|
|
|
void SetFocusOnEditBox(CefRefPtr<CefBrowser> 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<CefBrowser> 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);
|
|
}
|
|
|
|
static CefRefPtr<CefListValue> GetUpdateList(CefRefPtr<CefValue> value) {
|
|
EXPECT_TRUE(value.get());
|
|
EXPECT_EQ(value->GetType(), VTYPE_DICTIONARY);
|
|
CefRefPtr<CefDictionaryValue> topLevel = value->GetDictionary();
|
|
EXPECT_TRUE(topLevel.get());
|
|
|
|
return topLevel->GetList("updates");
|
|
}
|
|
|
|
static size_t GetUpdateListSize(CefRefPtr<CefValue> value) {
|
|
CefRefPtr<CefListValue> updates = GetUpdateList(value);
|
|
if (updates)
|
|
return updates->GetSize();
|
|
return 0U;
|
|
}
|
|
|
|
static CefRefPtr<CefDictionaryValue> GetUpdateValue(CefRefPtr<CefValue> value,
|
|
size_t index) {
|
|
CefRefPtr<CefListValue> updates = GetUpdateList(value);
|
|
if (!updates)
|
|
return nullptr;
|
|
EXPECT_LT(index, updates->GetSize());
|
|
CefRefPtr<CefDictionaryValue> update = updates->GetDictionary(index);
|
|
EXPECT_TRUE(update);
|
|
return update;
|
|
}
|
|
|
|
static CefRefPtr<CefListValue> GetEventList(CefRefPtr<CefValue> value) {
|
|
EXPECT_TRUE(value.get());
|
|
EXPECT_EQ(value->GetType(), VTYPE_DICTIONARY);
|
|
CefRefPtr<CefDictionaryValue> topLevel = value->GetDictionary();
|
|
EXPECT_TRUE(topLevel.get());
|
|
|
|
return topLevel->GetList("events");
|
|
}
|
|
|
|
static size_t GetEventListSize(CefRefPtr<CefValue> value) {
|
|
CefRefPtr<CefListValue> events = GetEventList(value);
|
|
if (events)
|
|
return events->GetSize();
|
|
return 0U;
|
|
}
|
|
|
|
static CefRefPtr<CefDictionaryValue> GetEventValue(CefRefPtr<CefValue> value,
|
|
size_t index) {
|
|
CefRefPtr<CefListValue> events = GetEventList(value);
|
|
if (!events)
|
|
return nullptr;
|
|
EXPECT_LT(index, events->GetSize());
|
|
CefRefPtr<CefDictionaryValue> event = events->GetDictionary(index);
|
|
EXPECT_TRUE(event);
|
|
return event;
|
|
}
|
|
|
|
static void GetFirstUpdateAndEvent(CefRefPtr<CefValue> value,
|
|
CefRefPtr<CefDictionaryValue>& update,
|
|
CefRefPtr<CefDictionaryValue>& event) {
|
|
if (GetUpdateListSize(value) > 0U)
|
|
update = GetUpdateValue(value, 0U);
|
|
if (GetEventListSize(value) > 0U)
|
|
event = GetEventValue(value, 0U);
|
|
}
|
|
|
|
static bool GetFirstMatchingEvent(CefRefPtr<CefValue> value,
|
|
const std::string& event_type,
|
|
CefRefPtr<CefDictionaryValue>& event) {
|
|
// Return the first event that matches the requested |event_type|.
|
|
const size_t event_size = GetEventListSize(value);
|
|
for (size_t i = 0; i < event_size; ++i) {
|
|
CefRefPtr<CefDictionaryValue> cur_event = GetEventValue(value, i);
|
|
const std::string& cur_event_type = cur_event->GetString("event_type");
|
|
if (cur_event_type == event_type) {
|
|
event = cur_event;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void TestEnableAccessibilityUpdate(CefRefPtr<CefValue> value) {
|
|
CefRefPtr<CefDictionaryValue> update, event;
|
|
GetFirstUpdateAndEvent(value, update, event);
|
|
EXPECT_TRUE(update.get());
|
|
|
|
// Ignore other events.
|
|
if (!event.get() || event->GetString("event_type") != "layoutComplete") {
|
|
return;
|
|
}
|
|
|
|
// Get update and validate it has tree data
|
|
EXPECT_TRUE(update->GetBool("has_tree_data"));
|
|
CefRefPtr<CefDictionaryValue> 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<CefListValue> nodes = update->GetList("nodes");
|
|
EXPECT_TRUE(nodes.get());
|
|
EXPECT_GT(nodes->GetSize(), 0U);
|
|
|
|
// Update has a valid root
|
|
CefRefPtr<CefDictionaryValue> root;
|
|
for (size_t index = 0; index < nodes->GetSize(); index++) {
|
|
CefRefPtr<CefDictionaryValue> node = nodes->GetDictionary(index);
|
|
if (node->GetString("role").ToString() == "rootWebArea") {
|
|
root = node;
|
|
break;
|
|
}
|
|
}
|
|
EXPECT_TRUE(root.get());
|
|
|
|
// One div containing the tree elements.
|
|
CefRefPtr<CefListValue> childIDs = root->GetList("child_ids");
|
|
EXPECT_TRUE(childIDs.get());
|
|
EXPECT_EQ(1U, childIDs->GetSize());
|
|
|
|
// A parent Group div containing the child.
|
|
CefRefPtr<CefDictionaryValue> group;
|
|
for (size_t index = 0; index < nodes->GetSize(); index++) {
|
|
CefRefPtr<CefDictionaryValue> 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<CefListValue> parentdiv = group->GetList("child_ids");
|
|
EXPECT_TRUE(parentdiv.get());
|
|
EXPECT_EQ(3U, parentdiv->GetSize());
|
|
|
|
int tipId = parentdiv->GetInt(0);
|
|
int editBoxId = parentdiv->GetInt(1);
|
|
int buttonId = parentdiv->GetInt(2);
|
|
|
|
// A parent Group div containing the child.
|
|
CefRefPtr<CefDictionaryValue> tip, editbox, button;
|
|
for (size_t index = 0; index < nodes->GetSize(); index++) {
|
|
CefRefPtr<CefDictionaryValue> 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());
|
|
CefRefPtr<CefDictionaryValue> tipattr = tip->GetDictionary("attributes");
|
|
EXPECT_TRUE(tipattr.get());
|
|
|
|
EXPECT_TRUE(editbox.get());
|
|
EXPECT_STREQ("textField", editbox->GetString("role").ToString().c_str());
|
|
CefRefPtr<CefDictionaryValue> 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<CefDictionaryValue> value) {
|
|
EXPECT_TRUE(value.get());
|
|
// Validate node data.
|
|
CefRefPtr<CefListValue> nodes = value->GetList("nodes");
|
|
EXPECT_TRUE(nodes.get());
|
|
EXPECT_GT(nodes->GetSize(), 0U);
|
|
|
|
// Find accessibility id for the text field.
|
|
for (size_t index = 0; index < nodes->GetSize(); index++) {
|
|
CefRefPtr<CefDictionaryValue> node = nodes->GetDictionary(index);
|
|
if (node->GetString("role").ToString() == "textField") {
|
|
edit_box_id_ = node->GetInt("id");
|
|
CefRefPtr<CefDictionaryValue> loc = node->GetDictionary("location");
|
|
EXPECT_TRUE(loc.get());
|
|
EXPECT_GT(loc->GetDouble("x"), 0);
|
|
EXPECT_GT(loc->GetDouble("y"), 0);
|
|
EXPECT_GT(loc->GetDouble("width"), 0);
|
|
EXPECT_GT(loc->GetDouble("height"), 0);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void DestroyTest() override {
|
|
if (test_type_ == TEST_LOCATION_CHANGE) {
|
|
EXPECT_GT(edit_box_id_, 0);
|
|
EXPECT_TRUE(got_hide_edit_box_);
|
|
EXPECT_TRUE(got_accessibility_location_change_);
|
|
}
|
|
TestHandler::DestroyTest();
|
|
}
|
|
|
|
AccessibilityTestType test_type_;
|
|
int edit_box_id_;
|
|
bool accessibility_disabled_;
|
|
TrackCallback got_hide_edit_box_;
|
|
TrackCallback got_accessibility_location_change_;
|
|
|
|
IMPLEMENT_REFCOUNTING(AccessibilityTestHandler);
|
|
};
|
|
|
|
} // namespace
|
|
|
|
TEST(OSRTest, AccessibilityEnable) {
|
|
CefRefPtr<AccessibilityTestHandler> handler =
|
|
new AccessibilityTestHandler(TEST_ENABLE);
|
|
handler->ExecuteTest();
|
|
ReleaseAndWaitForDestructor(handler);
|
|
}
|
|
|
|
TEST(OSRTest, AccessibilityDisable) {
|
|
CefRefPtr<AccessibilityTestHandler> handler =
|
|
new AccessibilityTestHandler(TEST_DISABLE);
|
|
handler->ExecuteTest();
|
|
ReleaseAndWaitForDestructor(handler);
|
|
}
|
|
|
|
TEST(OSRTest, AccessibilityFocusChange) {
|
|
CefRefPtr<AccessibilityTestHandler> handler =
|
|
new AccessibilityTestHandler(TEST_FOCUS_CHANGE);
|
|
handler->ExecuteTest();
|
|
ReleaseAndWaitForDestructor(handler);
|
|
}
|
|
|
|
TEST(OSRTest, AccessibilityLocationChange) {
|
|
CefRefPtr<AccessibilityTestHandler> handler =
|
|
new AccessibilityTestHandler(TEST_LOCATION_CHANGE);
|
|
handler->ExecuteTest();
|
|
ReleaseAndWaitForDestructor(handler);
|
|
}
|