From 406867f293042006e9f5f1a1e5201db012e50adf Mon Sep 17 00:00:00 2001 From: Nishant Kaushik Date: Fri, 12 May 2017 18:28:25 +0000 Subject: [PATCH] Implement accessibility enhancements (issue #1217) - Add new CefBrowserHost::SetAccessibilityState method for toggling accessibility state when readers are detected by the client. - Add new CefAccessibilityHandler interface for the delivery of accessibility notifications to windowless (OSR) clients. - Fix delivery of CefFocusHandler callbacks to windowless clients. - cefclient: Add example windowless accessibility implementation on Windows and macOS. - cefclient: Automatically detect screen readers on Windows and macOS. --- BUILD.gn | 9 +- cef_paths.gypi | 6 + cef_paths2.gypi | 11 + include/capi/cef_accessibility_handler_capi.h | 79 ++ include/capi/cef_browser_capi.h | 28 + include/capi/cef_render_handler_capi.h | 9 + include/cef_accessibility_handler.h | 66 ++ include/cef_browser.h | 31 +- include/cef_render_handler.h | 10 + libcef/browser/browser_host_impl.cc | 56 ++ libcef/browser/browser_host_impl.h | 8 + libcef/browser/browser_platform_delegate.cc | 10 + libcef/browser/browser_platform_delegate.h | 4 + .../osr/browser_platform_delegate_osr.cc | 29 + .../osr/browser_platform_delegate_osr.h | 6 + libcef/browser/osr/osr_accessibility_util.cc | 457 ++++++++++++ libcef/browser/osr/osr_accessibility_util.h | 29 + libcef/browser/osr/web_contents_view_osr.cc | 14 + libcef/browser/osr/web_contents_view_osr.h | 2 + .../cpptoc/accessibility_handler_cpptoc.cc | 81 +++ .../cpptoc/accessibility_handler_cpptoc.h | 34 + libcef_dll/cpptoc/browser_host_cpptoc.cc | 14 + libcef_dll/cpptoc/render_handler_cpptoc.cc | 19 + .../ctocpp/accessibility_handler_ctocpp.cc | 75 ++ .../ctocpp/accessibility_handler_ctocpp.h | 38 + libcef_dll/ctocpp/browser_host_ctocpp.cc | 13 + libcef_dll/ctocpp/browser_host_ctocpp.h | 1 + libcef_dll/ctocpp/render_handler_ctocpp.cc | 17 + libcef_dll/ctocpp/render_handler_ctocpp.h | 1 + libcef_dll/libcef_dll.cc | 3 + libcef_dll/wrapper/libcef_dll_wrapper.cc | 3 + libcef_dll/wrapper_types.h | 1 + .../browser/browser_window_osr_gtk.cc | 4 + .../browser/browser_window_osr_gtk.h | 1 + .../browser/browser_window_osr_mac.h | 2 + .../browser/browser_window_osr_mac.mm | 77 +- tests/cefclient/browser/client_handler_osr.cc | 7 + tests/cefclient/browser/client_handler_osr.h | 10 + .../browser/osr_accessibility_helper.cc | 145 ++++ .../browser/osr_accessibility_helper.h | 63 ++ .../browser/osr_accessibility_node.cc | 102 +++ .../browser/osr_accessibility_node.h | 116 +++ .../browser/osr_accessibility_node_mac.mm | 498 +++++++++++++ .../browser/osr_accessibility_node_win.cc | 674 ++++++++++++++++++ tests/cefclient/browser/osr_window_win.cc | 44 +- tests/cefclient/browser/osr_window_win.h | 13 +- tests/cefclient/browser/root_window_mac.mm | 13 + tests/cefclient/browser/root_window_win.cc | 12 + tests/cefclient/cefclient_mac.mm | 29 + tests/ceftests/accessibility_unittest.cc | 464 ++++++++++++ tests/ceftests/os_rendering_unittest.cc | 46 ++ 51 files changed, 3476 insertions(+), 8 deletions(-) create mode 100644 include/capi/cef_accessibility_handler_capi.h create mode 100644 include/cef_accessibility_handler.h create mode 100644 libcef/browser/osr/osr_accessibility_util.cc create mode 100644 libcef/browser/osr/osr_accessibility_util.h create mode 100644 libcef_dll/cpptoc/accessibility_handler_cpptoc.cc create mode 100644 libcef_dll/cpptoc/accessibility_handler_cpptoc.h create mode 100644 libcef_dll/ctocpp/accessibility_handler_ctocpp.cc create mode 100644 libcef_dll/ctocpp/accessibility_handler_ctocpp.h create mode 100644 tests/cefclient/browser/osr_accessibility_helper.cc create mode 100644 tests/cefclient/browser/osr_accessibility_helper.h create mode 100644 tests/cefclient/browser/osr_accessibility_node.cc create mode 100644 tests/cefclient/browser/osr_accessibility_node.h create mode 100644 tests/cefclient/browser/osr_accessibility_node_mac.mm create mode 100644 tests/cefclient/browser/osr_accessibility_node_win.cc create mode 100644 tests/ceftests/accessibility_unittest.cc diff --git a/BUILD.gn b/BUILD.gn index e571d4add..dbff79f1e 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -380,6 +380,8 @@ static_library("libcef_static") { "libcef/browser/origin_whitelist_impl.h", "libcef/browser/osr/browser_platform_delegate_osr.cc", "libcef/browser/osr/browser_platform_delegate_osr.h", + "libcef/browser/osr/osr_accessibility_util.cc", + "libcef/browser/osr/osr_accessibility_util.h", "libcef/browser/osr/osr_util.cc", "libcef/browser/osr/osr_util.h", "libcef/browser/osr/render_widget_host_view_osr.cc", @@ -1738,11 +1740,12 @@ if (is_mac) { libs = [ "comctl32.lib", - "shlwapi.lib", - "rpcrt4.lib", - "opengl32.lib", "glu32.lib", "imm32.lib", + "oleacc.lib", + "opengl32.lib", + "rpcrt4.lib", + "shlwapi.lib", ] } diff --git a/cef_paths.gypi b/cef_paths.gypi index 31a4b99f3..79360db03 100644 --- a/cef_paths.gypi +++ b/cef_paths.gypi @@ -12,6 +12,7 @@ { 'variables': { 'autogen_cpp_includes': [ + 'include/cef_accessibility_handler.h', 'include/cef_app.h', 'include/cef_auth_callback.h', 'include/cef_browser.h', @@ -100,6 +101,7 @@ 'include/views/cef_window_delegate.h', ], 'autogen_capi_includes': [ + 'include/capi/cef_accessibility_handler_capi.h', 'include/capi/cef_app_capi.h', 'include/capi/cef_auth_callback_capi.h', 'include/capi/cef_browser_capi.h', @@ -188,6 +190,8 @@ 'include/capi/views/cef_window_delegate_capi.h', ], 'autogen_library_side': [ + 'libcef_dll/ctocpp/accessibility_handler_ctocpp.cc', + 'libcef_dll/ctocpp/accessibility_handler_ctocpp.h', 'libcef_dll/ctocpp/app_ctocpp.cc', 'libcef_dll/ctocpp/app_ctocpp.h', 'libcef_dll/cpptoc/auth_callback_cpptoc.cc', @@ -460,6 +464,8 @@ 'libcef_dll/cpptoc/zip_reader_cpptoc.h', ], 'autogen_client_side': [ + 'libcef_dll/cpptoc/accessibility_handler_cpptoc.cc', + 'libcef_dll/cpptoc/accessibility_handler_cpptoc.h', 'libcef_dll/cpptoc/app_cpptoc.cc', 'libcef_dll/cpptoc/app_cpptoc.h', 'libcef_dll/ctocpp/auth_callback_ctocpp.cc', diff --git a/cef_paths2.gypi b/cef_paths2.gypi index c6ea90713..c51ca9eab 100644 --- a/cef_paths2.gypi +++ b/cef_paths2.gypi @@ -286,6 +286,11 @@ 'tests/cefclient/browser/main_context_impl_win.cc', 'tests/cefclient/browser/main_message_loop_multithreaded_win.cc', 'tests/cefclient/browser/main_message_loop_multithreaded_win.h', + 'tests/cefclient/browser/osr_accessibility_helper.cc', + 'tests/cefclient/browser/osr_accessibility_helper.h', + 'tests/cefclient/browser/osr_accessibility_node.cc', + 'tests/cefclient/browser/osr_accessibility_node.h', + 'tests/cefclient/browser/osr_accessibility_node_win.cc', 'tests/cefclient/browser/osr_dragdrop_win.cc', 'tests/cefclient/browser/osr_dragdrop_win.h', 'tests/cefclient/browser/osr_ime_handler_win.cc', @@ -321,6 +326,11 @@ 'tests/cefclient/browser/browser_window_std_mac.h', 'tests/cefclient/browser/browser_window_std_mac.mm', 'tests/cefclient/browser/main_context_impl_posix.cc', + 'tests/cefclient/browser/osr_accessibility_helper.cc', + 'tests/cefclient/browser/osr_accessibility_helper.h', + 'tests/cefclient/browser/osr_accessibility_node.cc', + 'tests/cefclient/browser/osr_accessibility_node.h', + 'tests/cefclient/browser/osr_accessibility_node_mac.mm', 'tests/cefclient/browser/root_window_mac.h', 'tests/cefclient/browser/root_window_mac.mm', 'tests/cefclient/browser/temp_window_mac.h', @@ -399,6 +409,7 @@ 'tests/cefsimple/simple_handler_linux.cc', ], 'ceftests_sources_common': [ + 'tests/ceftests/accessibility_unittest.cc', 'tests/ceftests/browser_info_map_unittest.cc', 'tests/ceftests/command_line_unittest.cc', 'tests/ceftests/cookie_unittest.cc', diff --git a/include/capi/cef_accessibility_handler_capi.h b/include/capi/cef_accessibility_handler_capi.h new file mode 100644 index 000000000..322ef5ebd --- /dev/null +++ b/include/capi/cef_accessibility_handler_capi.h @@ -0,0 +1,79 @@ +// Copyright (c) 2017 Marshall A. Greenblatt. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the name Chromium Embedded +// Framework nor the names of its contributors may be used to endorse +// or promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// --------------------------------------------------------------------------- +// +// This file was generated by the CEF translator tool and should not edited +// by hand. See the translator.README.txt file in the tools directory for +// more information. +// + +#ifndef CEF_INCLUDE_CAPI_CEF_ACCESSIBILITY_HANDLER_CAPI_H_ +#define CEF_INCLUDE_CAPI_CEF_ACCESSIBILITY_HANDLER_CAPI_H_ +#pragma once + +#include "include/capi/cef_values_capi.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +/// +// Implement this structure to receive accessibility notification when +// accessibility events have been registered. The functions of this structure +// will be called on the UI thread. +/// +typedef struct _cef_accessibility_handler_t { + /// + // Base structure. + /// + cef_base_ref_counted_t base; + + /// + // Called after renderer process sends accessibility tree changes to the + // browser process. + /// + void (CEF_CALLBACK *on_accessibility_tree_change)( + struct _cef_accessibility_handler_t* self, struct _cef_value_t* value); + + /// + // Called after renderer process sends accessibility location changes to the + // browser process. + /// + void (CEF_CALLBACK *on_accessibility_location_change)( + struct _cef_accessibility_handler_t* self, struct _cef_value_t* value); +} cef_accessibility_handler_t; + + +#ifdef __cplusplus +} +#endif + +#endif // CEF_INCLUDE_CAPI_CEF_ACCESSIBILITY_HANDLER_CAPI_H_ diff --git a/include/capi/cef_browser_capi.h b/include/capi/cef_browser_capi.h index 9485dd534..b92e13338 100644 --- a/include/capi/cef_browser_capi.h +++ b/include/capi/cef_browser_capi.h @@ -748,6 +748,34 @@ typedef struct _cef_browser_host_t { /// struct _cef_navigation_entry_t* (CEF_CALLBACK *get_visible_navigation_entry)( struct _cef_browser_host_t* self); + + /// + // Set accessibility state for all frames. |accessibility_state| may be + // default, enabled or disabled. If |accessibility_state| is STATE_DEFAULT + // then accessibility will be disabled by default and the state may be further + // controlled with the "force-renderer-accessibility" and "disable-renderer- + // accessibility" command-line switches. If |accessibility_state| is + // STATE_ENABLED then accessibility will be enabled. If |accessibility_state| + // is STATE_DISABLED then accessibility will be completely disabled. + // + // For windowed browsers accessibility will be enabled in Complete mode (which + // corresponds to kAccessibilityModeComplete in Chromium). In this mode all + // platform accessibility objects will be created and managed by Chromium's + // internal implementation. The client needs only to detect the screen reader + // and call this function appropriately. For example, on macOS the client can + // handle the @"AXEnhancedUserStructure" accessibility attribute to detect + // VoiceOver state changes and on Windows the client can handle WM_GETOBJECT + // with OBJID_CLIENT to detect accessibility readers. + // + // For windowless browsers accessibility will be enabled in TreeOnly mode + // (which corresponds to kAccessibilityModeWebContentsOnly in Chromium). In + // this mode renderer accessibility is enabled, the full tree is computed, and + // events are passed to CefAccessibiltyHandler, but platform accessibility + // objects are not created. The client may implement platform accessibility + // objects using CefAccessibiltyHandler callbacks if desired. + /// + void (CEF_CALLBACK *set_accessibility_state)(struct _cef_browser_host_t* self, + cef_state_t accessibility_state); } cef_browser_host_t; diff --git a/include/capi/cef_render_handler_capi.h b/include/capi/cef_render_handler_capi.h index 7912ff186..31fc11861 100644 --- a/include/capi/cef_render_handler_capi.h +++ b/include/capi/cef_render_handler_capi.h @@ -38,6 +38,7 @@ #define CEF_INCLUDE_CAPI_CEF_RENDER_HANDLER_CAPI_H_ #pragma once +#include "include/capi/cef_accessibility_handler_capi.h" #include "include/capi/cef_base_capi.h" #include "include/capi/cef_browser_capi.h" #include "include/capi/cef_drag_data_capi.h" @@ -57,6 +58,14 @@ typedef struct _cef_render_handler_t { /// cef_base_ref_counted_t base; + /// + // Return the handler for accessibility notifications. If no handler is + // provided the default implementation will be used. + /// + struct _cef_accessibility_handler_t* ( + CEF_CALLBACK *get_accessibility_handler)( + struct _cef_render_handler_t* self); + /// // Called to retrieve the root window rectangle in screen coordinates. Return // true (1) if the rectangle was provided. diff --git a/include/cef_accessibility_handler.h b/include/cef_accessibility_handler.h new file mode 100644 index 000000000..ecd336cac --- /dev/null +++ b/include/cef_accessibility_handler.h @@ -0,0 +1,66 @@ +// Copyright (c) 2017 Marshall A. Greenblatt. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the name Chromium Embedded +// Framework nor the names of its contributors may be used to endorse +// or promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// --------------------------------------------------------------------------- +// +// The contents of this file must follow a specific format in order to +// support the CEF translator tool. See the translator.README.txt file in the +// tools directory for more information. +// + +#ifndef CEF_INCLUDE_CEF_ACCESSIBILITY_HANDLER_H_ +#define CEF_INCLUDE_CEF_ACCESSIBILITY_HANDLER_H_ +#pragma once + +#include "include/cef_values.h" + +/// +// Implement this interface to receive accessibility notification when +// accessibility events have been registered. The methods of this class will +// be called on the UI thread. +/// +/*--cef(source=client)--*/ +class CefAccessibilityHandler : public virtual CefBaseRefCounted { + public: + /// + // Called after renderer process sends accessibility tree changes to the + // browser process. + /// + /*--cef()--*/ + virtual void OnAccessibilityTreeChange(CefRefPtr value) =0; + + /// + // Called after renderer process sends accessibility location changes to the + // browser process. + /// + /*--cef()--*/ + virtual void OnAccessibilityLocationChange(CefRefPtr value) =0; +}; + +#endif // CEF_INCLUDE_CEF_ACCESSIBILITY_HANDLER_H_ diff --git a/include/cef_browser.h b/include/cef_browser.h index f12facb9a..88f7b541d 100644 --- a/include/cef_browser.h +++ b/include/cef_browser.h @@ -365,7 +365,7 @@ class CefBrowserHost : public virtual CefBaseRefCounted { // Retrieve the window handle of the browser that opened this browser. Will // return NULL for non-popup windows or if this browser is wrapped in a // CefBrowserView. This method can be used in combination with custom handling - // of modal windows. + // of modal windows. /// /*--cef()--*/ virtual CefWindowHandle GetOpenerWindowHandle() =0; @@ -805,6 +805,35 @@ class CefBrowserHost : public virtual CefBaseRefCounted { /// /*--cef()--*/ virtual CefRefPtr GetVisibleNavigationEntry() =0; + + /// + // Set accessibility state for all frames. |accessibility_state| may be + // default, enabled or disabled. If |accessibility_state| is STATE_DEFAULT + // then accessibility will be disabled by default and the state may be further + // controlled with the "force-renderer-accessibility" and + // "disable-renderer-accessibility" command-line switches. If + // |accessibility_state| is STATE_ENABLED then accessibility will be enabled. + // If |accessibility_state| is STATE_DISABLED then accessibility will be + // completely disabled. + // + // For windowed browsers accessibility will be enabled in Complete mode (which + // corresponds to kAccessibilityModeComplete in Chromium). In this mode all + // platform accessibility objects will be created and managed by Chromium's + // internal implementation. The client needs only to detect the screen reader + // and call this method appropriately. For example, on macOS the client can + // handle the @"AXEnhancedUserInterface" accessibility attribute to detect + // VoiceOver state changes and on Windows the client can handle WM_GETOBJECT + // with OBJID_CLIENT to detect accessibility readers. + // + // For windowless browsers accessibility will be enabled in TreeOnly mode + // (which corresponds to kAccessibilityModeWebContentsOnly in Chromium). In + // this mode renderer accessibility is enabled, the full tree is computed, and + // events are passed to CefAccessibiltyHandler, but platform accessibility + // objects are not created. The client may implement platform accessibility + // objects using CefAccessibiltyHandler callbacks if desired. + /// + /*--cef()--*/ + virtual void SetAccessibilityState(cef_state_t accessibility_state) =0; }; #endif // CEF_INCLUDE_CEF_BROWSER_H_ diff --git a/include/cef_render_handler.h b/include/cef_render_handler.h index 9c253535e..057046b43 100644 --- a/include/cef_render_handler.h +++ b/include/cef_render_handler.h @@ -40,6 +40,7 @@ #include +#include "include/cef_accessibility_handler.h" #include "include/cef_base.h" #include "include/cef_browser.h" #include "include/cef_drag_data.h" @@ -57,6 +58,15 @@ class CefRenderHandler : public virtual CefBaseRefCounted { typedef cef_paint_element_type_t PaintElementType; typedef std::vector RectList; + /// + // Return the handler for accessibility notifications. If no handler is + // provided the default implementation will be used. + /// + /*--cef()--*/ + virtual CefRefPtr GetAccessibilityHandler() { + return NULL; + } + /// // Called to retrieve the root window rectangle in screen coordinates. Return // true if the rectangle was provided. diff --git a/libcef/browser/browser_host_impl.cc b/libcef/browser/browser_host_impl.cc index ef2a7f520..0b923a007 100644 --- a/libcef/browser/browser_host_impl.cc +++ b/libcef/browser/browser_host_impl.cc @@ -46,6 +46,7 @@ #include "components/zoom/zoom_controller.h" #include "content/browser/renderer_host/render_view_host_impl.h" #include "content/browser/gpu/compositor_util.h" +#include "content/browser/web_contents/web_contents_impl.h" #include "content/common/view_messages.h" #include "content/public/browser/desktop_media_id.h" #include "content/public/browser/download_manager.h" @@ -930,6 +931,39 @@ CefRefPtr CefBrowserHostImpl::GetVisibleNavigationEntry() { return new CefNavigationEntryImpl(entry); } +void CefBrowserHostImpl::SetAccessibilityState( + cef_state_t accessibility_state) { + // Do nothing if state is set to default. It'll be disabled by default and + // controlled by the commmand-line flags "force-renderer-accessibility" and + // "disable-renderer-accessibility". + if (accessibility_state == STATE_DEFAULT) + return; + + if (!CEF_CURRENTLY_ON_UIT()) { + CEF_POST_TASK(CEF_UIT, + base::Bind(&CefBrowserHostImpl::SetAccessibilityState, + this, accessibility_state)); + return; + } + + content::WebContentsImpl* web_contents_impl = + static_cast(web_contents()); + + if (!web_contents_impl) + return; + + content::AccessibilityMode accMode; + // In windowless mode set accessibility to TreeOnly mode. Else native + // accessibility APIs, specific to each platform, are also created. + if (accessibility_state == STATE_ENABLED) { + if (IsWindowless()) + accMode = content::kAccessibilityModeWebContentsOnly; + else + accMode = content::kAccessibilityModeComplete; + } + web_contents_impl->SetAccessibilityMode(accMode); +} + void CefBrowserHostImpl::SetMouseCursorChangeDisabled(bool disabled) { base::AutoLock lock_scope(state_lock_); mouse_cursor_change_disabled_ = disabled; @@ -2690,6 +2724,28 @@ bool CefBrowserHostImpl::OnMessageReceived(const IPC::Message& message) { return handled; } +void CefBrowserHostImpl::AccessibilityEventReceived( + const std::vector& eventData) { + // Only needed in windowless mode. + if (IsWindowless()) { + if (!web_contents() || !platform_delegate_) + return; + + platform_delegate_->AccessibilityEventReceived(eventData); + } +} + +void CefBrowserHostImpl::AccessibilityLocationChangesReceived( + const std::vector& locData) { + // Only needed in windowless mode. + if (IsWindowless()) { + if (!web_contents() || !platform_delegate_) + return; + + platform_delegate_->AccessibilityLocationChangesReceived(locData); + } +} + void CefBrowserHostImpl::OnWebContentsFocused() { if (client_.get()) { CefRefPtr handler = client_->GetFocusHandler(); diff --git a/libcef/browser/browser_host_impl.h b/libcef/browser/browser_host_impl.h index 0000eb545..27ae9a1b6 100644 --- a/libcef/browser/browser_host_impl.h +++ b/libcef/browser/browser_host_impl.h @@ -229,6 +229,7 @@ class CefBrowserHostImpl : public CefBrowserHost, void DragSourceSystemDragEnded() override; void DragSourceEndedAt(int x, int y, DragOperationsMask op) override; CefRefPtr GetVisibleNavigationEntry() override; + void SetAccessibilityState(cef_state_t accessibility_state) override; // CefBrowser methods. CefRefPtr GetHost() override; @@ -471,6 +472,13 @@ class CefBrowserHostImpl : public CefBrowserHost, void DidUpdateFaviconURL( const std::vector& candidates) override; bool OnMessageReceived(const IPC::Message& message) override; + void AccessibilityEventReceived( + const std::vector& eventData) + override; + void AccessibilityLocationChangesReceived( + const std::vector& locData) + override; + void OnWebContentsFocused() override; // Override to provide a thread safe implementation. bool Send(IPC::Message* message) override; diff --git a/libcef/browser/browser_platform_delegate.cc b/libcef/browser/browser_platform_delegate.cc index 43ca183a1..765818e7e 100644 --- a/libcef/browser/browser_platform_delegate.cc +++ b/libcef/browser/browser_platform_delegate.cc @@ -202,6 +202,16 @@ void CefBrowserPlatformDelegate::DragSourceSystemDragEnded() { NOTREACHED(); } +void CefBrowserPlatformDelegate::AccessibilityEventReceived( + const std::vector& eventData) { + NOTREACHED(); +} + +void CefBrowserPlatformDelegate::AccessibilityLocationChangesReceived( + const std::vector& locData) { + NOTREACHED(); +} + // static int CefBrowserPlatformDelegate::TranslateModifiers(uint32 cef_modifiers) { int webkit_modifiers = 0; diff --git a/libcef/browser/browser_platform_delegate.h b/libcef/browser/browser_platform_delegate.h index 4b4cb79ba..4098afe9a 100644 --- a/libcef/browser/browser_platform_delegate.h +++ b/libcef/browser/browser_platform_delegate.h @@ -262,6 +262,10 @@ class CefBrowserPlatformDelegate { virtual void DragSourceEndedAt(int x, int y, cef_drag_operations_mask_t op); virtual void DragSourceSystemDragEnded(); + virtual void AccessibilityEventReceived( + const std::vector& eventData); + virtual void AccessibilityLocationChangesReceived( + const std::vector& locData); protected: // Allow deletion via scoped_ptr only. diff --git a/libcef/browser/osr/browser_platform_delegate_osr.cc b/libcef/browser/osr/browser_platform_delegate_osr.cc index 11ecd70f6..efe0f483e 100644 --- a/libcef/browser/osr/browser_platform_delegate_osr.cc +++ b/libcef/browser/osr/browser_platform_delegate_osr.cc @@ -8,6 +8,7 @@ #include "libcef/browser/browser_host_impl.h" #include "libcef/browser/image_impl.h" +#include "libcef/browser/osr/osr_accessibility_util.h" #include "libcef/browser/osr/render_widget_host_view_osr.h" #include "libcef/browser/osr/web_contents_view_osr.h" #include "libcef/common/drag_data_impl.h" @@ -522,6 +523,34 @@ void CefBrowserPlatformDelegateOsr::DragSourceSystemDragEnded() { drag_start_rwh_ = nullptr; } +void CefBrowserPlatformDelegateOsr::AccessibilityEventReceived( + const std::vector& eventData) { + CefRefPtr handler = browser_->client()->GetRenderHandler(); + if (handler.get()) { + CefRefPtr acchandler = + handler->GetAccessibilityHandler(); + + if (acchandler.get()) { + acchandler->OnAccessibilityTreeChange( + osr_accessibility_util::ParseAccessibilityEventData(eventData)); + } + } +} + +void CefBrowserPlatformDelegateOsr::AccessibilityLocationChangesReceived( + const std::vector& locData) { + CefRefPtr handler = browser_->client()->GetRenderHandler(); + if (handler.get()) { + CefRefPtr acchandler = + handler->GetAccessibilityHandler(); + + if (acchandler.get()) { + acchandler->OnAccessibilityLocationChange( + osr_accessibility_util::ParseAccessibilityLocationData(locData)); + } + } +} + CefWindowHandle CefBrowserPlatformDelegateOsr::GetParentWindowHandle() const { return GetHostWindowHandle(); } diff --git a/libcef/browser/osr/browser_platform_delegate_osr.h b/libcef/browser/osr/browser_platform_delegate_osr.h index 98c795837..951b72479 100644 --- a/libcef/browser/osr/browser_platform_delegate_osr.h +++ b/libcef/browser/osr/browser_platform_delegate_osr.h @@ -88,6 +88,12 @@ class CefBrowserPlatformDelegateOsr : void DragSourceEndedAt(int x, int y, cef_drag_operations_mask_t op) override; void DragSourceSystemDragEnded() override; + void AccessibilityEventReceived( + const std::vector& eventData) + override; + void AccessibilityLocationChangesReceived( + const std::vector& locData) + override; // CefBrowserPlatformDelegateNative::WindowlessHandler methods: CefWindowHandle GetParentWindowHandle() const override; diff --git a/libcef/browser/osr/osr_accessibility_util.cc b/libcef/browser/osr/osr_accessibility_util.cc new file mode 100644 index 000000000..19b71841e --- /dev/null +++ b/libcef/browser/osr/osr_accessibility_util.cc @@ -0,0 +1,457 @@ +// Copyright 2017 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 "libcef/browser/osr/osr_accessibility_util.h" + +#include +#include +#include + +#include "base/json/string_escape.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "content/public/browser/ax_event_notification_details.h" +#include "ui/accessibility/ax_tree_update.h" +#include "ui/accessibility/ax_text_utils.h" +#include "ui/gfx/transform.h" + +namespace { +using ui::ToString; + +template +CefRefPtr ToCefValue(const std::vector& vecData); + +template<> +CefRefPtr ToCefValue(const std::vector& vecData) { + CefRefPtr value = CefListValue::Create(); + for (size_t i = 0; i SetInt(i, vecData[i]); + + return value; +} + +// Helper function for AXNodeData::ToCefValue - Converts AXState attributes to +// CefListValue. +CefRefPtr ToCefValue(uint32_t state) { + CefRefPtr value = CefListValue::Create(); + + static ui::AXState state_arr[] = { + ui::AX_STATE_NONE, + ui::AX_STATE_BUSY, + ui::AX_STATE_CHECKED, + ui::AX_STATE_COLLAPSED, + ui::AX_STATE_DEFAULT, + ui::AX_STATE_DISABLED, + ui::AX_STATE_EDITABLE, + ui::AX_STATE_EXPANDED, + ui::AX_STATE_FOCUSABLE, + ui::AX_STATE_HASPOPUP, + ui::AX_STATE_HORIZONTAL, + ui::AX_STATE_HOVERED, + ui::AX_STATE_INVISIBLE, + ui::AX_STATE_LINKED, + ui::AX_STATE_MULTILINE, + ui::AX_STATE_MULTISELECTABLE, + ui::AX_STATE_OFFSCREEN, + ui::AX_STATE_PRESSED, + ui::AX_STATE_PROTECTED, + ui::AX_STATE_READ_ONLY, + ui::AX_STATE_REQUIRED, + ui::AX_STATE_RICHLY_EDITABLE, + ui::AX_STATE_SELECTABLE, + ui::AX_STATE_SELECTED, + ui::AX_STATE_VERTICAL, + ui::AX_STATE_VISITED, + ui::AX_STATE_LAST + }; + + int index = 0; + // Iterate and find which states are set. + for (unsigned i = 0; i < arraysize(state_arr); i++) { + if (state & (1 << state_arr[i])) + value->SetString(index++, ToString(state_arr[i])); + } + return value; +} + +// Helper function for AXNodeData::ToCefValue - converts GfxRect to +// CefDictionaryValue. +CefRefPtr ToCefValue(const gfx::RectF& bounds) { + CefRefPtr value = CefDictionaryValue::Create(); + value->SetDouble("x", bounds.x()); + value->SetDouble("y", bounds.y()); + value->SetDouble("width", bounds.width()); + value->SetDouble("height", bounds.height()); + return value; +} + +// Helper Functor for adding AxNodeData::attributes to AXNodeData::ToCefValue. +struct PopulateAxNodeAttributes { + CefRefPtr attributes; + + explicit PopulateAxNodeAttributes(CefRefPtr attrs) : + attributes(attrs) {} + + // Int Attributes + void operator()(const std::pair attr) { + if (attr.first == ui::AX_INT_ATTRIBUTE_NONE) + return; + + switch (attr.first) { + case ui::AX_INT_ATTRIBUTE_NONE: + break; + case ui::AX_ATTR_SCROLL_X: + case ui::AX_ATTR_SCROLL_X_MIN: + case ui::AX_ATTR_SCROLL_X_MAX: + case ui::AX_ATTR_SCROLL_Y: + case ui::AX_ATTR_SCROLL_Y_MIN: + case ui::AX_ATTR_SCROLL_Y_MAX: + case ui::AX_ATTR_HIERARCHICAL_LEVEL: + case ui::AX_ATTR_TEXT_SEL_START: + case ui::AX_ATTR_TEXT_SEL_END: + case ui::AX_ATTR_ARIA_COL_COUNT: + case ui::AX_ATTR_ARIA_COL_INDEX: + case ui::AX_ATTR_ARIA_ROW_COUNT: + case ui::AX_ATTR_ARIA_ROW_INDEX: + case ui::AX_ATTR_TABLE_ROW_COUNT: + case ui::AX_ATTR_TABLE_COLUMN_COUNT: + case ui::AX_ATTR_TABLE_CELL_COLUMN_INDEX: + case ui::AX_ATTR_TABLE_CELL_ROW_INDEX: + case ui::AX_ATTR_TABLE_CELL_COLUMN_SPAN: + case ui::AX_ATTR_TABLE_CELL_ROW_SPAN: + case ui::AX_ATTR_TABLE_COLUMN_HEADER_ID: + case ui::AX_ATTR_TABLE_COLUMN_INDEX: + case ui::AX_ATTR_TABLE_HEADER_ID: + case ui::AX_ATTR_TABLE_ROW_HEADER_ID: + case ui::AX_ATTR_TABLE_ROW_INDEX: + case ui::AX_ATTR_ACTIVEDESCENDANT_ID: + case ui::AX_ATTR_IN_PAGE_LINK_TARGET_ID: + case ui::AX_ATTR_ERRORMESSAGE_ID: + case ui::AX_ATTR_MEMBER_OF_ID: + case ui::AX_ATTR_NEXT_ON_LINE_ID: + case ui::AX_ATTR_PREVIOUS_ON_LINE_ID: + case ui::AX_ATTR_CHILD_TREE_ID: + case ui::AX_ATTR_SET_SIZE: + case ui::AX_ATTR_POS_IN_SET: + attributes->SetInt(ToString(attr.first), attr.second); + break; + case ui::AX_ATTR_ACTION: + attributes->SetString(ToString(attr.first), + ui::ActionToUnlocalizedString( + static_cast(attr.second))); + break; + case ui::AX_ATTR_INVALID_STATE: + if (ui::AX_INVALID_STATE_NONE != attr.second) { + attributes->SetString(ToString(attr.first), ToString( + static_cast(attr.second))); + } + break; + case ui::AX_ATTR_SORT_DIRECTION: + if (ui::AX_SORT_DIRECTION_NONE != attr.second) { + attributes->SetString(ToString(attr.first), ToString( + static_cast(attr.second))); + } + break; + case ui::AX_ATTR_NAME_FROM: + attributes->SetString(ToString(attr.first), ToString( + static_cast(attr.second))); + break; + case ui::AX_ATTR_COLOR_VALUE: + case ui::AX_ATTR_BACKGROUND_COLOR: + case ui::AX_ATTR_COLOR: + attributes->SetString(ToString(attr.first), + base::StringPrintf("0x%X", attr.second)); + break; + case ui::AX_ATTR_DESCRIPTION_FROM: + attributes->SetString(ToString(attr.first), ToString( + static_cast(attr.second))); + break; + case ui::AX_ATTR_ARIA_CURRENT_STATE: + if (ui::AX_ARIA_CURRENT_STATE_NONE != attr.second) { + attributes->SetString(ToString(attr.first), ToString( + static_cast(attr.second))); + } + break; + case ui::AX_ATTR_TEXT_DIRECTION: + if (ui::AX_TEXT_DIRECTION_NONE != attr.second) { + attributes->SetString(ToString(attr.first), ToString( + static_cast(attr.second))); + } + break; + case ui::AX_ATTR_TEXT_STYLE: { + auto text_style = static_cast(attr.second); + if (text_style == ui::AX_TEXT_STYLE_NONE) + break; + + static ui::AXTextStyle textStyleArr[] = { + ui::AX_TEXT_STYLE_BOLD, + ui::AX_TEXT_STYLE_ITALIC, + ui::AX_TEXT_STYLE_UNDERLINE, + ui::AX_TEXT_STYLE_LINE_THROUGH + }; + + CefRefPtr list = CefListValue::Create(); + int index = 0; + // Iterate and find which states are set. + for (unsigned i = 0; i < arraysize(textStyleArr); i++) { + if (text_style & textStyleArr[i]) + list->SetString(index++, ToString(textStyleArr[i])); + } + attributes->SetList(ToString(attr.first), list); + } + break; + } + } + + // Set Bool Attributes. + void operator()(const std::pair attr) { + if (attr.first != ui::AX_BOOL_ATTRIBUTE_NONE) + attributes->SetBool(ToString(attr.first), attr.second); + } + // Set String Attributes. + void operator()(const std::pair& attr) { + if (attr.first != ui::AX_STRING_ATTRIBUTE_NONE) + attributes->SetString(ToString(attr.first), attr.second); + } + // Set Float attributes. + void operator()(const std::pair& attr) { + if (attr.first != ui::AX_FLOAT_ATTRIBUTE_NONE) + attributes->SetDouble(ToString(attr.first), attr.second); + } + + // Set Int list attributes. + void operator()(const std::pair>& attr) { + if (attr.first != ui::AX_INT_LIST_ATTRIBUTE_NONE) { + CefRefPtr list; + + if (ui::AX_ATTR_MARKER_TYPES == attr.first) { + list = CefListValue::Create(); + int index = 0; + for (size_t i = 0; i < attr.second.size(); ++i) { + auto type = static_cast(attr.second[i]); + + if (type == ui::AX_MARKER_TYPE_NONE) + continue; + + static ui::AXMarkerType marktypeArr[] = { + ui::AX_MARKER_TYPE_SPELLING, + ui::AX_MARKER_TYPE_GRAMMAR, + ui::AX_MARKER_TYPE_TEXT_MATCH + }; + + // Iterate and find which markers are set. + for (unsigned j = 0; j < arraysize(marktypeArr); j++) { + if (type & marktypeArr[j]) + list->SetString(index++, ToString(marktypeArr[j])); + } + } + } else { + list = ToCefValue(attr.second); + } + attributes->SetList(ToString(attr.first), list); + } + } +}; + +// Converts AXNodeData to CefDictionaryValue(like AXNodeData::ToString). +CefRefPtr ToCefValue(const ui::AXNodeData& node) { + CefRefPtr value = CefDictionaryValue::Create(); + + if (node.id != -1) + value->SetInt("id", node.id); + + value->SetString("role", ToString(node.role)); + value->SetList("state", ToCefValue(node.state)); + + if (node.offset_container_id != -1) + value->SetInt("offset_container_id", node.offset_container_id); + + value->SetDictionary("location", ToCefValue(node.location)); + + // Transform matrix is private, so we set the string that Clients can parse + // and use if needed. + if (node.transform && !node.transform->IsIdentity()) + value->SetString("transform", node.transform->ToString()); + + if (!node.child_ids.empty()) + value->SetList("child_ids", ToCefValue(node.child_ids)); + + CefRefPtr attributes = CefDictionaryValue::Create(); + PopulateAxNodeAttributes func(attributes); + + // Poupulate Int Attributes. + std::for_each(node.int_attributes.begin(), node.int_attributes.end(), func); + + // Poupulate String Attributes. + std::for_each(node.string_attributes.begin(), + node.string_attributes.end(), func); + + // Poupulate Float Attributes. + std::for_each(node.float_attributes.begin(), + node.float_attributes.end(), func); + + // Poupulate Bool Attributes. + std::for_each(node.bool_attributes.begin(), node.bool_attributes.end(), func); + + // Populate int list attributes. + std::for_each(node.intlist_attributes.begin(), + node.intlist_attributes.end(), func); + + value->SetDictionary("attributes", attributes); + + return value; +} + +// Converts AXTreeData to CefDictionaryValue(like AXTreeData::ToString). +CefRefPtr ToCefValue(const ui::AXTreeData& treeData) { + CefRefPtr value = CefDictionaryValue::Create(); + + if (treeData.tree_id != -1) + value->SetInt("tree_id", treeData.tree_id); + + if (treeData.parent_tree_id != -1) + value->SetInt("parent_tree_id", treeData.parent_tree_id); + + if (treeData.focused_tree_id != -1) + value->SetInt("focused_tree_id", treeData.focused_tree_id); + + if (!treeData.doctype.empty()) + value->SetString("doctype", treeData.doctype); + + value->SetBool("loaded", treeData.loaded); + + if (treeData.loading_progress != 0.0) + value->SetDouble("loading_progress", treeData.loading_progress); + + if (!treeData.mimetype.empty()) + value->SetString("mimetype", treeData.mimetype); + if (!treeData.url.empty()) + value->SetString("url", treeData.url); + if (!treeData.title.empty()) + value->SetString("title", treeData.title); + + if (treeData.sel_anchor_object_id != -1) { + value->SetInt("sel_anchor_object_id", treeData.sel_anchor_object_id); + value->SetInt("sel_anchor_offset", treeData.sel_anchor_offset); + value->SetString("sel_anchor_affinity", + ToString(treeData.sel_anchor_affinity)); + } + if (treeData.sel_focus_object_id != -1) { + value->SetInt("sel_focus_object_id", treeData.sel_anchor_object_id); + value->SetInt("sel_focus_offset", treeData.sel_anchor_offset); + value->SetString("sel_focus_affinity", + ToString(treeData.sel_anchor_affinity)); + } + + if (treeData.focus_id != -1) + value->SetInt("focus_id", treeData.focus_id); + + return value; +} + +// Converts AXTreeUpdate to CefDictionaryValue(like AXTreeUpdate::ToString). +CefRefPtr ToCefValue(const ui::AXTreeUpdate& update) { + CefRefPtr value = CefDictionaryValue::Create(); + + if (update.has_tree_data) { + value->SetBool("has_tree_data", true); + value->SetDictionary("tree_data", ToCefValue(update.tree_data)); + } + + if (update.node_id_to_clear != 0) + value->SetInt("node_id_to_clear", update.node_id_to_clear); + + if (update.root_id != 0) + value->SetInt("root_id", update.root_id); + + value->SetList("nodes", ToCefValue(update.nodes)); + + return value; +} + +// Convert AXEventNotificationDetails to CefDictionaryValue. +CefRefPtr ToCefValue( + const content::AXEventNotificationDetails& eventData) { + CefRefPtr value = CefDictionaryValue::Create(); + + if (eventData.id != -1) + value->SetInt("id", eventData.id); + + if (eventData.ax_tree_id != -1) + value->SetInt("ax_tree_id", eventData.ax_tree_id); + + if (eventData.event_type != ui::AX_EVENT_NONE) + value->SetString("event_type", ToString(eventData.event_type)); + + if (eventData.event_from != ui::AX_EVENT_FROM_NONE) + value->SetString("event_from", ToString(eventData.event_from)); + + value->SetDictionary("update", ToCefValue(eventData.update)); + return value; +} + +// Convert AXRelativeBounds to CefDictionaryValue. Similar to +// AXRelativeBounds::ToString. See that for more details +CefRefPtr ToCefValue(const ui::AXRelativeBounds& location) { + CefRefPtr value = CefDictionaryValue::Create(); + + if (location.offset_container_id != -1) + value->SetInt("offset_container_id", location.offset_container_id); + + value->SetDictionary("bounds", ToCefValue(location.bounds)); + + // Transform matrix is private, so we set the string that Clients can parse + // and use if needed. + if (location.transform && !location.transform->IsIdentity()) + value->SetString("transform", location.transform->ToString()); + + return value; +} + +// Convert AXEventNotificationDetails to CefDictionaryValue. +CefRefPtr ToCefValue( + const content::AXLocationChangeNotificationDetails& locData) { + CefRefPtr value = CefDictionaryValue::Create(); + + if (locData.id != -1) + value->SetInt("id", locData.id); + + if (locData.ax_tree_id != -1) + value->SetInt("ax_tree_id", locData.ax_tree_id); + + value->SetDictionary("new_location", ToCefValue(locData.new_location)); + + return value; +} + +template +CefRefPtr ToCefValue(const std::vector& vecData) { + CefRefPtr value = CefListValue::Create(); + + for (size_t i = 0; i SetDictionary(i, ToCefValue(vecData[i])); + + return value; +} + +} // namespace + +namespace osr_accessibility_util { + +CefRefPtr ParseAccessibilityEventData( + const std::vector& data) { + CefRefPtr value = CefValue::Create(); + value->SetList(ToCefValue(data)); + return value; +} + +CefRefPtr ParseAccessibilityLocationData( + const std::vector& data) { + CefRefPtr value = CefValue::Create(); + value->SetList(ToCefValue(data)); + return value; +} + +} // namespace osr_accessibility_util diff --git a/libcef/browser/osr/osr_accessibility_util.h b/libcef/browser/osr/osr_accessibility_util.h new file mode 100644 index 000000000..677710381 --- /dev/null +++ b/libcef/browser/osr/osr_accessibility_util.h @@ -0,0 +1,29 @@ +// Copyright 2017 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. + +#ifndef CEF_LIBCEF_BROWSER_OSR_ACCESSIBILITY_UTIL_H_ +#define CEF_LIBCEF_BROWSER_OSR_ACCESSIBILITY_UTIL_H_ +#pragma once + +#include +#include "include/cef_values.h" + +namespace content { +struct AXEventNotificationDetails; +struct AXLocationChangeNotificationDetails; +} + +namespace osr_accessibility_util { + +// Convert Accessibility Event and location updates to CefValue, which may be +// consumed or serialized with CefJSONWrite. +CefRefPtr ParseAccessibilityEventData( + const std::vector& data); + +CefRefPtr ParseAccessibilityLocationData( + const std::vector& data); + +} // namespace osr_accessibility_util + +#endif // CEF_LIBCEF_BROWSER_ACCESSIBILITY_UTIL_H_ diff --git a/libcef/browser/osr/web_contents_view_osr.cc b/libcef/browser/osr/web_contents_view_osr.cc index 7fcd310ea..cb480691c 100644 --- a/libcef/browser/osr/web_contents_view_osr.cc +++ b/libcef/browser/osr/web_contents_view_osr.cc @@ -71,6 +71,20 @@ void CefWebContentsViewOSR::StoreFocus() { void CefWebContentsViewOSR::RestoreFocus() { } +void CefWebContentsViewOSR::GotFocus() { + if (web_contents_) { + content::WebContentsImpl* web_contents_impl = + static_cast(web_contents_); + if (web_contents_impl) + web_contents_impl->NotifyWebContentsFocused(); + } +} + +void CefWebContentsViewOSR::TakeFocus(bool reverse) { + if (web_contents_->GetDelegate()) + web_contents_->GetDelegate()->TakeFocus(web_contents_, reverse); +} + content::DropData* CefWebContentsViewOSR::GetDropData() const { return NULL; } diff --git a/libcef/browser/osr/web_contents_view_osr.h b/libcef/browser/osr/web_contents_view_osr.h index 37967b5ff..09d31c4f9 100644 --- a/libcef/browser/osr/web_contents_view_osr.h +++ b/libcef/browser/osr/web_contents_view_osr.h @@ -70,6 +70,8 @@ class CefWebContentsViewOSR : public content::WebContentsView, const content::DragEventSourceInfo& event_info, content::RenderWidgetHostImpl* source_rwh) override; void UpdateDragCursor(blink::WebDragOperation operation) override; + virtual void GotFocus() override; + virtual void TakeFocus(bool reverse) override; private: CefRenderWidgetHostViewOSR* GetView() const; diff --git a/libcef_dll/cpptoc/accessibility_handler_cpptoc.cc b/libcef_dll/cpptoc/accessibility_handler_cpptoc.cc new file mode 100644 index 000000000..cacc01a70 --- /dev/null +++ b/libcef_dll/cpptoc/accessibility_handler_cpptoc.cc @@ -0,0 +1,81 @@ +// Copyright (c) 2017 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. +// +// --------------------------------------------------------------------------- +// +// This file was generated by the CEF translator tool. If making changes by +// hand only do so within the body of existing method and function +// implementations. See the translator.README.txt file in the tools directory +// for more information. +// + +#include "libcef_dll/cpptoc/accessibility_handler_cpptoc.h" +#include "libcef_dll/ctocpp/value_ctocpp.h" + + +namespace { + +// MEMBER FUNCTIONS - Body may be edited by hand. + +void CEF_CALLBACK accessibility_handler_on_accessibility_tree_change( + struct _cef_accessibility_handler_t* self, struct _cef_value_t* value) { + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + DCHECK(self); + if (!self) + return; + // Verify param: value; type: refptr_diff + DCHECK(value); + if (!value) + return; + + // Execute + CefAccessibilityHandlerCppToC::Get(self)->OnAccessibilityTreeChange( + CefValueCToCpp::Wrap(value)); +} + +void CEF_CALLBACK accessibility_handler_on_accessibility_location_change( + struct _cef_accessibility_handler_t* self, struct _cef_value_t* value) { + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + DCHECK(self); + if (!self) + return; + // Verify param: value; type: refptr_diff + DCHECK(value); + if (!value) + return; + + // Execute + CefAccessibilityHandlerCppToC::Get(self)->OnAccessibilityLocationChange( + CefValueCToCpp::Wrap(value)); +} + +} // namespace + + +// CONSTRUCTOR - Do not edit by hand. + +CefAccessibilityHandlerCppToC::CefAccessibilityHandlerCppToC() { + GetStruct()->on_accessibility_tree_change = + accessibility_handler_on_accessibility_tree_change; + GetStruct()->on_accessibility_location_change = + accessibility_handler_on_accessibility_location_change; +} + +template<> CefRefPtr CefCppToCRefCounted::UnwrapDerived( + CefWrapperType type, cef_accessibility_handler_t* s) { + NOTREACHED() << "Unexpected class type: " << type; + return NULL; +} + +#if DCHECK_IS_ON() +template<> base::AtomicRefCount CefCppToCRefCounted::DebugObjCt = 0; +#endif + +template<> CefWrapperType CefCppToCRefCounted::kWrapperType = + WT_ACCESSIBILITY_HANDLER; diff --git a/libcef_dll/cpptoc/accessibility_handler_cpptoc.h b/libcef_dll/cpptoc/accessibility_handler_cpptoc.h new file mode 100644 index 000000000..4ec8d228c --- /dev/null +++ b/libcef_dll/cpptoc/accessibility_handler_cpptoc.h @@ -0,0 +1,34 @@ +// Copyright (c) 2017 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. +// +// --------------------------------------------------------------------------- +// +// This file was generated by the CEF translator tool. If making changes by +// hand only do so within the body of existing method and function +// implementations. See the translator.README.txt file in the tools directory +// for more information. +// + +#ifndef CEF_LIBCEF_DLL_CPPTOC_ACCESSIBILITY_HANDLER_CPPTOC_H_ +#define CEF_LIBCEF_DLL_CPPTOC_ACCESSIBILITY_HANDLER_CPPTOC_H_ +#pragma once + +#if !defined(WRAPPING_CEF_SHARED) +#error This file can be included wrapper-side only +#endif + +#include "include/cef_accessibility_handler.h" +#include "include/capi/cef_accessibility_handler_capi.h" +#include "libcef_dll/cpptoc/cpptoc_ref_counted.h" + +// Wrap a C++ class with a C structure. +// This class may be instantiated and accessed wrapper-side only. +class CefAccessibilityHandlerCppToC + : public CefCppToCRefCounted { + public: + CefAccessibilityHandlerCppToC(); +}; + +#endif // CEF_LIBCEF_DLL_CPPTOC_ACCESSIBILITY_HANDLER_CPPTOC_H_ diff --git a/libcef_dll/cpptoc/browser_host_cpptoc.cc b/libcef_dll/cpptoc/browser_host_cpptoc.cc index 09fd64372..450a92990 100644 --- a/libcef_dll/cpptoc/browser_host_cpptoc.cc +++ b/libcef_dll/cpptoc/browser_host_cpptoc.cc @@ -1005,6 +1005,19 @@ struct _cef_navigation_entry_t* CEF_CALLBACK browser_host_get_visible_navigation return CefNavigationEntryCppToC::Wrap(_retval); } +void CEF_CALLBACK browser_host_set_accessibility_state( + struct _cef_browser_host_t* self, cef_state_t accessibility_state) { + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + DCHECK(self); + if (!self) + return; + + // Execute + CefBrowserHostCppToC::Get(self)->SetAccessibilityState( + accessibility_state); +} + } // namespace @@ -1072,6 +1085,7 @@ CefBrowserHostCppToC::CefBrowserHostCppToC() { browser_host_drag_source_system_drag_ended; GetStruct()->get_visible_navigation_entry = browser_host_get_visible_navigation_entry; + GetStruct()->set_accessibility_state = browser_host_set_accessibility_state; } template<> CefRefPtr CefCppToCRefCounted _retval = CefRenderHandlerCppToC::Get( + self)->GetAccessibilityHandler(); + + // Return type: refptr_same + return CefAccessibilityHandlerCppToC::Wrap(_retval); +} + int CEF_CALLBACK render_handler_get_root_screen_rect( struct _cef_render_handler_t* self, cef_browser_t* browser, cef_rect_t* rect) { @@ -396,6 +413,8 @@ void CEF_CALLBACK render_handler_on_ime_composition_range_changed( // CONSTRUCTOR - Do not edit by hand. CefRenderHandlerCppToC::CefRenderHandlerCppToC() { + GetStruct()->get_accessibility_handler = + render_handler_get_accessibility_handler; GetStruct()->get_root_screen_rect = render_handler_get_root_screen_rect; GetStruct()->get_view_rect = render_handler_get_view_rect; GetStruct()->get_screen_point = render_handler_get_screen_point; diff --git a/libcef_dll/ctocpp/accessibility_handler_ctocpp.cc b/libcef_dll/ctocpp/accessibility_handler_ctocpp.cc new file mode 100644 index 000000000..415dc0485 --- /dev/null +++ b/libcef_dll/ctocpp/accessibility_handler_ctocpp.cc @@ -0,0 +1,75 @@ +// Copyright (c) 2017 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. +// +// --------------------------------------------------------------------------- +// +// This file was generated by the CEF translator tool. If making changes by +// hand only do so within the body of existing method and function +// implementations. See the translator.README.txt file in the tools directory +// for more information. +// + +#include "libcef_dll/cpptoc/value_cpptoc.h" +#include "libcef_dll/ctocpp/accessibility_handler_ctocpp.h" + + +// VIRTUAL METHODS - Body may be edited by hand. + +void CefAccessibilityHandlerCToCpp::OnAccessibilityTreeChange( + CefRefPtr value) { + cef_accessibility_handler_t* _struct = GetStruct(); + if (CEF_MEMBER_MISSING(_struct, on_accessibility_tree_change)) + return; + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Verify param: value; type: refptr_diff + DCHECK(value.get()); + if (!value.get()) + return; + + // Execute + _struct->on_accessibility_tree_change(_struct, + CefValueCppToC::Wrap(value)); +} + +void CefAccessibilityHandlerCToCpp::OnAccessibilityLocationChange( + CefRefPtr value) { + cef_accessibility_handler_t* _struct = GetStruct(); + if (CEF_MEMBER_MISSING(_struct, on_accessibility_location_change)) + return; + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Verify param: value; type: refptr_diff + DCHECK(value.get()); + if (!value.get()) + return; + + // Execute + _struct->on_accessibility_location_change(_struct, + CefValueCppToC::Wrap(value)); +} + + +// CONSTRUCTOR - Do not edit by hand. + +CefAccessibilityHandlerCToCpp::CefAccessibilityHandlerCToCpp() { +} + +template<> cef_accessibility_handler_t* CefCToCppRefCounted::UnwrapDerived( + CefWrapperType type, CefAccessibilityHandler* c) { + NOTREACHED() << "Unexpected class type: " << type; + return NULL; +} + +#if DCHECK_IS_ON() +template<> base::AtomicRefCount CefCToCppRefCounted::DebugObjCt = 0; +#endif + +template<> CefWrapperType CefCToCppRefCounted::kWrapperType = + WT_ACCESSIBILITY_HANDLER; diff --git a/libcef_dll/ctocpp/accessibility_handler_ctocpp.h b/libcef_dll/ctocpp/accessibility_handler_ctocpp.h new file mode 100644 index 000000000..e1567ea2b --- /dev/null +++ b/libcef_dll/ctocpp/accessibility_handler_ctocpp.h @@ -0,0 +1,38 @@ +// Copyright (c) 2017 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. +// +// --------------------------------------------------------------------------- +// +// This file was generated by the CEF translator tool. If making changes by +// hand only do so within the body of existing method and function +// implementations. See the translator.README.txt file in the tools directory +// for more information. +// + +#ifndef CEF_LIBCEF_DLL_CTOCPP_ACCESSIBILITY_HANDLER_CTOCPP_H_ +#define CEF_LIBCEF_DLL_CTOCPP_ACCESSIBILITY_HANDLER_CTOCPP_H_ +#pragma once + +#if !defined(BUILDING_CEF_SHARED) +#error This file can be included DLL-side only +#endif + +#include "include/cef_accessibility_handler.h" +#include "include/capi/cef_accessibility_handler_capi.h" +#include "libcef_dll/ctocpp/ctocpp_ref_counted.h" + +// Wrap a C structure with a C++ class. +// This class may be instantiated and accessed DLL-side only. +class CefAccessibilityHandlerCToCpp + : public CefCToCppRefCounted { + public: + CefAccessibilityHandlerCToCpp(); + + // CefAccessibilityHandler methods. + void OnAccessibilityTreeChange(CefRefPtr value) override; + void OnAccessibilityLocationChange(CefRefPtr value) override; +}; + +#endif // CEF_LIBCEF_DLL_CTOCPP_ACCESSIBILITY_HANDLER_CTOCPP_H_ diff --git a/libcef_dll/ctocpp/browser_host_ctocpp.cc b/libcef_dll/ctocpp/browser_host_ctocpp.cc index be2e52c78..d97fd240b 100644 --- a/libcef_dll/ctocpp/browser_host_ctocpp.cc +++ b/libcef_dll/ctocpp/browser_host_ctocpp.cc @@ -843,6 +843,19 @@ CefRefPtr CefBrowserHostCToCpp::GetVisibleNavigationEntry( return CefNavigationEntryCToCpp::Wrap(_retval); } +void CefBrowserHostCToCpp::SetAccessibilityState( + cef_state_t accessibility_state) { + cef_browser_host_t* _struct = GetStruct(); + if (CEF_MEMBER_MISSING(_struct, set_accessibility_state)) + return; + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Execute + _struct->set_accessibility_state(_struct, + accessibility_state); +} + // CONSTRUCTOR - Do not edit by hand. diff --git a/libcef_dll/ctocpp/browser_host_ctocpp.h b/libcef_dll/ctocpp/browser_host_ctocpp.h index 667d51132..b868eaf95 100644 --- a/libcef_dll/ctocpp/browser_host_ctocpp.h +++ b/libcef_dll/ctocpp/browser_host_ctocpp.h @@ -103,6 +103,7 @@ class CefBrowserHostCToCpp void DragSourceEndedAt(int x, int y, DragOperationsMask op) OVERRIDE; void DragSourceSystemDragEnded() OVERRIDE; CefRefPtr GetVisibleNavigationEntry() OVERRIDE; + void SetAccessibilityState(cef_state_t accessibility_state) OVERRIDE; }; #endif // CEF_LIBCEF_DLL_CTOCPP_BROWSER_HOST_CTOCPP_H_ diff --git a/libcef_dll/ctocpp/render_handler_ctocpp.cc b/libcef_dll/ctocpp/render_handler_ctocpp.cc index 2bcd17d99..867f93814 100644 --- a/libcef_dll/ctocpp/render_handler_ctocpp.cc +++ b/libcef_dll/ctocpp/render_handler_ctocpp.cc @@ -12,11 +12,28 @@ #include "libcef_dll/cpptoc/browser_cpptoc.h" #include "libcef_dll/cpptoc/drag_data_cpptoc.h" +#include "libcef_dll/ctocpp/accessibility_handler_ctocpp.h" #include "libcef_dll/ctocpp/render_handler_ctocpp.h" // VIRTUAL METHODS - Body may be edited by hand. +CefRefPtr CefRenderHandlerCToCpp::GetAccessibilityHandler( + ) { + cef_render_handler_t* _struct = GetStruct(); + if (CEF_MEMBER_MISSING(_struct, get_accessibility_handler)) + return NULL; + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Execute + cef_accessibility_handler_t* _retval = _struct->get_accessibility_handler( + _struct); + + // Return type: refptr_same + return CefAccessibilityHandlerCToCpp::Wrap(_retval); +} + bool CefRenderHandlerCToCpp::GetRootScreenRect(CefRefPtr browser, CefRect& rect) { cef_render_handler_t* _struct = GetStruct(); diff --git a/libcef_dll/ctocpp/render_handler_ctocpp.h b/libcef_dll/ctocpp/render_handler_ctocpp.h index b49310251..84b9509ec 100644 --- a/libcef_dll/ctocpp/render_handler_ctocpp.h +++ b/libcef_dll/ctocpp/render_handler_ctocpp.h @@ -31,6 +31,7 @@ class CefRenderHandlerCToCpp CefRenderHandlerCToCpp(); // CefRenderHandler methods. + CefRefPtr GetAccessibilityHandler() override; bool GetRootScreenRect(CefRefPtr browser, CefRect& rect) override; bool GetViewRect(CefRefPtr browser, CefRect& rect) override; bool GetScreenPoint(CefRefPtr browser, int viewX, int viewY, diff --git a/libcef_dll/libcef_dll.cc b/libcef_dll/libcef_dll.cc index 096eede28..c7d27c7db 100644 --- a/libcef_dll/libcef_dll.cc +++ b/libcef_dll/libcef_dll.cc @@ -107,6 +107,7 @@ #include "libcef_dll/cpptoc/x509certificate_cpptoc.h" #include "libcef_dll/cpptoc/xml_reader_cpptoc.h" #include "libcef_dll/cpptoc/zip_reader_cpptoc.h" +#include "libcef_dll/ctocpp/accessibility_handler_ctocpp.h" #include "libcef_dll/ctocpp/app_ctocpp.h" #include "libcef_dll/ctocpp/browser_process_handler_ctocpp.h" #include "libcef_dll/ctocpp/views/browser_view_delegate_ctocpp.h" @@ -237,6 +238,8 @@ CEF_EXPORT void cef_shutdown() { #if DCHECK_IS_ON() // Check that all wrapper objects have been destroyed + DCHECK(base::AtomicRefCountIsZero( + &CefAccessibilityHandlerCToCpp::DebugObjCt)); DCHECK(base::AtomicRefCountIsZero(&CefAuthCallbackCppToC::DebugObjCt)); DCHECK(base::AtomicRefCountIsZero( &CefBeforeDownloadCallbackCppToC::DebugObjCt)); diff --git a/libcef_dll/wrapper/libcef_dll_wrapper.cc b/libcef_dll/wrapper/libcef_dll_wrapper.cc index 15026ab5c..efa5c73e9 100644 --- a/libcef_dll/wrapper/libcef_dll_wrapper.cc +++ b/libcef_dll/wrapper/libcef_dll_wrapper.cc @@ -39,6 +39,7 @@ #include "include/cef_web_plugin.h" #include "include/capi/cef_web_plugin_capi.h" #include "include/cef_version.h" +#include "libcef_dll/cpptoc/accessibility_handler_cpptoc.h" #include "libcef_dll/cpptoc/app_cpptoc.h" #include "libcef_dll/cpptoc/browser_process_handler_cpptoc.h" #include "libcef_dll/cpptoc/views/browser_view_delegate_cpptoc.h" @@ -229,6 +230,8 @@ CEF_GLOBAL void CefShutdown() { #if DCHECK_IS_ON() // Check that all wrapper objects have been destroyed + DCHECK(base::AtomicRefCountIsZero( + &CefAccessibilityHandlerCppToC::DebugObjCt)); DCHECK(base::AtomicRefCountIsZero(&CefAuthCallbackCToCpp::DebugObjCt)); DCHECK(base::AtomicRefCountIsZero( &CefBeforeDownloadCallbackCToCpp::DebugObjCt)); diff --git a/libcef_dll/wrapper_types.h b/libcef_dll/wrapper_types.h index c9a464f15..de3a301c1 100644 --- a/libcef_dll/wrapper_types.h +++ b/libcef_dll/wrapper_types.h @@ -17,6 +17,7 @@ enum CefWrapperType { WT_BASE_REF_COUNTED = 1, WT_BASE_SCOPED, + WT_ACCESSIBILITY_HANDLER, WT_APP, WT_AUTH_CALLBACK, WT_BEFORE_DOWNLOAD_CALLBACK, diff --git a/tests/cefclient/browser/browser_window_osr_gtk.cc b/tests/cefclient/browser/browser_window_osr_gtk.cc index 14f606775..628d8a476 100644 --- a/tests/cefclient/browser/browser_window_osr_gtk.cc +++ b/tests/cefclient/browser/browser_window_osr_gtk.cc @@ -1279,6 +1279,10 @@ void BrowserWindowOsrGtk::OnImeCompositionRangeChanged( CEF_REQUIRE_UI_THREAD(); } +void BrowserWindowOsrGtk::UpdateAccessibilityTree(CefRefPtr value) { + CEF_REQUIRE_UI_THREAD(); +} + void BrowserWindowOsrGtk::Create(ClientWindowHandle parent_handle) { REQUIRE_MAIN_THREAD(); DCHECK(!glarea_); diff --git a/tests/cefclient/browser/browser_window_osr_gtk.h b/tests/cefclient/browser/browser_window_osr_gtk.h index 07778e1b4..f1fbee6fa 100644 --- a/tests/cefclient/browser/browser_window_osr_gtk.h +++ b/tests/cefclient/browser/browser_window_osr_gtk.h @@ -80,6 +80,7 @@ class BrowserWindowOsrGtk : public BrowserWindow, CefRefPtr browser, const CefRange& selection_range, const CefRenderHandler::RectList& character_bounds) OVERRIDE; + void UpdateAccessibilityTree(CefRefPtr value) OVERRIDE; private: ~BrowserWindowOsrGtk(); diff --git a/tests/cefclient/browser/browser_window_osr_mac.h b/tests/cefclient/browser/browser_window_osr_mac.h index b50681d0a..9600bf21c 100644 --- a/tests/cefclient/browser/browser_window_osr_mac.h +++ b/tests/cefclient/browser/browser_window_osr_mac.h @@ -83,6 +83,8 @@ class BrowserWindowOsrMac : public BrowserWindow, const CefRange& selection_range, const CefRenderHandler::RectList& character_bounds) OVERRIDE; + void UpdateAccessibilityTree(CefRefPtr value) OVERRIDE; + private: // Create the NSView. void Create(ClientWindowHandle parent_handle, const CefRect& rect); diff --git a/tests/cefclient/browser/browser_window_osr_mac.mm b/tests/cefclient/browser/browser_window_osr_mac.mm index 47bf2ebd6..6112c0415 100644 --- a/tests/cefclient/browser/browser_window_osr_mac.mm +++ b/tests/cefclient/browser/browser_window_osr_mac.mm @@ -13,10 +13,14 @@ #include "include/wrapper/cef_closure_task.h" #include "tests/cefclient/browser/bytes_write_handler.h" #include "tests/cefclient/browser/main_context.h" +#include "tests/cefclient/browser/osr_accessibility_helper.h" +#include "tests/cefclient/browser/osr_accessibility_node.h" #include "tests/cefclient/browser/text_input_client_osr_mac.h" #include "tests/shared/browser/geometry_util.h" #include "tests/shared/browser/main_message_loop.h" +#import + namespace { CefTextInputClientOSRMac* GetInputClientFromContext( @@ -29,7 +33,7 @@ CefTextInputClientOSRMac* GetInputClientFromContext( } // namespace @interface BrowserOpenGLView - : NSOpenGLView { + : NSOpenGLView { @private NSTrackingArea* tracking_area_; client::BrowserWindowOsrMac* browser_window_; @@ -52,6 +56,9 @@ CefTextInputClientOSRMac* GetInputClientFromContext( // For intreacting with IME. NSTextInputContext* text_input_context_osr_mac_; + // Manages Accessibility Tree + client::OsrAccessibilityHelper* accessibility_helper_; + // Event monitor for scroll wheel end event. id endWheelMonitor_; } @@ -99,6 +106,7 @@ CefTextInputClientOSRMac* GetInputClientFromContext( - (NSRect)convertRectToBackingInternal:(NSRect)aRect; - (void)ChangeCompositionRange:(CefRange)range character_bounds:(const CefRenderHandler::RectList&) character_bounds; +- (void)UpdateAccessibilityTree:(CefRefPtr)value; @end @@ -948,6 +956,50 @@ NSPoint ConvertPointFromWindowToScreen(NSWindow* window, NSPoint point) { } } +// NSAccessibility Protocol implementation. +- (BOOL)accessibilityIsIgnored { + if(!accessibility_helper_) + return YES; + else + return NO; +} + +- (id)accessibilityAttributeValue:(NSString *)attribute { + if(!accessibility_helper_) + return [super accessibilityAttributeValue:attribute]; + if ([attribute isEqualToString:NSAccessibilityRoleAttribute]) { + return NSAccessibilityGroupRole; + } else if ([attribute isEqualToString:NSAccessibilityDescriptionAttribute]) { + client::OsrAXNode* node = accessibility_helper_->GetRootNode(); + std::string desc = node ? node->AxDescription(): ""; + return [NSString stringWithUTF8String:desc.c_str()]; + } else if ([attribute isEqualToString:NSAccessibilityValueAttribute]) { + client::OsrAXNode* node = accessibility_helper_->GetRootNode(); + std::string desc = node ? node->AxValue(): ""; + return [NSString stringWithUTF8String:desc.c_str()]; + } else if ([attribute isEqualToString: + NSAccessibilityRoleDescriptionAttribute]) { + return NSAccessibilityRoleDescriptionForUIElement(self); + } else if ([attribute isEqualToString:NSAccessibilityChildrenAttribute]) { + client::OsrAXNode* node = accessibility_helper_->GetRootNode(); + // Add Root as first Kid + NSMutableArray* kids = [NSMutableArray arrayWithCapacity:1]; + NSObject* child = node->GetNativeAccessibleObject(NULL); + [kids addObject: child]; + return NSAccessibilityUnignoredChildren(kids); + } else { + return [super accessibilityAttributeValue:attribute]; + } +} + +- (id)accessibilityFocusedUIElement { + if (accessibility_helper_) { + client::OsrAXNode* node = accessibility_helper_->GetFocusedNode(); + return node ? node->GetNativeAccessibleObject(NULL) : nil; + } + return nil; +} + // Utility methods. - (void)resetDragDrop { current_drag_op_ = NSDragOperationNone; @@ -1142,6 +1194,21 @@ NSPoint ConvertPointFromWindowToScreen(NSWindow* window, NSPoint point) { if (client) [client ChangeCompositionRange: range character_bounds:bounds]; } + +- (void)UpdateAccessibilityTree:(CefRefPtr)value { + if (!accessibility_helper_) { + accessibility_helper_ = new client::OsrAccessibilityHelper(value, + [self getBrowser]); + } else { + accessibility_helper_->UpdateAccessibilityTree(value); + } + + if (accessibility_helper_) { + NSAccessibilityPostNotification(self, + NSAccessibilityValueChangedNotification); + } + return; +} @end @@ -1493,6 +1560,14 @@ void BrowserWindowOsrMac::OnImeCompositionRangeChanged( } } +void BrowserWindowOsrMac::UpdateAccessibilityTree(CefRefPtr value) { + CEF_REQUIRE_UI_THREAD(); + + if (nsview_) { + [GLView(nsview_) UpdateAccessibilityTree:value]; + } +} + void BrowserWindowOsrMac::Create(ClientWindowHandle parent_handle, const CefRect& rect) { REQUIRE_MAIN_THREAD(); diff --git a/tests/cefclient/browser/client_handler_osr.cc b/tests/cefclient/browser/client_handler_osr.cc index e3bc12de8..b84543805 100644 --- a/tests/cefclient/browser/client_handler_osr.cc +++ b/tests/cefclient/browser/client_handler_osr.cc @@ -146,4 +146,11 @@ void ClientHandlerOsr::OnImeCompositionRangeChanged( character_bounds); } +void ClientHandlerOsr::OnAccessibilityTreeChange(CefRefPtr value) { + CEF_REQUIRE_UI_THREAD(); + if (!osr_delegate_) + return; + osr_delegate_->UpdateAccessibilityTree(value); +} + } // namespace client diff --git a/tests/cefclient/browser/client_handler_osr.h b/tests/cefclient/browser/client_handler_osr.h index f1af487de..83837317b 100644 --- a/tests/cefclient/browser/client_handler_osr.h +++ b/tests/cefclient/browser/client_handler_osr.h @@ -13,6 +13,7 @@ namespace client { // Client handler implementation for windowless browsers. There will only ever // be one browser per handler instance. class ClientHandlerOsr : public ClientHandler, + public CefAccessibilityHandler, public CefRenderHandler { public: // Implement this interface to receive notification of ClientHandlerOsr @@ -61,6 +62,8 @@ class ClientHandlerOsr : public ClientHandler, const CefRange& selection_range, const CefRenderHandler::RectList& character_bounds) = 0; + virtual void UpdateAccessibilityTree(CefRefPtr value) = 0; + protected: virtual ~OsrDelegate() {} }; @@ -77,6 +80,9 @@ class ClientHandlerOsr : public ClientHandler, CefRefPtr GetRenderHandler() OVERRIDE { return this; } + CefRefPtr GetAccessibilityHandler() OVERRIDE { + return this; + } // CefLifeSpanHandler methods. void OnAfterCreated(CefRefPtr browser) OVERRIDE; @@ -118,6 +124,10 @@ class ClientHandlerOsr : public ClientHandler, const CefRange& selection_range, const CefRenderHandler::RectList& character_bounds) OVERRIDE; + // CefAccessibilityHandler methods. + void OnAccessibilityTreeChange(CefRefPtr value) OVERRIDE; + void OnAccessibilityLocationChange(CefRefPtr value) OVERRIDE {} + private: // Only accessed on the UI thread. OsrDelegate* osr_delegate_; diff --git a/tests/cefclient/browser/osr_accessibility_helper.cc b/tests/cefclient/browser/osr_accessibility_helper.cc new file mode 100644 index 000000000..07f803014 --- /dev/null +++ b/tests/cefclient/browser/osr_accessibility_helper.cc @@ -0,0 +1,145 @@ +// Copyright 2017 The Chromium Embedded Framework Authors. Portions copyright +// 2013 The Chromium 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 "tests/cefclient/browser/osr_accessibility_helper.h" +#include "tests/cefclient/browser/osr_accessibility_node.h" + +namespace client { + +OsrAccessibilityHelper::OsrAccessibilityHelper(CefRefPtr value, + CefRefPtr browser) + : root_node_id_(-1), + focused_node_id_(-1), + browser_(browser) { + UpdateAccessibilityTree(value); +} + +void OsrAccessibilityHelper::UpdateAccessibilityTree( + CefRefPtr value) { + if (value && value->GetType() == VTYPE_LIST) { + CefRefPtr list = value->GetList(); + size_t numEvents = list->GetSize(); + if (numEvents > 0) { + for (size_t i = 0; i < numEvents; i++) { + CefRefPtr event = list->GetDictionary(i); + if (event && event->HasKey("event_type") && event->HasKey("update")) { + std::string event_type = event->GetString("event_type"); + + CefRefPtr update = event->GetDictionary("update"); + if (event_type == "layoutComplete") + UpdateLayout(update); + if (event_type == "focus" && event->HasKey("id")) { + // Update focused node id + focused_node_id_ = event->GetInt("id"); + UpdateFocusedNode(update); + } + } + } + } + } +} + +void OsrAccessibilityHelper::UpdateLayout( + CefRefPtr update) { + if (update) { + CefRefPtr tree_data; + // get tree data + if (update->HasKey("has_tree_data") && update->GetBool("has_tree_data")) + tree_data = update->GetDictionary("tree_data"); + + // If a node is to be cleared + if (update->HasKey("node_id_to_clear")) { + int node_id_to_clear = update->GetInt("node_id_to_clear"); + + // reset root node if that is to be cleared + if (node_id_to_clear == root_node_id_) + root_node_id_ = -1; + OsrAXNode *node = GetNode(node_id_to_clear); + DestroyNode(node); + } + + if (update->HasKey("root_id")) + root_node_id_ = update->GetInt("root_id"); + + if (tree_data && tree_data->HasKey("focus_id")) + focused_node_id_ = tree_data->GetInt("focus_id"); + // Now initialize/update the node data. + if (update->HasKey("nodes")) { + CefRefPtr nodes = update->GetList("nodes"); + + for (size_t index = 0; index < nodes->GetSize(); index++) { + CefRefPtr node = nodes->GetDictionary(index); + if (node) { + int node_id = node->GetInt("id"); + OsrAXNode* axNode = GetNode(node_id); + // Create if it is a new one + if (axNode) { + axNode->UpdateValue(node); + } else { + axNode = OsrAXNode::CreateNode(node, this); + accessibility_node_map_[node_id] = axNode; + } + } + } + } + } +} + +void OsrAccessibilityHelper::UpdateFocusedNode( + CefRefPtr update) { + if (update && update->HasKey("nodes")) { + CefRefPtr nodes = update->GetList("nodes"); + + for (size_t index = 0; index < nodes->GetSize(); index++) { + CefRefPtr node = nodes->GetDictionary(index); + if (node) { + int node_id = node->GetInt("id"); + OsrAXNode* axNode = GetNode(node_id); + // Create if it is a new one + if (axNode) { + axNode->UpdateValue(node); + } else { + axNode = OsrAXNode::CreateNode(node, this); + accessibility_node_map_[node_id] = axNode; + } + } + } + } + // Now Notify Screen Reader + OsrAXNode* axNode = GetFocusedNode(); + // Fallback to Root + if (!axNode) + axNode = GetRootNode(); + + axNode->NotifyAccessibilityEvent("focus"); +} + +void OsrAccessibilityHelper::Reset() { + accessibility_node_map_.clear(); + root_node_id_ = focused_node_id_ = -1; +} + +void OsrAccessibilityHelper::DestroyNode(OsrAXNode* node) { + if (node) { + int numChilds = node->GetChildCount(); + if (numChilds > 0) { + for (int i = 0; i < numChilds; i++) { + DestroyNode(node->ChildAtIndex(i)); + } + } + accessibility_node_map_.erase(node->OsrAXNodeId()); + node->Destroy(); + } +} + +OsrAXNode* OsrAccessibilityHelper::GetNode(int nodeId) const { + if (nodeId != -1 && + accessibility_node_map_.find(nodeId) != accessibility_node_map_.end()) { + return accessibility_node_map_.at(nodeId); + } + + return NULL; +} + +} // namespace client diff --git a/tests/cefclient/browser/osr_accessibility_helper.h b/tests/cefclient/browser/osr_accessibility_helper.h new file mode 100644 index 000000000..c6c337576 --- /dev/null +++ b/tests/cefclient/browser/osr_accessibility_helper.h @@ -0,0 +1,63 @@ +// Copyright 2017 The Chromium Embedded Framework Authors. Portions copyright +// 2013 The Chromium Authors. All rights reserved. Use of this source code is +// governed by a BSD-style license that can be found in the LICENSE file. + +#ifndef CEF_TESTS_CEFCLIENT_BROWSER_OSR_ACCESSIBILITY_HELPER_H_ +#define CEF_TESTS_CEFCLIENT_BROWSER_OSR_ACCESSIBILITY_HELPER_H_ + +#include + +#include "include/cef_browser.h" + +namespace client { + +class OsrAXNode; + +// Helper class that abstracts Renderer Accessibility tree and provides a +// uniform interface to be consumed by IAccessible interface on Windows and +// NSAccessibility implementation on Mac in CefClient. +class OsrAccessibilityHelper { + public: + OsrAccessibilityHelper(CefRefPtr value, + CefRefPtr browser); + + void UpdateAccessibilityTree(CefRefPtr value); + + OsrAXNode* GetRootNode() const { + return GetNode(root_node_id_); + } + + OsrAXNode* GetFocusedNode() const { + return GetNode(focused_node_id_); + } + + CefWindowHandle GetWindowHandle() const { + return browser_->GetHost()->GetWindowHandle(); + } + + CefRefPtr GetBrowser() const { + return browser_; + }; + + OsrAXNode* GetNode(int nodeId) const; + + private: + OsrAXNode* CreateNode(OsrAXNode* parent, CefRefPtr value); + + void Reset(); + + void UpdateLayout(CefRefPtr update); + + void UpdateFocusedNode(CefRefPtr update); + + // Destroy the node and remove from Map + void DestroyNode(OsrAXNode* node); + int root_node_id_; + int focused_node_id_; + CefRefPtr browser_; + std::map accessibility_node_map_; +}; + +} // namespace client + +#endif // CEF_TESTS_CEFCLIENT_BROWSER_OSR_ACCESSIBILITY_HELPER_H_ diff --git a/tests/cefclient/browser/osr_accessibility_node.cc b/tests/cefclient/browser/osr_accessibility_node.cc new file mode 100644 index 000000000..b0615c822 --- /dev/null +++ b/tests/cefclient/browser/osr_accessibility_node.cc @@ -0,0 +1,102 @@ +// Copyright 2017 The Chromium Embedded Framework Authors. Portions copyright +// 2013 The Chromium Authors. All rights reserved. Use of this source code is +// governed by a BSD-style license that can be found in the LICENSE file. + +// Base class implementation for CEF Acccessibility node. This is subclassed and +// used by both IAccessible/NSAccessibility protocol implementation. + +#include "tests/cefclient/browser/osr_accessibility_node.h" + +#include "tests/cefclient/browser/osr_accessibility_helper.h" + +namespace client { + +OsrAXNode::OsrAXNode(CefRefPtr value, + OsrAccessibilityHelper* helper) + : node_id_(-1), platform_accessibility_(NULL), parent_(NULL), + accessibility_helper_(helper) { + UpdateValue(value); +} + +void OsrAXNode::UpdateValue(CefRefPtr value) { + if (value && value->HasKey("id")) { + node_id_ = value->GetInt("id"); + + if (value->HasKey("role")) + role_ = value->GetString("role"); + + if (value->HasKey("child_ids")) { + CefRefPtr childs = value->GetList("child_ids"); + // Reset child Ids + child_ids_.clear(); + for(size_t idx = 0; idx < childs->GetSize(); idx++) + child_ids_.push_back(childs->GetInt(idx)); + } + // Update Location + if (value->HasKey("location")) { + CefRefPtr loc = value->GetDictionary("location"); + if (loc) { + location_ = CefRect(loc->GetDouble("x"), loc->GetDouble("y"), + loc->GetDouble("width"), loc->GetDouble("height")); + } + } + // Update offsets + if (value->HasKey("offset_container_id")) { + offset_container_id_ = value->GetInt("offset_container_id"); + } + // Update attributes + if (value->HasKey("attributes")) { + attributes_ = value->GetDictionary("attributes"); + + if (attributes_ && attributes_->HasKey("name")) + name_ = attributes_->GetString("name"); + if (attributes_ && attributes_->HasKey("value")) + value_ = attributes_->GetString("value"); + if (attributes_ && attributes_->HasKey("description")) + description_ = attributes_->GetString("description"); + } + } +} + +CefWindowHandle OsrAXNode::GetWindowHandle() const { + if (accessibility_helper_) + return accessibility_helper_->GetWindowHandle(); + return NULL; +} + +CefRefPtr OsrAXNode::GetBrowser() const { + if (accessibility_helper_) + return accessibility_helper_->GetBrowser(); + return NULL; +} + +void OsrAXNode::SetParent(OsrAXNode* parent) { + parent_ = parent; +} + +CefRect OsrAXNode::AxLocation() const { + CefRect loc = location_; + OsrAXNode* offsetNode = accessibility_helper_->GetNode(offset_container_id_); + // Add offset from parent Lcoation + if (offsetNode) { + CefRect offset = offsetNode->AxLocation(); + loc.x += offset.x; + loc.y += offset.y; + } + return loc; +} + +OsrAXNode* OsrAXNode::ChildAtIndex(int index) const { + if (index < GetChildCount()) + return accessibility_helper_->GetNode(child_ids_[index]); + else + return NULL; +} + +// Create and return the platform specific OsrAXNode Object +OsrAXNode* OsrAXNode::CreateNode(CefRefPtr value, + OsrAccessibilityHelper* helper) { + return new OsrAXNode(value, helper); +} + +} // namespace client diff --git a/tests/cefclient/browser/osr_accessibility_node.h b/tests/cefclient/browser/osr_accessibility_node.h new file mode 100644 index 000000000..fd56a3d96 --- /dev/null +++ b/tests/cefclient/browser/osr_accessibility_node.h @@ -0,0 +1,116 @@ +// Copyright 2017 The Chromium Embedded Framework Authors. Portions copyright +// 2013 The Chromium Authors. All rights reserved. Use of this source code is +// governed by a BSD-style license that can be found in the LICENSE file. + +#ifndef CEF_TESTS_CEFCLIENT_BROWSER_OSR_ACCESSIBILITY_NODE_H_ +#define CEF_TESTS_CEFCLIENT_BROWSER_OSR_ACCESSIBILITY_NODE_H_ +#pragma once + +#include + +#include "include/cef_browser.h" + +#if defined(OS_MACOSX) +#ifdef __OBJC__ +@class NSObject; +#else +class NSObject; +#endif +typedef NSObject CefNativeAccessible; +#elif defined(OS_WIN) +struct IAccessible; +typedef IAccessible CefNativeAccessible; +#else +#error "Unsupported platform" +#endif + +namespace client { + +class OsrAccessibilityHelper; + +// OsrAXNode is the base class for implementation for the NSAccessibility +// protocol for interacting with VoiceOver and other accessibility clients. +class OsrAXNode { + public: + // Create and return the platform specific OsrAXNode Object. + static OsrAXNode* CreateNode(CefRefPtr value, + OsrAccessibilityHelper* helper); + + // Update Value. + void UpdateValue(CefRefPtr value); + + // Fire a platform-specific notification that an event has occurred on + // this object. + void NotifyAccessibilityEvent(std::string event_type) const; + + // Call Destroy rather than deleting this, because the subclass may + // use reference counting. + void Destroy(); + + // Return NSAccessibility Object for Mac/ IAccessible for Windows + CefNativeAccessible* GetNativeAccessibleObject(OsrAXNode* parent); + + CefNativeAccessible* GetParentAccessibleObject() const { + return parent_? parent_->platform_accessibility_ : NULL; + } + + OsrAccessibilityHelper* GetAccessibilityHelper() const { + return accessibility_helper_; + }; + + int GetChildCount() const { + return static_cast(child_ids_.size()); + } + + // Return the Child at the specified index + OsrAXNode* ChildAtIndex(int index) const; + + const CefString& AxRole() const { + return role_; + } + + int OsrAXNodeId() const { + return node_id_; + } + + const CefString& AxValue() const { + return value_; + } + + const CefString& AxName() const { + return name_; + } + + const CefString& AxDescription() const { + return description_; + } + + CefRect AxLocation() const; + + CefWindowHandle GetWindowHandle() const; + + CefRefPtr GetBrowser() const; + + void SetParent(OsrAXNode* parent); + + protected: + OsrAXNode(CefRefPtr value, + OsrAccessibilityHelper* helper); + + int node_id_; + CefString role_; + CefString value_; + CefString name_; + CefString description_; + CefRect location_; + std::vector child_ids_; + CefNativeAccessible* platform_accessibility_; + OsrAXNode* parent_; + int offset_container_id_; + OsrAccessibilityHelper* accessibility_helper_; + CefRefPtr attributes_; +}; + +} // namespace client + +#endif // CEF_TESTS_CEFCLIENT_BROWSER_OSR_ACCESSIBILITY_NODE_H_ diff --git a/tests/cefclient/browser/osr_accessibility_node_mac.mm b/tests/cefclient/browser/osr_accessibility_node_mac.mm new file mode 100644 index 000000000..07cfa3cba --- /dev/null +++ b/tests/cefclient/browser/osr_accessibility_node_mac.mm @@ -0,0 +1,498 @@ +// Copyright 2017 The Chromium Embedded Framework Authors. Portions copyright +// 2013 The Chromium Authors. All rights reserved. Use of this source code is +// governed by a BSD-style license that can be found in the LICENSE file. + +// Sample implementation for the NSAccessibility protocol for interacting with +// VoiceOver and other accessibility clients. + +#include "tests/cefclient/browser/osr_accessibility_node.h" + +#import +#import + +#include "tests/cefclient/browser/osr_accessibility_helper.h" + +namespace { + +NSString* AxRoleToNSAxRole(const std::string& role_string) { + if (role_string == "abbr") + return NSAccessibilityGroupRole; + if (role_string == "alertDialog") + return NSAccessibilityGroupRole; + if (role_string == "alert") + return NSAccessibilityGroupRole; + if (role_string == "annotation") + return NSAccessibilityUnknownRole; + if (role_string == "application") + return NSAccessibilityGroupRole; + if (role_string == "article") + return NSAccessibilityGroupRole; + if (role_string == "audio") + return NSAccessibilityGroupRole; + if (role_string == "banner") + return NSAccessibilityGroupRole; + if (role_string == "blockquote") + return NSAccessibilityGroupRole; + if (role_string == "busyIndicator") + return NSAccessibilityBusyIndicatorRole; + if (role_string == "button") + return NSAccessibilityButtonRole; + if (role_string == "buttonDropDown") + return NSAccessibilityButtonRole; + if (role_string == "canvas") + return NSAccessibilityImageRole; + if (role_string == "caption") + return NSAccessibilityGroupRole; + if (role_string == "checkBox") + return NSAccessibilityCheckBoxRole; + if (role_string == "colorWell") + return NSAccessibilityColorWellRole; + if (role_string == "column") + return NSAccessibilityColumnRole; + if (role_string == "comboBox") + return NSAccessibilityComboBoxRole; + if (role_string == "complementary") + return NSAccessibilityGroupRole; + if (role_string == "contentInfo") + return NSAccessibilityGroupRole; + if (role_string == "definition") + return NSAccessibilityGroupRole; + if (role_string == "descriptionListDetail") + return NSAccessibilityGroupRole; + if (role_string == "descriptionList") + return NSAccessibilityListRole; + if (role_string == "descriptionListTerm") + return NSAccessibilityGroupRole; + if (role_string == "details") + return NSAccessibilityGroupRole; + if (role_string == "dialog") + return NSAccessibilityGroupRole; + if (role_string == "directory") + return NSAccessibilityListRole; + if (role_string == "disclosureTriangle") + return NSAccessibilityDisclosureTriangleRole; + if (role_string == "div") + return NSAccessibilityGroupRole; + if (role_string == "document") + return NSAccessibilityGroupRole; + if (role_string == "embeddedObject") + return NSAccessibilityGroupRole; + if (role_string == "figcaption") + return NSAccessibilityGroupRole; + if (role_string == "figure") + return NSAccessibilityGroupRole; + if (role_string == "footer") + return NSAccessibilityGroupRole; + if (role_string == "form") + return NSAccessibilityGroupRole; + if (role_string == "grid") + return NSAccessibilityGroupRole; + if (role_string == "group") + return NSAccessibilityGroupRole; + if (role_string == "iframe") + return NSAccessibilityGroupRole; + if (role_string == "iframePresentational") + return NSAccessibilityGroupRole; + if (role_string == "ignored") + return NSAccessibilityUnknownRole; + if (role_string == "imageMapLink") + return NSAccessibilityLinkRole; + if (role_string == "imageMap") + return NSAccessibilityGroupRole; + if (role_string == "image") + return NSAccessibilityImageRole; + if (role_string == "labelText") + return NSAccessibilityGroupRole; + if (role_string == "legend") + return NSAccessibilityGroupRole; + if (role_string == "link") + return NSAccessibilityLinkRole; + if (role_string == "listBoxOption") + return NSAccessibilityStaticTextRole; + if (role_string == "listBox") + return NSAccessibilityListRole; + if (role_string == "listItem") + return NSAccessibilityGroupRole; + if (role_string == "list") + return NSAccessibilityListRole; + if (role_string == "log") + return NSAccessibilityGroupRole; + if (role_string == "main") + return NSAccessibilityGroupRole; + if (role_string == "mark") + return NSAccessibilityGroupRole; + if (role_string == "marquee") + return NSAccessibilityGroupRole; + if (role_string == "math") + return NSAccessibilityGroupRole; + if (role_string == "menu") + return NSAccessibilityMenuRole; + if (role_string == "menuBar") + return NSAccessibilityMenuBarRole; + if (role_string == "menuButton") + return NSAccessibilityButtonRole; + if (role_string == "menuItem") + return NSAccessibilityMenuItemRole; + if (role_string == "menuItemCheckBox") + return NSAccessibilityMenuItemRole; + if (role_string == "menuItemRadio") + return NSAccessibilityMenuItemRole; + if (role_string == "menuListOption") + return NSAccessibilityMenuItemRole; + if (role_string == "menuListPopup") + return NSAccessibilityUnknownRole; + if (role_string == "meter") + return NSAccessibilityProgressIndicatorRole; + if (role_string == "navigation") + return NSAccessibilityGroupRole; + if (role_string == "note") + return NSAccessibilityGroupRole; + if (role_string == "outline") + return NSAccessibilityOutlineRole; + if (role_string == "paragraph") + return NSAccessibilityGroupRole; + if (role_string == "popUpButton") + return NSAccessibilityPopUpButtonRole; + if (role_string == "pre") + return NSAccessibilityGroupRole; + if (role_string == "presentational") + return NSAccessibilityGroupRole; + if (role_string == "progressIndicator") + return NSAccessibilityProgressIndicatorRole; + if (role_string == "radioButton") + return NSAccessibilityRadioButtonRole; + if (role_string == "radioGroup") + return NSAccessibilityRadioGroupRole; + if (role_string == "region") + return NSAccessibilityGroupRole; + if (role_string == "row") + return NSAccessibilityRowRole; + if (role_string == "ruler") + return NSAccessibilityRulerRole; + if (role_string == "scrollBar") + return NSAccessibilityScrollBarRole; + if (role_string == "search") + return NSAccessibilityGroupRole; + if (role_string == "searchBox") + return NSAccessibilityTextFieldRole; + if (role_string == "slider") + return NSAccessibilitySliderRole; + if (role_string == "sliderThumb") + return NSAccessibilityValueIndicatorRole; + if (role_string == "spinButton") + return NSAccessibilityIncrementorRole; + if (role_string == "splitter") + return NSAccessibilitySplitterRole; + if (role_string == "staticText") + return NSAccessibilityStaticTextRole; + if (role_string == "status") + return NSAccessibilityGroupRole; + if (role_string == "svgRoot") + return NSAccessibilityGroupRole; + if (role_string == "switch") + return NSAccessibilityCheckBoxRole; + if (role_string == "tabGroup") + return NSAccessibilityTabGroupRole; + if (role_string == "tabList") + return NSAccessibilityTabGroupRole; + if (role_string == "tabPanel") + return NSAccessibilityGroupRole; + if (role_string == "tab") + return NSAccessibilityRadioButtonRole; + if (role_string == "tableHeaderContainer") + return NSAccessibilityGroupRole; + if (role_string == "table") + return NSAccessibilityTableRole; + if (role_string == "textField") + return NSAccessibilityTextFieldRole; + if (role_string == "time") + return NSAccessibilityGroupRole; + if (role_string == "timer") + return NSAccessibilityGroupRole; + if (role_string == "toggleButton") + return NSAccessibilityCheckBoxRole; + if (role_string == "toolbar") + return NSAccessibilityToolbarRole; + if (role_string == "treeGrid") + return NSAccessibilityTableRole; + if (role_string == "treeItem") + return NSAccessibilityRowRole; + if (role_string == "tree") + return NSAccessibilityOutlineRole; + if (role_string == "unknown") + return NSAccessibilityUnknownRole; + if (role_string == "tooltip") + return NSAccessibilityGroupRole; + if (role_string == "video") + return NSAccessibilityGroupRole; + if (role_string == "window") + return NSAccessibilityWindowRole; + return [NSString stringWithUTF8String:role_string.c_str()]; +} + +inline int MiddleX(const CefRect& rect) { + return rect.x + rect.width / 2; +} + +inline int MiddleY(const CefRect& rect) { + return rect.y + rect.height / 2; +} + +} // namespace + +// OsrAXNodeObject is sample implementation for the NSAccessibility protocol +// for interacting with VoiceOver and other accessibility clients. +@interface OsrAXNodeObject : NSObject { + // OsrAXNode* proxy object + client::OsrAXNode* node_; + CefNativeAccessible* parent_; +} + +- (id) init:(client::OsrAXNode*) node; ++ (OsrAXNodeObject *) elementWithNode:(client::OsrAXNode*) node; +@end + + +@implementation OsrAXNodeObject +- (id)init:(client::OsrAXNode*)node { + node_ = node; + parent_ = node_->GetParentAccessibleObject(); + if (!parent_) { + parent_ = node_->GetWindowHandle(); + } + return self; +} + ++ (OsrAXNodeObject *)elementWithNode:(client::OsrAXNode*)node { + // We manage the release ourself + return [[OsrAXNodeObject alloc] init:node]; +} + +- (BOOL)isEqual:(id)object { + if ([object isKindOfClass:[OsrAXNodeObject self]]) { + OsrAXNodeObject* other = object; + return (node_ == other->node_); + } else { + return NO; + } +} + +// Utility methods to map AX information received from renderer +// to platform properties +- (NSString*) axRole { + // Get the Role from CefAccessibilityHelper and Map to NSRole + return AxRoleToNSAxRole(node_->AxRole()); +} + +- (NSString*) axDescription { + std::string desc = node_->AxDescription(); + return [NSString stringWithUTF8String:desc.c_str()]; +} + +- (NSString*) axName { + std::string desc = node_->AxName(); + return [NSString stringWithUTF8String:desc.c_str()]; +} + +- (NSString*) axValue { + std::string desc = node_->AxValue(); + return [NSString stringWithUTF8String:desc.c_str()]; +} + +- (void)doMouseClick: (cef_mouse_button_type_t)type { + CefRefPtr browser = node_->GetBrowser(); + if (browser) { + CefMouseEvent mouse_event; + const CefRect& rect = node_->AxLocation(); + mouse_event.x = MiddleX(rect); + mouse_event.y = MiddleY(rect); + + mouse_event.modifiers = 0; + browser->GetHost()->SendMouseClickEvent(mouse_event, type, false, 1); + browser->GetHost()->SendMouseClickEvent(mouse_event, type, true, 1); + } +} + +- (NSMutableArray *) getKids { + int numChilds = node_->GetChildCount(); + if (numChilds > 0) { + NSMutableArray* kids = [NSMutableArray arrayWithCapacity:numChilds]; + for(int index = 0; indexChildAtIndex(index); + [kids addObject: child ? child->GetNativeAccessibleObject(node_) : nil]; + } + return kids; + } + return nil; +} + +- (NSPoint) position { + CefRect cef_rect = node_->AxLocation(); + NSPoint origin = NSMakePoint(cef_rect.x, cef_rect.y); + NSSize size = NSMakeSize(cef_rect.width, cef_rect.height); + + NSView* view = node_->GetWindowHandle(); + origin.y = NSHeight([view bounds]) - origin.y; + NSPoint originInWindow = [view convertPoint:origin toView:nil]; + + NSRect point_rect = NSMakeRect(originInWindow.x, originInWindow.y, 0, 0); + NSPoint originInScreen = [[view window] + convertRectToScreen:point_rect].origin; + + originInScreen.y = originInScreen.y - size.height; + return originInScreen; +} + +- (NSSize) size { + CefRect cef_rect = node_->AxLocation(); + NSRect rect = NSMakeRect(cef_rect.x, cef_rect.y, + cef_rect.width, cef_rect.height); + NSView* view = node_->GetWindowHandle(); + rect = [[view window]convertRectToScreen: rect]; + return rect.size; +} + +// +// accessibility protocol +// + +// attributes + +- (BOOL)accessibilityIsIgnored { + return NO; +} + +- (NSArray *)accessibilityAttributeNames { + static NSArray* attributes = nil; + if (attributes == nil) { + attributes = [[NSArray alloc] initWithObjects: + NSAccessibilityRoleAttribute, + NSAccessibilityRoleDescriptionAttribute, + NSAccessibilityChildrenAttribute, + NSAccessibilityValueAttribute, + NSAccessibilityTitleAttribute, + NSAccessibilityDescriptionAttribute, + NSAccessibilityFocusedAttribute, + NSAccessibilityParentAttribute, + NSAccessibilityWindowAttribute, + NSAccessibilityTopLevelUIElementAttribute, + NSAccessibilityPositionAttribute, + NSAccessibilitySizeAttribute, + nil]; + } + return attributes; +} + +- (id)accessibilityAttributeValue:(NSString *)attribute { + if (!node_) + return nil; + if ([attribute isEqualToString:NSAccessibilityRoleAttribute]) { + return [self axRole]; + } else if ([attribute isEqualToString: + NSAccessibilityRoleDescriptionAttribute]) { + return NSAccessibilityRoleDescription([self axRole], nil); + } else if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) { + // Just check if the app thinks we're focused. + id focusedElement = [NSApp accessibilityAttributeValue: + NSAccessibilityFocusedUIElementAttribute]; + return [NSNumber numberWithBool:[focusedElement isEqual:self]]; + } else if ([attribute isEqualToString:NSAccessibilityParentAttribute]) { + return NSAccessibilityUnignoredAncestor(parent_); + } else if ([attribute isEqualToString:NSAccessibilityChildrenAttribute]) { + return NSAccessibilityUnignoredChildren([self getKids]); + } else if ([attribute isEqualToString:NSAccessibilityWindowAttribute]) { + // We're in the same window as our parent. + return [parent_ + accessibilityAttributeValue:NSAccessibilityWindowAttribute]; + } else if ([attribute isEqualToString: + NSAccessibilityTopLevelUIElementAttribute]) { + // We're in the same top level element as our parent. + return [parent_ accessibilityAttributeValue: + NSAccessibilityTopLevelUIElementAttribute]; + } else if ([attribute isEqualToString:NSAccessibilityPositionAttribute]) { + return [NSValue valueWithPoint:[self position]]; + } else if ([attribute isEqualToString:NSAccessibilitySizeAttribute]) { + return [NSValue valueWithSize:[self size]]; + } else if ([attribute isEqualToString:NSAccessibilityDescriptionAttribute]) { + return [self axDescription]; + } else if ([attribute isEqualToString:NSAccessibilityValueAttribute]) { + return [self axValue]; + } else if ([attribute isEqualToString:NSAccessibilityTitleAttribute]) { + return [self axName]; + } + return nil; +} + +- (id)accessibilityHitTest:(NSPoint)point { + return NSAccessibilityUnignoredAncestor(self); +} + +- (NSArray *)accessibilityActionNames { + return [NSArray arrayWithObject:NSAccessibilityPressAction]; +} + +- (NSString *)accessibilityActionDescription:(NSString *)action { + return NSAccessibilityActionDescription(action); +} + +- (void)accessibilityPerformAction:(NSString *)action { + if ([action isEqualToString:NSAccessibilityPressAction]) { + // Do Click on Default action + [self doMouseClick:MBT_LEFT]; + } else if ([action isEqualToString:NSAccessibilityShowMenuAction]) { + // Right click for Context Menu + [self doMouseClick:MBT_RIGHT]; + } +} + +- (id)accessibilityFocusedUIElement { + return NSAccessibilityUnignoredAncestor(self); +} + +- (BOOL)accessibilityNotifiesWhenDestroyed { + // Indicate that BrowserAccessibilityCocoa will post a notification when it's + // destroyed (see -detach). This allows VoiceOver to do some internal things + // more efficiently. + return YES; +} + +@end + +namespace client { + +void OsrAXNode::NotifyAccessibilityEvent(std::string event_type) const { + if (event_type == "focus") { + NSAccessibilityPostNotification(GetWindowHandle(), + NSAccessibilityFocusedUIElementChangedNotification); + } else if (event_type == "textChanged") { + NSAccessibilityPostNotification(GetWindowHandle(), + NSAccessibilityTitleChangedNotification); + } else if (event_type == "valueChanged"){ + NSAccessibilityPostNotification(GetWindowHandle(), + NSAccessibilityValueChangedNotification); + } else if (event_type == "textSelectionChanged") { + NSAccessibilityPostNotification(GetWindowHandle(), + NSAccessibilityValueChangedNotification); + } +} + +void OsrAXNode::Destroy() { + if (platform_accessibility_) { + NSAccessibilityPostNotification(platform_accessibility_, + NSAccessibilityUIElementDestroyedNotification); + } + + delete this; +} + +// Create and return NSAccessibility Implementation Object for Mac +CefNativeAccessible* OsrAXNode::GetNativeAccessibleObject( + client::OsrAXNode* parent) { + if (!platform_accessibility_) { + platform_accessibility_ = [OsrAXNodeObject elementWithNode:this]; + SetParent(parent); + } + return platform_accessibility_; +} + +} // namespace client diff --git a/tests/cefclient/browser/osr_accessibility_node_win.cc b/tests/cefclient/browser/osr_accessibility_node_win.cc new file mode 100644 index 000000000..d4f57d90c --- /dev/null +++ b/tests/cefclient/browser/osr_accessibility_node_win.cc @@ -0,0 +1,674 @@ +// Copyright 2017 The Chromium Embedded Framework Authors. Portions copyright +// 2013 The Chromium Authors. All rights reserved. Use of this source code is +// governed by a BSD-style license that can be found in the LICENSE file. + +// This class implements our accessible proxy object that handles moving +// data back and forth between MSAA clients and CefClient renderers. +// Sample implementation based on ui\accessibility\ax_platform_node_win.h + +#include "tests/cefclient/browser/osr_accessibility_node.h" + +#if defined(CEF_USE_ATL) + +#include +#include +#include + +#include "tests/cefclient/browser/osr_accessibility_helper.h" + +namespace client { + +// Return CO_E_OBJNOTCONNECTED for accessible objects thar still exists but the +// window and/or object it references has been destroyed. +#define DATACHECK(node) (node) ? S_OK : CO_E_OBJNOTCONNECTED +#define VALID_CHILDID(varChild) ((varChild.vt == VT_I4)) + +namespace { + +// Helper function to convert a rectangle from client coordinates to screen +// coordinates. +void ClientToScreen(HWND hwnd, LPRECT lpRect) { + if (lpRect) { + POINT ptTL = { lpRect->left, lpRect->top }; + POINT ptBR = { lpRect->right, lpRect->bottom }; + // Win32 API only provides the call for a point. + ClientToScreen(hwnd, &ptTL); + ClientToScreen(hwnd, &ptBR); + SetRect(lpRect, ptTL.x, ptTL.y, ptBR.x, ptBR.y); + } +} + +// Helper function to convert to MSAARole +int AxRoleToMSAARole(const std::string& role_string) { + if (role_string == "alert") + return ROLE_SYSTEM_ALERT; + if (role_string == "application") + return ROLE_SYSTEM_APPLICATION; + if (role_string == "buttonDropDown") + return ROLE_SYSTEM_BUTTONDROPDOWN; + if (role_string == "popUpButton") + return ROLE_SYSTEM_BUTTONMENU; + if (role_string == "checkBox") + return ROLE_SYSTEM_CHECKBUTTON; + if (role_string == "comboBox") + return ROLE_SYSTEM_COMBOBOX; + if (role_string == "dialog") + return ROLE_SYSTEM_DIALOG; + if (role_string == "group") + return ROLE_SYSTEM_GROUPING; + if (role_string == "image") + return ROLE_SYSTEM_GRAPHIC; + if (role_string == "link") + return ROLE_SYSTEM_LINK; + if (role_string == "locationBar") + return ROLE_SYSTEM_GROUPING; + if (role_string == "menuBar") + return ROLE_SYSTEM_MENUBAR; + if (role_string == "menuItem") + return ROLE_SYSTEM_MENUITEM; + if (role_string == "menuListPopup") + return ROLE_SYSTEM_MENUPOPUP; + if (role_string == "tree") + return ROLE_SYSTEM_OUTLINE; + if (role_string == "treeItem") + return ROLE_SYSTEM_OUTLINEITEM; + if (role_string == "tab") + return ROLE_SYSTEM_PAGETAB; + if (role_string == "tabList") + return ROLE_SYSTEM_PAGETABLIST; + if (role_string == "pane") + return ROLE_SYSTEM_PANE; + if (role_string == "progressIndicator") + return ROLE_SYSTEM_PROGRESSBAR; + if (role_string == "button") + return ROLE_SYSTEM_PUSHBUTTON; + if (role_string == "radioButton") + return ROLE_SYSTEM_RADIOBUTTON; + if (role_string == "scrollBar") + return ROLE_SYSTEM_SCROLLBAR; + if (role_string == "splitter") + return ROLE_SYSTEM_SEPARATOR; + if (role_string == "slider") + return ROLE_SYSTEM_SLIDER; + if (role_string == "staticText") + return ROLE_SYSTEM_STATICTEXT; + if (role_string == "textField") + return ROLE_SYSTEM_TEXT; + if (role_string == "titleBar") + return ROLE_SYSTEM_TITLEBAR; + if (role_string == "toolbar") + return ROLE_SYSTEM_TOOLBAR; + if (role_string == "webView") + return ROLE_SYSTEM_GROUPING; + if (role_string == "window") + return ROLE_SYSTEM_WINDOW; + if (role_string == "client") + return ROLE_SYSTEM_CLIENT; + // This is the default role for MSAA. + return ROLE_SYSTEM_CLIENT; +} + +static inline int MiddleX(const CefRect& rect) { + return rect.x + rect.width / 2; +} + +static inline int MiddleY(const CefRect& rect) { + return rect.y + rect.height / 2; +} + +} // namespace + +struct CefIAccessible : public IAccessible { + public: + // Implement IUnknown + STDMETHODIMP QueryInterface(REFIID riid, void** ppvObject); + STDMETHODIMP_(ULONG) AddRef(); + STDMETHODIMP_(ULONG) Release(); + + // + // IAccessible methods. + // + // Retrieves the child element or child object at a given point on the screen. + STDMETHODIMP accHitTest(LONG x_left, LONG y_top, VARIANT* child) override; + + // Performs the object's default action. + STDMETHODIMP accDoDefaultAction(VARIANT var_id) override; + + // Retrieves the specified object's current screen location. + STDMETHODIMP accLocation(LONG* x_left, LONG* y_top, LONG* width, + LONG* height, VARIANT var_id) override; + + // Traverses to another UI element and retrieves the object. + STDMETHODIMP accNavigate(LONG nav_dir, VARIANT start, VARIANT* end) override; + + // Retrieves an IDispatch interface pointer for the specified child. + STDMETHODIMP get_accChild(VARIANT var_child, IDispatch** disp_child) override; + + // Retrieves the number of accessible children. + STDMETHODIMP get_accChildCount(LONG* child_count) override; + + // Retrieves a string that describes the object's default action. + STDMETHODIMP get_accDefaultAction(VARIANT var_id, + BSTR* default_action) override; + + // Retrieves the tooltip description. + STDMETHODIMP get_accDescription(VARIANT var_id, BSTR* desc) override; + + // Retrieves the object that has the keyboard focus. + STDMETHODIMP get_accFocus(VARIANT* focus_child) override; + + // Retrieves the specified object's shortcut. + STDMETHODIMP get_accKeyboardShortcut(VARIANT var_id, + BSTR* access_key) override; + + // Retrieves the name of the specified object. + STDMETHODIMP get_accName(VARIANT var_id, BSTR* name) override; + + // Retrieves the IDispatch interface of the object's parent. + STDMETHODIMP get_accParent(IDispatch** disp_parent) override; + + // Retrieves information describing the role of the specified object. + STDMETHODIMP get_accRole(VARIANT var_id, VARIANT* role) override; + + // Retrieves the current state of the specified object. + STDMETHODIMP get_accState(VARIANT var_id, VARIANT* state) override; + + // Gets the help string for the specified object. + STDMETHODIMP get_accHelp(VARIANT var_id, BSTR* help) override; + + // Retrieve or set the string value associated with the specified object. + // Setting the value is not typically used by screen readers, but it's + // used frequently by automation software. + STDMETHODIMP get_accValue(VARIANT var_id, BSTR* value) override; + STDMETHODIMP put_accValue(VARIANT var_id, BSTR new_value) override; + + // IAccessible methods not implemented. + STDMETHODIMP get_accSelection(VARIANT* selected) override; + STDMETHODIMP accSelect(LONG flags_sel, VARIANT var_id) override; + STDMETHODIMP get_accHelpTopic(BSTR* help_file, VARIANT var_id, + LONG* topic_id) override; + STDMETHODIMP put_accName(VARIANT var_id, BSTR put_name) override; + + // Implement IDispatch + STDMETHODIMP GetTypeInfoCount(unsigned int FAR* pctinfo); + STDMETHODIMP GetTypeInfo(unsigned int iTInfo, LCID lcid, + ITypeInfo FAR* FAR* ppTInfo); + STDMETHODIMP GetIDsOfNames(REFIID riid, OLECHAR FAR* FAR* rgszNames, + unsigned int cNames, LCID lcid, + DISPID FAR* rgDispId); + STDMETHODIMP Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, + DISPPARAMS FAR* pDispParams, VARIANT FAR* pVarResult, + EXCEPINFO FAR* pExcepInfo, unsigned int FAR* puArgErr); + + CefIAccessible(OsrAXNode* node) : node_(node), ref_count_(0) { + } + + // Remove the node reference when OsrAXNode is destroyed, so that + // MSAA clients get CO_E_OBJNOTCONNECTED + void MarkDestroyed() { + node_ = NULL; + } + + protected: + // Ref Count + ULONG ref_count_; + // OsrAXNode* proxy object + OsrAXNode* node_; +}; + +// Implement IUnknown +// ********************* + +// Handles ref counting and querying for other supported interfaces. +// We only support, IUnknown, IDispatch and IAccessible. +STDMETHODIMP CefIAccessible::QueryInterface(REFIID riid, void** ppvObject) { + if (riid == IID_IAccessible) + *ppvObject = static_cast(this); + else if (riid == IID_IDispatch) + *ppvObject = static_cast(this); + else if (riid == IID_IUnknown) + *ppvObject = static_cast(this); + else + *ppvObject = NULL; + + if (*ppvObject) + reinterpret_cast(*ppvObject)->AddRef(); + + return (*ppvObject) ? S_OK : E_NOINTERFACE; +} + +// Increments COM objects refcount required by IUnknown for reference counting +STDMETHODIMP_(ULONG) CefIAccessible::AddRef() { + return InterlockedIncrement((LONG volatile*)&ref_count_); +} + +STDMETHODIMP_(ULONG) CefIAccessible::Release() { + ULONG ulRefCnt = InterlockedDecrement((LONG volatile*)&ref_count_); + if (ulRefCnt == 0) { + // Remove reference from OsrAXNode + if (node_) + node_->Destroy(); + delete this; + } + + return ulRefCnt; +} + +// Implement IAccessible +// ********************* + +// Returns the parent IAccessible in the form of an IDispatch interface. +STDMETHODIMP CefIAccessible::get_accParent(IDispatch **ppdispParent) { + HRESULT retCode = DATACHECK(node_); + if (SUCCEEDED(retCode)) { + if (ppdispParent) { + CefNativeAccessible* parent = node_->GetParentAccessibleObject(); + if (!parent) { + // Find our parent window + HWND hWnd = ::GetParent(node_->GetWindowHandle()); + // if we have a window attempt to get its IAccessible pointer + if (hWnd) { + AccessibleObjectFromWindow(hWnd, (DWORD)OBJID_CLIENT, + IID_IAccessible, (void**)(&parent)); + } + } + + if (parent) + parent->AddRef(); + *ppdispParent = parent; + retCode = (*ppdispParent) ? S_OK : S_FALSE; + } + } + else { + retCode = E_INVALIDARG; + } + return retCode; +} + +// Returns the number of children we have for this element. +STDMETHODIMP CefIAccessible::get_accChildCount(long *pcountChildren) { + HRESULT retCode = DATACHECK(node_); + if (SUCCEEDED(retCode) && pcountChildren) { + // Get Child node count for this from Accessibility tree + *pcountChildren = node_->GetChildCount(); + } else { + retCode = E_INVALIDARG; + } + return retCode; +} + +// Returns a child IAccessible object. +STDMETHODIMP CefIAccessible::get_accChild(VARIANT varChild, + IDispatch **ppdispChild) { + HRESULT retCode = DATACHECK(node_); + if (SUCCEEDED(retCode)) { + int numChilds = node_->GetChildCount(); + // Mark Leaf node if there are no child + if (numChilds <= 0) { + *ppdispChild = NULL; + return S_FALSE; + } else { + if (ppdispChild && VALID_CHILDID(varChild)) { + if (varChild.lVal == CHILDID_SELF) { + *ppdispChild = this; + } else { + // Convert to 0 based index and get Child Node. + OsrAXNode* child = node_->ChildAtIndex(varChild.lVal - 1); + // Fallback to focused node + if (!child) + child = node_->GetAccessibilityHelper()->GetFocusedNode(); + + *ppdispChild = child->GetNativeAccessibleObject(node_); + } + if (*ppdispChild == NULL) + retCode = S_FALSE; + else + (*ppdispChild)->AddRef(); + } + } + } + return retCode; +} + +// Check and returns the accessible name for element from accessibility tree +STDMETHODIMP CefIAccessible::get_accName(VARIANT varChild, BSTR *pszName) { + HRESULT retCode = DATACHECK(node_); + if (SUCCEEDED(retCode)) { + if (pszName && VALID_CHILDID(varChild)) { + std::string name = node_->AxName(); + CComBSTR bstrResult(name.c_str()); + *pszName = bstrResult.Detach(); + } + } else { + retCode = E_INVALIDARG; + } + return retCode; +} + +// Check and returns the value for element from accessibility tree +STDMETHODIMP CefIAccessible::get_accValue(VARIANT varChild, BSTR *pszValue) { + HRESULT retCode = DATACHECK(node_); + if (SUCCEEDED(retCode)) { + if (pszValue && VALID_CHILDID(varChild)) { + std::string name = node_->AxValue(); + CComBSTR bstrResult(name.c_str()); + *pszValue = bstrResult.Detach(); + } + } else { + retCode = E_INVALIDARG; + } + return retCode; +} + +// Check and returns the description for element from accessibility tree +STDMETHODIMP CefIAccessible::get_accDescription(VARIANT varChild, + BSTR* pszDescription) { + HRESULT retCode = DATACHECK(node_); + if (SUCCEEDED(retCode)) { + if (pszDescription && VALID_CHILDID(varChild)) { + std::string name = node_->AxDescription(); + CComBSTR bstrResult(name.c_str()); + *pszDescription = bstrResult.Detach(); + } + } else { + retCode = E_INVALIDARG; + } + return retCode; +} + +// Check and returns the MSAA Role for element from accessibility tree +STDMETHODIMP CefIAccessible::get_accRole(VARIANT varChild, VARIANT *pvarRole) { + HRESULT retCode = DATACHECK(node_); + if (SUCCEEDED(retCode)) { + // Get the accessibilty role and Map to MSAA Role + if (pvarRole) { + pvarRole->vt = VT_I4; + pvarRole->lVal = AxRoleToMSAARole(node_->AxRole()); + } else { + retCode = E_INVALIDARG; + } + } + return retCode; +} + +// Check and returns Accessibility State for element from accessibility tree +STDMETHODIMP CefIAccessible::get_accState(VARIANT varChild, + VARIANT *pvarState) { + HRESULT retCode = DATACHECK(node_); + if (SUCCEEDED(retCode)) { + if (pvarState) { + pvarState->vt = VT_I4; + pvarState->lVal = (GetFocus() == node_->GetWindowHandle()) ? + STATE_SYSTEM_FOCUSED : 0; + pvarState->lVal |= STATE_SYSTEM_PRESSED; + pvarState->lVal |= STATE_SYSTEM_FOCUSABLE; + + // For child + if (varChild.lVal == CHILDID_SELF) { + DWORD dwStyle = GetWindowLong(node_->GetWindowHandle(), GWL_STYLE); + pvarState->lVal |= ((dwStyle & WS_VISIBLE) == 0) ? + STATE_SYSTEM_INVISIBLE : 0; + pvarState->lVal |= ((dwStyle & WS_DISABLED) > 0) ? + STATE_SYSTEM_UNAVAILABLE : 0; + } + } else { + retCode = E_INVALIDARG; + } + } + return retCode; +} + +// Check and returns Accessibility Shortcut if any for element +STDMETHODIMP CefIAccessible::get_accKeyboardShortcut(VARIANT varChild, + BSTR* pszKeyboardShortcut) { + HRESULT retCode = DATACHECK(node_); + if (SUCCEEDED(retCode)) { + if (pszKeyboardShortcut && VALID_CHILDID(varChild)) + *pszKeyboardShortcut = ::SysAllocString(L"None"); + else + retCode = E_INVALIDARG; + } + return retCode; +} + +// Return focused element from the accessibility tree +STDMETHODIMP CefIAccessible::get_accFocus(VARIANT *pFocusChild) { + HRESULT retCode = DATACHECK(node_); + if (SUCCEEDED(retCode)) { + OsrAXNode* focusedNode = node_->GetAccessibilityHelper()->GetFocusedNode(); + CefNativeAccessible* nativeObj = NULL; + if (focusedNode) + nativeObj = focusedNode->GetNativeAccessibleObject(NULL); + + if (nativeObj) { + if (nativeObj == this) { + pFocusChild->vt = VT_I4; + pFocusChild->lVal = CHILDID_SELF; + } + else { + pFocusChild->vt = VT_DISPATCH; + pFocusChild->pdispVal = nativeObj; + pFocusChild->pdispVal->AddRef(); + } + } else { + pFocusChild->vt = VT_EMPTY; + } + } + return retCode; +} + +// Return a selection list for multiple selection items. +STDMETHODIMP CefIAccessible::get_accSelection(VARIANT *pvarChildren) { + HRESULT retCode = DATACHECK(node_); + if (SUCCEEDED(retCode)) { + if (pvarChildren) + pvarChildren->vt = VT_EMPTY; + else + retCode = E_INVALIDARG; + } + return retCode; +} + +// Return a string description of the default action of our element, eg. push +STDMETHODIMP CefIAccessible::get_accDefaultAction(VARIANT varChild, + BSTR* pszDefaultAction) { + HRESULT retCode = DATACHECK(node_); + if (SUCCEEDED(retCode)) { + if (pszDefaultAction && VALID_CHILDID(varChild)) + *pszDefaultAction = ::SysAllocString(L"Push"); + else + retCode = E_INVALIDARG; + } + return retCode; +} + +// child item selectionor for an item to take focus. +STDMETHODIMP CefIAccessible::accSelect(long flagsSelect, VARIANT varChild) { + HRESULT retCode = DATACHECK(node_); + if (SUCCEEDED(retCode)) { + if (VALID_CHILDID(varChild)) { + HWND hwnd = node_->GetWindowHandle(); + // we only support SELFLAG_TAKEFOCUS. + if (((flagsSelect & SELFLAG_TAKEFOCUS) > 0) && (GetFocus() == hwnd)) { + RECT rcWnd; + GetClientRect(hwnd, &rcWnd); + InvalidateRect(hwnd, &rcWnd, FALSE); + } else { + retCode = S_FALSE; + } + } else { + retCode = E_INVALIDARG; + } + } + + return retCode; +} + +// Returns back the screen coordinates of our element or one of its childs +STDMETHODIMP CefIAccessible::accLocation(long* pxLeft, long* pyTop, + long* pcxWidth, long* pcyHeight, + VARIANT varChild) { + HRESULT retCode = DATACHECK(node_); + if (SUCCEEDED(retCode)) { + if (pxLeft && pyTop && pcxWidth && pcyHeight && VALID_CHILDID(varChild)) { + CefRect loc = node_->AxLocation(); + RECT rcItem = { loc.x, loc.y, loc.x + loc.width, loc.y + loc.height }; + HWND hwnd = node_->GetWindowHandle(); + ClientToScreen(hwnd, &rcItem); + + *pxLeft = rcItem.left; + *pyTop = rcItem.top; + *pcxWidth = rcItem.right - rcItem.left; + *pcyHeight = rcItem.bottom - rcItem.top; + } else { + retCode = E_INVALIDARG; + } + } + return retCode; +} + +// Allow clients to move the keyboard focus within the control +// Deprecated +STDMETHODIMP CefIAccessible::accNavigate(long navDir, VARIANT varStart, + VARIANT* pvarEndUpAt) { + return E_NOTIMPL; +} + +// Check if the coordinates provided are within our element or child items. +STDMETHODIMP CefIAccessible::accHitTest(long xLeft, long yTop, + VARIANT *pvarChild) { + HRESULT retCode = DATACHECK(node_); + if (SUCCEEDED(retCode)) { + if (pvarChild) { + pvarChild->vt = VT_EMPTY; + + CefRect loc = node_->AxLocation(); + RECT rcItem = { loc.x, loc.y, loc.x + loc.width, loc.y + loc.height }; + POINT pt = { xLeft, yTop }; + + ClientToScreen(node_->GetWindowHandle(), &rcItem); + + if (PtInRect(&rcItem, pt)) { + pvarChild->vt = VT_I4; + pvarChild->lVal = 1; + } + } else { + retCode = E_INVALIDARG; + } + } + + return retCode; +} + +// Forces the default action of our element. In simplest cases, send a click. +STDMETHODIMP CefIAccessible::accDoDefaultAction(VARIANT varChild) { + HRESULT retCode = DATACHECK(node_); + if (SUCCEEDED(retCode) && VALID_CHILDID(varChild)) { + // doing our default action for out button is to simply click the button. + CefRefPtr browser = node_->GetBrowser(); + if (browser) { + CefMouseEvent mouse_event; + const CefRect& rect = node_->AxLocation(); + mouse_event.x = MiddleX(rect); + mouse_event.y = MiddleY(rect); + + mouse_event.modifiers = 0; + browser->GetHost()->SendMouseClickEvent(mouse_event, MBT_LEFT, false, 1); + browser->GetHost()->SendMouseClickEvent(mouse_event, MBT_LEFT, true, 1); + } + } else { + retCode = E_INVALIDARG; + } + + return retCode; +} + +// Set the name for an element in the accessibility tree +STDMETHODIMP CefIAccessible::put_accName(VARIANT varChild, BSTR szName) { + return E_NOTIMPL; +} + +// Set the value for an element in the accessibility tree +STDMETHODIMP CefIAccessible::put_accValue(VARIANT varChild, BSTR szValue) { + return E_NOTIMPL; +} + +// Return E_NOTIMPL as no help file/ topic +STDMETHODIMP CefIAccessible::get_accHelp(VARIANT varChild, BSTR *pszHelp) { + return E_NOTIMPL; +} + +STDMETHODIMP CefIAccessible::get_accHelpTopic(BSTR *pszHelpFile, + VARIANT varChild, + long* pidTopic) { + return E_NOTIMPL; +} + +// IDispatch - We are not going to return E_NOTIMPL from IDispatch methods and +// let Active Accessibility implement the IAccessible interface for them. +STDMETHODIMP CefIAccessible::GetTypeInfoCount(unsigned int FAR* pctinfo) { + return E_NOTIMPL; +} + +STDMETHODIMP CefIAccessible::GetTypeInfo(unsigned int iTInfo, LCID lcid, + ITypeInfo FAR* FAR* ppTInfo) { + return E_NOTIMPL; +} + +STDMETHODIMP CefIAccessible::GetIDsOfNames(REFIID riid, + OLECHAR FAR* FAR* rgszNames, + unsigned int cNames, + LCID lcid, + DISPID FAR* rgDispId) { + return E_NOTIMPL; +} + +STDMETHODIMP CefIAccessible::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, + WORD wFlags, DISPPARAMS FAR* pDispParams, + VARIANT FAR* pVarResult, + EXCEPINFO FAR* pExcepInfo, + unsigned int FAR* puArgErr) { + return E_NOTIMPL; +} + +void OsrAXNode::NotifyAccessibilityEvent(std::string event_type) const { + if (event_type == "focus") { + // Notify Screen Reader of focus change + ::NotifyWinEvent(EVENT_OBJECT_FOCUS, GetWindowHandle(), OBJID_CLIENT, + node_id_); + } +} + +void OsrAXNode::Destroy() { + CefIAccessible* ptr = static_cast(platform_accessibility_); + if (ptr) + ptr->MarkDestroyed(); + platform_accessibility_ = NULL; +} + +// Create and return NSAccessibility Implementation Object for Window +CefNativeAccessible* OsrAXNode::GetNativeAccessibleObject(OsrAXNode* parent) { + if (!platform_accessibility_) { + platform_accessibility_ = new CefIAccessible(this); + platform_accessibility_->AddRef(); + SetParent(parent); + } + return platform_accessibility_; +} + +} // namespace client + +#else // !defined(CEF_USE_ATL) + +namespace client { + +void OsrAXNode::Destroy() { +} + +CefNativeAccessible* OsrAXNode::GetNativeAccessibleObject(OsrAXNode* parent) { + return NULL; +} + +} // namespace client + +#endif // !defined(CEF_USE_ATL) diff --git a/tests/cefclient/browser/osr_window_win.cc b/tests/cefclient/browser/osr_window_win.cc index 1a8e1ca7b..4d2a211a3 100644 --- a/tests/cefclient/browser/osr_window_win.cc +++ b/tests/cefclient/browser/osr_window_win.cc @@ -5,13 +5,18 @@ #include "tests/cefclient/browser/osr_window_win.h" #include +#if defined(CEF_USE_ATL) +#include +#endif #include "include/base/cef_build.h" #include "tests/shared/browser/geometry_util.h" #include "tests/shared/browser/main_message_loop.h" #include "tests/cefclient/browser/main_context.h" -#include "tests/cefclient/browser/resource.h" +#include "tests/cefclient/browser/osr_accessibility_helper.h" +#include "tests/cefclient/browser/osr_accessibility_node.h" #include "tests/cefclient/browser/osr_ime_handler_win.h" +#include "tests/cefclient/browser/resource.h" #include "tests/shared/browser/util_win.h" namespace client { @@ -250,6 +255,8 @@ void OsrWindowWin::Create(HWND parent_hwnd, const RECT& rect) { SetUserDataPtr(hwnd_, this); #if defined(CEF_USE_ATL) + accessibility_root_ = NULL; + // Create/register the drag&drop handler. drop_target_ = DropTargetWin::Create(this, hwnd_); HRESULT register_res = RegisterDragDrop(hwnd_, drop_target_); @@ -486,7 +493,25 @@ LRESULT CALLBACK OsrWindowWin::OsrWndProc(HWND hWnd, UINT message, self->OnIMECancelCompositionEvent(); // Let WTL call::DefWindowProc() and release its resources. break; +#if defined(CEF_USE_ATL) + case WM_GETOBJECT: { + // Only the lower 32 bits of lParam are valid when checking the object id + // because it sometimes gets sign-extended incorrectly (but not always). + DWORD obj_id = static_cast(static_cast(lParam)); + // Accessibility readers will send an OBJID_CLIENT message. + if (static_cast(OBJID_CLIENT) == obj_id) { + if (self->accessibility_root_) { + return LresultFromObject(IID_IAccessible, wParam, + static_cast(self->accessibility_root_)); + } else { + // Notify the renderer to enable accessibility. + if (self->browser_ && self->browser_->GetHost()) + self->browser_->GetHost()->SetAccessibilityState(STATE_ENABLED); + } + } + } break; +#endif case WM_LBUTTONDOWN: case WM_RBUTTONDOWN: case WM_MBUTTONDOWN: @@ -1014,6 +1039,23 @@ void OsrWindowWin::OnImeCompositionRangeChanged( } } +void OsrWindowWin::UpdateAccessibilityTree(CefRefPtr value) { + CEF_REQUIRE_UI_THREAD(); + +#if defined(CEF_USE_ATL) + if (!accessibility_handler_) { + accessibility_handler_.reset(new OsrAccessibilityHelper(value, browser_)); + } else { + accessibility_handler_->UpdateAccessibilityTree(value); + } + + // Update |accessibility_root_| because UpdateAccessibilityTree may have + // cleared it. + OsrAXNode* root = accessibility_handler_->GetRootNode(); + accessibility_root_ = root ? root->GetNativeAccessibleObject(NULL) : NULL; +#endif // defined(CEF_USE_ATL) +} + #if defined(CEF_USE_ATL) CefBrowserHost::DragOperationsMask diff --git a/tests/cefclient/browser/osr_window_win.h b/tests/cefclient/browser/osr_window_win.h index 78dc2e4a2..a7df8d041 100644 --- a/tests/cefclient/browser/osr_window_win.h +++ b/tests/cefclient/browser/osr_window_win.h @@ -11,11 +11,13 @@ #include "include/wrapper/cef_closure_task.h" #include "include/wrapper/cef_helpers.h" #include "tests/cefclient/browser/client_handler_osr.h" +#include "tests/cefclient/browser/osr_accessibility_node.h" #include "tests/cefclient/browser/osr_dragdrop_win.h" #include "tests/cefclient/browser/osr_renderer.h" namespace client { +class OsrAccessibilityHelper; class OsrImeHandlerWin; // Represents the native parent window for an off-screen browser. This object @@ -24,7 +26,7 @@ class OsrImeHandlerWin; class OsrWindowWin : public base::RefCountedThreadSafe, public ClientHandlerOsr::OsrDelegate - #if defined(CEF_USE_ATL) +#if defined(CEF_USE_ATL) , public OsrDragEvents #endif { @@ -51,7 +53,7 @@ class OsrWindowWin : const CefBrowserSettings& settings, CefRefPtr request_context, const std::string& startup_url); - + // Show the popup window with correct parent and bounds in parent coordinates. void ShowPopup(HWND parent_hwnd, int x, int y, size_t width, size_t height); @@ -145,6 +147,8 @@ class OsrWindowWin : const CefRange& selection_range, const CefRenderHandler::RectList& character_bounds) OVERRIDE; + void UpdateAccessibilityTree(CefRefPtr value); + #if defined(CEF_USE_ATL) // OsrDragEvents methods. CefBrowserHost::DragOperationsMask OnDragEnter( @@ -178,6 +182,11 @@ class OsrWindowWin : #if defined(CEF_USE_ATL) CComPtr drop_target_; CefRenderHandler::DragOperation current_drag_op_; + + // Class that abstracts the accessibility information received from the + // renderer. + scoped_ptr accessibility_handler_; + IAccessible* accessibility_root_; #endif bool painting_popup_; diff --git a/tests/cefclient/browser/root_window_mac.mm b/tests/cefclient/browser/root_window_mac.mm index ba2afe79a..585b4b5af 100644 --- a/tests/cefclient/browser/root_window_mac.mm +++ b/tests/cefclient/browser/root_window_mac.mm @@ -648,6 +648,19 @@ void RootWindowMac::OnSetLoadingState(bool isLoading, [back_button_ setEnabled:canGoBack]; [forward_button_ setEnabled:canGoForward]; } + + // After Loading is done, check if voiceover is running and accessibility + // should be enabled. + if (!isLoading) { + Boolean keyExists = false; + // On OSX there is no API to query if VoiceOver is active or not. The value + // however is stored in preferences that can be queried. + if (CFPreferencesGetAppBooleanValue(CFSTR("voiceOverOnOffKey"), + CFSTR("com.apple.universalaccess"), + &keyExists)) { + GetBrowser()->GetHost()->SetAccessibilityState(STATE_ENABLED); + } + } } void RootWindowMac::NotifyDestroyedIfDone() { diff --git a/tests/cefclient/browser/root_window_win.cc b/tests/cefclient/browser/root_window_win.cc index 6d7f63b95..3122e934a 100644 --- a/tests/cefclient/browser/root_window_win.cc +++ b/tests/cefclient/browser/root_window_win.cc @@ -541,6 +541,18 @@ LRESULT CALLBACK RootWindowWin::RootWndProc(HWND hWnd, UINT message, return 0; break; + case WM_GETOBJECT: { + // Only the lower 32 bits of lParam are valid when checking the object id + // because it sometimes gets sign-extended incorrectly (but not always). + DWORD obj_id = static_cast(static_cast(lParam)); + + // Accessibility readers will send an OBJID_CLIENT message. + if (static_cast(OBJID_CLIENT) == obj_id) { + if (self->GetBrowser() && self->GetBrowser()->GetHost()) + self->GetBrowser()->GetHost()->SetAccessibilityState(STATE_ENABLED); + } + } break; + case WM_PAINT: self->OnPaint(); return 0; diff --git a/tests/cefclient/cefclient_mac.mm b/tests/cefclient/cefclient_mac.mm index fc88b27f4..fa10b9ccb 100644 --- a/tests/cefclient/cefclient_mac.mm +++ b/tests/cefclient/cefclient_mac.mm @@ -65,6 +65,7 @@ NSMenuItem* GetMenuItemWithAction(NSMenu* menu, SEL action_selector) { - (IBAction)menuTestsPrint:(id)sender; - (IBAction)menuTestsPrintToPdf:(id)sender; - (IBAction)menuTestsOtherTests:(id)sender; +- (void)enableAccessibility:(bool)bEnable; @end // Provide the CefAppProtocol implementation required by CEF. @@ -132,6 +133,18 @@ NSMenuItem* GetMenuItemWithAction(NSMenu* menu, SEL action_selector) { [delegate tryToTerminateApplication:self]; // Return, don't exit. The application is responsible for exiting on its own. } + +// Detect dynamically if VoiceOver is running. Like Chromium, rely upon the +// undocumented accessibility attribute @"AXEnhancedUserInterface" which is set +// when VoiceOver is launched and unset when VoiceOver is closed. +- (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute { + if ([attribute isEqualToString:@"AXEnhancedUserInterface"]) { + ClientAppDelegate* delegate = static_cast( + [[NSApplication sharedApplication] delegate]); + [delegate enableAccessibility:([value intValue] == 1)]; + } + return [super accessibilitySetValue:value forAttribute:attribute]; +} @end @implementation ClientAppDelegate @@ -297,6 +310,22 @@ NSMenuItem* GetMenuItemWithAction(NSMenu* menu, SEL action_selector) { [self testsItemSelected:ID_TESTS_OTHER_TESTS]; } +- (void)enableAccessibility:(bool)bEnable { + // Retrieve the active RootWindow. + NSWindow* key_window = [[NSApplication sharedApplication] keyWindow]; + if (!key_window) + return; + + scoped_refptr root_window = + client::RootWindow::GetForNSWindow(key_window); + + CefRefPtr browser = root_window->GetBrowser(); + if (browser.get()) { + browser->GetHost()->SetAccessibilityState(bEnable ? + STATE_ENABLED : STATE_DISABLED); + } +} + - (NSApplicationTerminateReply)applicationShouldTerminate: (NSApplication *)sender { return NSTerminateNow; diff --git a/tests/ceftests/accessibility_unittest.cc b/tests/ceftests/accessibility_unittest.cc new file mode 100644 index 000000000..fb5c50a9b --- /dev/null +++ b/tests/ceftests/accessibility_unittest.cc @@ -0,0 +1,464 @@ +// 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; indexGetSize(); 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; indexGetSize(); index++) { + CefRefPtr node = nodes->GetDictionary(index); + if (node->GetString("role").ToString() == "group") { + 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; indexGetSize(); 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; indexGetSize(); 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); +} diff --git a/tests/ceftests/os_rendering_unittest.cc b/tests/ceftests/os_rendering_unittest.cc index 06dadba35..a9a3d6885 100644 --- a/tests/ceftests/os_rendering_unittest.cc +++ b/tests/ceftests/os_rendering_unittest.cc @@ -107,6 +107,7 @@ const char kKeyTestWord[] = "done"; #define VKEY_N 0x4E #define VKEY_E 0x45 #define VKEY_ESCAPE 0x1B +#define VKEY_TAB 0x09 const unsigned int kNativeKeyTestCodes[] = { XK_d, @@ -116,6 +117,7 @@ const unsigned int kNativeKeyTestCodes[] = { }; const unsigned int kNativeKeyEscape = XK_Escape; +const unsigned int kNativeKeyTab = XK_Tab; #elif defined(OS_MACOSX) @@ -125,6 +127,7 @@ const unsigned int kNativeKeyEscape = XK_Escape; #define VKEY_N 'n' #define VKEY_E 'e' #define VKEY_ESCAPE kEscapeCharCode +#define VKEY_TAB kTabCharCode const unsigned int kNativeKeyTestCodes[] = { kVK_ANSI_D, @@ -134,6 +137,7 @@ const unsigned int kNativeKeyTestCodes[] = { }; const unsigned int kNativeKeyEscape = kVK_Escape; +const unsigned int kNativeKeyTab = kVK_Tab; #endif @@ -154,6 +158,12 @@ enum OSRTestType { OSR_TEST_IS_WINDOWLESS, // focusing webview, LI00 will get red & repainted OSR_TEST_FOCUS, + // tab key traversal should iterate the focus across HTML element and + // subsequently after last element CefFocusHandler::OnTakeFocus should be + // called to allow giving focus to the next component. + OSR_TEST_TAKE_FOCUS, + // send focus event should set focus on the webview + OSR_TEST_GOT_FOCUS, // loading webview should trigger a full paint (L01) OSR_TEST_PAINT, // same as OSR_TEST_PAINT but with alpha values @@ -512,6 +522,23 @@ class OSRTestHandler : public RoutingTestHandler, browser->GetHost()->SendFocusEvent(true); } break; + case OSR_TEST_TAKE_FOCUS: + if (StartTest() || started()) { + // Tab traversal across HTML element +#if defined(OS_WIN) + SendKeyEvent(browser, VK_TAB); +#elif defined(OS_MACOSX) || defined(OS_LINUX) + SendKeyEvent(browser, kNativeKeyTab, VKEY_TAB); +#else +#error "Unsupported platform" +#endif + } + break; + case OSR_TEST_GOT_FOCUS: + if (StartTest()) { + browser->GetHost()->SendFocusEvent(true); + } + break; case OSR_TEST_CURSOR: if (StartTest()) { // make mouse leave first @@ -980,6 +1007,21 @@ class OSRTestHandler : public RoutingTestHandler, return false; } + void OnTakeFocus(CefRefPtr browser, + bool next) { + if (test_type_ == OSR_TEST_TAKE_FOCUS) { + EXPECT_TRUE(true); + DestroySucceededTestSoon(); + } + } + + void OnGotFocus(CefRefPtr browser) { + if (test_type_ == OSR_TEST_GOT_FOCUS) { + EXPECT_TRUE(true); + DestroySucceededTestSoon(); + } + } + void OnCursorChange(CefRefPtr browser, CefCursorHandle cursor, CursorType type, @@ -1319,6 +1361,10 @@ OSR_TEST(Windowless, OSR_TEST_IS_WINDOWLESS, 1.0f); OSR_TEST(Windowless2x, OSR_TEST_IS_WINDOWLESS, 2.0f); OSR_TEST(Focus, OSR_TEST_FOCUS, 1.0f); OSR_TEST(Focus2x, OSR_TEST_FOCUS, 2.0f); +OSR_TEST(TakeFocus, OSR_TEST_TAKE_FOCUS, 1.0f); +OSR_TEST(TakeFocus2x, OSR_TEST_TAKE_FOCUS, 2.0f); +OSR_TEST(GotFocus, OSR_TEST_GOT_FOCUS, 1.0f); +OSR_TEST(GotFocus2x, OSR_TEST_GOT_FOCUS, 2.0f); OSR_TEST(Paint, OSR_TEST_PAINT, 1.0f); OSR_TEST(Paint2x, OSR_TEST_PAINT, 2.0f); OSR_TEST(TransparentPaint, OSR_TEST_TRANSPARENCY, 1.0f);