Commit 7801fa45 authored by Aaron Leventhal's avatar Aaron Leventhal Committed by Commit Bot

Missing caret hide events in Windows accessibility support

Fix: Plumb through IME state change use to notify of caret change

Bug: 887534
Change-Id: If472661b199afcbc8b30b7cb99e6d83b15114942
Reviewed-on: https://chromium-review.googlesource.com/1236735Reviewed-by: default avatarDave Tapuska <dtapuska@chromium.org>
Reviewed-by: default avatarSadrul Chowdhury <sadrul@chromium.org>
Reviewed-by: default avatarDominic Mazzoni <dmazzoni@chromium.org>
Commit-Queue: Aaron Leventhal <aleventhal@chromium.org>
Cr-Commit-Position: refs/heads/master@{#594065}
parent be77f657
...@@ -288,6 +288,12 @@ IN_PROC_BROWSER_TEST_F(DumpAccessibilityEventsTest, ...@@ -288,6 +288,12 @@ IN_PROC_BROWSER_TEST_F(DumpAccessibilityEventsTest,
RunEventTest(FILE_PATH_LITERAL("checked-state-changed.html")); RunEventTest(FILE_PATH_LITERAL("checked-state-changed.html"));
} }
// http:/crbug.com/889013
IN_PROC_BROWSER_TEST_F(DumpAccessibilityEventsTest,
DISABLED_AccessibilityEventsCaretHide) {
RunEventTest(FILE_PATH_LITERAL("caret-hide.html"));
}
// http:/crbug.com/889013 // http:/crbug.com/889013
IN_PROC_BROWSER_TEST_F(DumpAccessibilityEventsTest, IN_PROC_BROWSER_TEST_F(DumpAccessibilityEventsTest,
DISABLED_AccessibilityEventsCaretMove) { DISABLED_AccessibilityEventsCaretMove) {
......
EVENT_OBJECT_FOCUS on <input#btn> role=ROLE_SYSTEM_PUSHBUTTON name="Submit" FOCUSED,FOCUSABLE
EVENT_OBJECT_FOCUS on <input#in1> role=ROLE_SYSTEM_TEXT value="abcde" FOCUSED,FOCUSABLE IA2_STATE_EDITABLE,IA2_STATE_SELECTABLE_TEXT,IA2_STATE_SINGLE_LINE
EVENT_OBJECT_HIDE role=ROLE_SYSTEM_CARET INVISIBLE window_class=Chrome_WidgetWin_0
EVENT_OBJECT_LOCATIONCHANGE role=ROLE_SYSTEM_CARET window_class=Chrome_WidgetWin_0
EVENT_OBJECT_SHOW role=ROLE_SYSTEM_CARET window_class=Chrome_WidgetWin_0
EVENT_OBJECT_VALUECHANGE on <input#in1> role=ROLE_SYSTEM_TEXT value="end" FOCUSABLE IA2_STATE_EDITABLE,IA2_STATE_SELECTABLE_TEXT,IA2_STATE_SINGLE_LINE
IA2_EVENT_TEXT_CARET_MOVED on <input#in1> role=ROLE_SYSTEM_TEXT value="abcde" FOCUSED,FOCUSABLE IA2_STATE_EDITABLE,IA2_STATE_SELECTABLE_TEXT,IA2_STATE_SINGLE_LINE
\ No newline at end of file
<!--
@RUN-UNTIL-EVENT:EVENT_OBJECT_VALUECHANGE
@WIN-DENY:IA2_EVENT_TEXT_INSERTED*
@WIN-DENY:IA2_EVENT_TEXT_REMOVED*
-->
<!DOCTYPE html>
<html>
<body>
<input id="in1" type="text" value="abcde">
<input id="btn" type="submit">
<script>
function go() {
const inp = document.getElementById('in1');
inp.focus();
inp.setSelectionRange(0, 0);
setTimeout(() => {
document.getElementById('btn').focus();
setTimeout(() => {
inp.value = 'end'; // Trigger test end.
}, 50);
}, 50);
}
</script>
</body>
</html>
\ No newline at end of file
...@@ -89,6 +89,7 @@ bool InputMethodWinImm32::OnUntranslatedIMEMessage( ...@@ -89,6 +89,7 @@ bool InputMethodWinImm32::OnUntranslatedIMEMessage(
void InputMethodWinImm32::OnTextInputTypeChanged( void InputMethodWinImm32::OnTextInputTypeChanged(
const TextInputClient* client) { const TextInputClient* client) {
InputMethodBase::OnTextInputTypeChanged(client);
if (!IsTextInputClientFocused(client) || !IsWindowFocused(client)) if (!IsTextInputClientFocused(client) || !IsWindowFocused(client))
return; return;
imm32_manager_.CancelIME(toplevel_window_handle_); imm32_manager_.CancelIME(toplevel_window_handle_);
......
...@@ -79,6 +79,7 @@ bool InputMethodWinTSF::OnUntranslatedIMEMessage( ...@@ -79,6 +79,7 @@ bool InputMethodWinTSF::OnUntranslatedIMEMessage(
} }
void InputMethodWinTSF::OnTextInputTypeChanged(const TextInputClient* client) { void InputMethodWinTSF::OnTextInputTypeChanged(const TextInputClient* client) {
InputMethodBase::OnTextInputTypeChanged(client);
if (!IsTextInputClientFocused(client) || !IsWindowFocused(client)) if (!IsTextInputClientFocused(client) || !IsWindowFocused(client))
return; return;
ui::TSFBridge::GetInstance()->CancelComposition(); ui::TSFBridge::GetInstance()->CancelComposition();
......
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
#include "ui/gfx/geometry/rect.h" #include "ui/gfx/geometry/rect.h"
#include "ui/gfx/native_widget_types.h" #include "ui/gfx/native_widget_types.h"
#include "ui/gl/test/gl_surface_test_support.h" #include "ui/gl/test/gl_surface_test_support.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/textfield/textfield.h" #include "ui/views/controls/textfield/textfield.h"
#include "ui/views/controls/textfield/textfield_test_api.h" #include "ui/views/controls/textfield/textfield_test_api.h"
#include "ui/views/test/widget_test.h" #include "ui/views/test/widget_test.h"
...@@ -74,6 +75,132 @@ class AXSystemCaretWinTest : public test::WidgetTest { ...@@ -74,6 +75,132 @@ class AXSystemCaretWinTest : public test::WidgetTest {
DISALLOW_COPY_AND_ASSIGN(AXSystemCaretWinTest); DISALLOW_COPY_AND_ASSIGN(AXSystemCaretWinTest);
}; };
class WinAccessibilityCaretEventMonitor {
public:
WinAccessibilityCaretEventMonitor(UINT event_min, UINT event_max);
~WinAccessibilityCaretEventMonitor();
// Blocks until the next event is received. When it's received, it
// queries accessibility information about the object that fired the
// event and populates the event, hwnd, role, state, and name in the
// passed arguments.
void WaitForNextEvent(DWORD* out_event, UINT* out_role, UINT* out_state);
private:
void OnWinEventHook(HWINEVENTHOOK handle,
DWORD event,
HWND hwnd,
LONG obj_id,
LONG child_id,
DWORD event_thread,
DWORD event_time);
static void CALLBACK WinEventHookThunk(HWINEVENTHOOK handle,
DWORD event,
HWND hwnd,
LONG obj_id,
LONG child_id,
DWORD event_thread,
DWORD event_time);
struct EventInfo {
DWORD event;
HWND hwnd;
LONG obj_id;
LONG child_id;
};
base::circular_deque<EventInfo> event_queue_;
base::RunLoop loop_runner_;
HWINEVENTHOOK win_event_hook_handle_;
static WinAccessibilityCaretEventMonitor* instance_;
DISALLOW_COPY_AND_ASSIGN(WinAccessibilityCaretEventMonitor);
};
// static
WinAccessibilityCaretEventMonitor*
WinAccessibilityCaretEventMonitor::instance_ = NULL;
WinAccessibilityCaretEventMonitor::WinAccessibilityCaretEventMonitor(
UINT event_min,
UINT event_max) {
CHECK(!instance_) << "There can be only one instance of"
<< " WinAccessibilityCaretEventMonitor at a time.";
instance_ = this;
win_event_hook_handle_ =
SetWinEventHook(event_min, event_max, NULL,
&WinAccessibilityCaretEventMonitor::WinEventHookThunk,
GetCurrentProcessId(),
0, // Hook all threads
WINEVENT_OUTOFCONTEXT);
}
WinAccessibilityCaretEventMonitor::~WinAccessibilityCaretEventMonitor() {
UnhookWinEvent(win_event_hook_handle_);
instance_ = NULL;
}
void WinAccessibilityCaretEventMonitor::WaitForNextEvent(DWORD* out_event,
UINT* out_role,
UINT* out_state) {
if (event_queue_.empty())
loop_runner_.Run();
EventInfo event_info = event_queue_.front();
event_queue_.pop_front();
*out_event = event_info.event;
Microsoft::WRL::ComPtr<IAccessible> acc_obj;
base::win::ScopedVariant child_variant;
CHECK(S_OK == AccessibleObjectFromEvent(
event_info.hwnd, event_info.obj_id, event_info.child_id,
acc_obj.GetAddressOf(), child_variant.Receive()));
base::win::ScopedVariant role_variant;
if (S_OK == acc_obj->get_accRole(child_variant, role_variant.Receive()))
*out_role = V_I4(role_variant.ptr());
else
*out_role = 0;
base::win::ScopedVariant state_variant;
if (S_OK == acc_obj->get_accState(child_variant, state_variant.Receive()))
*out_state = V_I4(state_variant.ptr());
else
*out_state = 0;
}
void WinAccessibilityCaretEventMonitor::OnWinEventHook(HWINEVENTHOOK handle,
DWORD event,
HWND hwnd,
LONG obj_id,
LONG child_id,
DWORD event_thread,
DWORD event_time) {
EventInfo event_info;
event_info.event = event;
event_info.hwnd = hwnd;
event_info.obj_id = obj_id;
event_info.child_id = child_id;
event_queue_.push_back(event_info);
loop_runner_.Quit();
}
// static
void CALLBACK
WinAccessibilityCaretEventMonitor::WinEventHookThunk(HWINEVENTHOOK handle,
DWORD event,
HWND hwnd,
LONG obj_id,
LONG child_id,
DWORD event_thread,
DWORD event_time) {
if (instance_ && obj_id == OBJID_CARET) {
instance_->OnWinEventHook(handle, event, hwnd, obj_id, child_id,
event_thread, event_time);
}
}
} // namespace } // namespace
TEST_F(AXSystemCaretWinTest, DISABLED_TestOnCaretBoundsChangeInTextField) { TEST_F(AXSystemCaretWinTest, DISABLED_TestOnCaretBoundsChangeInTextField) {
...@@ -183,4 +310,81 @@ TEST_F(AXSystemCaretWinTest, DISABLED_TestMovingWindow) { ...@@ -183,4 +310,81 @@ TEST_F(AXSystemCaretWinTest, DISABLED_TestMovingWindow) {
EXPECT_EQ(height, height3); EXPECT_EQ(height, height3);
} }
TEST_F(AXSystemCaretWinTest, TestCaretMSAAEvents) {
TextfieldTestApi textfield_test_api(textfield_);
Microsoft::WRL::ComPtr<IAccessible> caret_accessible;
gfx::NativeWindow native_window = widget_->GetNativeWindow();
ASSERT_NE(nullptr, native_window);
HWND hwnd = native_window->GetHost()->GetAcceleratedWidget();
EXPECT_HRESULT_SUCCEEDED(AccessibleObjectFromWindow(
hwnd, static_cast<DWORD>(OBJID_CARET), IID_IAccessible,
reinterpret_cast<void**>(caret_accessible.GetAddressOf())));
DWORD event;
UINT role;
UINT state;
{
// Set caret to start of textfield.
WinAccessibilityCaretEventMonitor monitor(EVENT_OBJECT_SHOW,
EVENT_OBJECT_LOCATIONCHANGE);
textfield_test_api.ExecuteTextEditCommand(
ui::TextEditCommand::MOVE_TO_BEGINNING_OF_DOCUMENT);
monitor.WaitForNextEvent(&event, &role, &state);
ASSERT_EQ(event, static_cast<DWORD>(EVENT_OBJECT_LOCATIONCHANGE))
<< "Event should be EVENT_OBJECT_LOCATIONCHANGE";
ASSERT_EQ(role, static_cast<UINT>(ROLE_SYSTEM_CARET))
<< "Role should be ROLE_SYSTEM_CARET";
ASSERT_EQ(state, static_cast<UINT>(0)) << "State should be 0";
}
{
// Set caret to end of textfield.
WinAccessibilityCaretEventMonitor monitor(EVENT_OBJECT_SHOW,
EVENT_OBJECT_LOCATIONCHANGE);
textfield_test_api.ExecuteTextEditCommand(
ui::TextEditCommand::MOVE_TO_END_OF_DOCUMENT);
monitor.WaitForNextEvent(&event, &role, &state);
ASSERT_EQ(event, static_cast<DWORD>(EVENT_OBJECT_LOCATIONCHANGE))
<< "Event should be EVENT_OBJECT_LOCATIONCHANGE";
ASSERT_EQ(role, static_cast<UINT>(ROLE_SYSTEM_CARET))
<< "Role should be ROLE_SYSTEM_CARET";
ASSERT_EQ(state, static_cast<UINT>(0)) << "State should be 0";
}
{
// Move focus to a button.
LabelButton button(nullptr, base::string16());
button.SetBounds(500, 0, 200, 20);
widget_->GetRootView()->AddChildView(&button);
test::WidgetActivationWaiter waiter(widget_, true);
WinAccessibilityCaretEventMonitor monitor(EVENT_OBJECT_SHOW,
EVENT_OBJECT_LOCATIONCHANGE);
widget_->Show();
waiter.Wait();
button.SetFocusBehavior(View::FocusBehavior::ALWAYS);
button.RequestFocus();
monitor.WaitForNextEvent(&event, &role, &state);
ASSERT_EQ(event, static_cast<DWORD>(EVENT_OBJECT_HIDE))
<< "Event should be EVENT_OBJECT_HIDE";
ASSERT_EQ(role, static_cast<UINT>(ROLE_SYSTEM_CARET))
<< "Role should be ROLE_SYSTEM_CARET";
ASSERT_EQ(state, static_cast<UINT>(STATE_SYSTEM_INVISIBLE))
<< "State should be STATE_SYSTEM_INVISIBLE";
}
{
// Move focus back to the text field.
WinAccessibilityCaretEventMonitor monitor(EVENT_OBJECT_SHOW,
EVENT_OBJECT_LOCATIONCHANGE);
textfield_->RequestFocus();
monitor.WaitForNextEvent(&event, &role, &state);
ASSERT_EQ(event, static_cast<DWORD>(EVENT_OBJECT_SHOW))
<< "Event should be EVENT_OBJECT_SHOW";
ASSERT_EQ(role, static_cast<UINT>(ROLE_SYSTEM_CARET))
<< "Role should be ROLE_SYSTEM_CARET";
ASSERT_EQ(state, static_cast<UINT>(0)) << "State should be 0";
}
}
} // namespace views } // namespace views
...@@ -1017,7 +1017,10 @@ void HWNDMessageHandler::OnCaretBoundsChanged( ...@@ -1017,7 +1017,10 @@ void HWNDMessageHandler::OnCaretBoundsChanged(
} }
void HWNDMessageHandler::OnTextInputStateChanged( void HWNDMessageHandler::OnTextInputStateChanged(
const ui::TextInputClient* client) {} const ui::TextInputClient* client) {
if (!client || client->GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE)
OnCaretBoundsChanged(client);
}
void HWNDMessageHandler::OnInputMethodDestroyed( void HWNDMessageHandler::OnInputMethodDestroyed(
const ui::InputMethod* input_method) { const ui::InputMethod* input_method) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment