Commit bf8cec44 authored by dmazzoni's avatar dmazzoni Committed by Commit bot

Suppress accessibility events when user is navigating away.

When the user navigates to a new page, stop sending accessibility
events on the old page.

BUG=421116,450409

Committed: https://crrev.com/6ce40a1e561892849c1f6ac070dda140f6cc0115
Cr-Commit-Position: refs/heads/master@{#314812}

Review URL: https://codereview.chromium.org/830053004

Cr-Commit-Position: refs/heads/master@{#315231}
parent 4ff72b0b
// Copyright (c) 2015 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 <oleacc.h>
#include "base/strings/string_util.h"
#include "base/win/scoped_bstr.h"
#include "base/win/scoped_com_initializer.h"
#include "base/win/scoped_comptr.h"
#include "base/win/scoped_variant.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/omnibox/omnibox_view.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/omnibox/omnibox_view_views.h"
#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/interactive_test_utils.h"
#include "chrome/test/base/ui_test_utils.h"
#include "content/public/browser/browser_accessibility_state.h"
#include "net/dns/mock_host_resolver.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/test/ui_controls.h"
#include "url/gurl.h"
// We could move this into a utility file in the future if it ends up
// being useful to other tests.
class WinAccessibilityEventMonitor {
public:
WinAccessibilityEventMonitor(UINT event_min, UINT event_max);
~WinAccessibilityEventMonitor();
// 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,
HWND* out_hwnd,
UINT* out_role,
UINT* out_state,
std::string* out_name);
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;
};
std::deque<EventInfo> event_queue_;
scoped_refptr<content::MessageLoopRunner> loop_runner_;
HWINEVENTHOOK win_event_hook_handle_;
static WinAccessibilityEventMonitor* instance_;
DISALLOW_COPY_AND_ASSIGN(WinAccessibilityEventMonitor);
};
// static
WinAccessibilityEventMonitor* WinAccessibilityEventMonitor::instance_ = NULL;
WinAccessibilityEventMonitor::WinAccessibilityEventMonitor(
UINT event_min, UINT event_max) {
CHECK(!instance_) << "There can be only one instance of"
<< " WinAccessibilityEventMonitor at a time.";
instance_ = this;
win_event_hook_handle_ = SetWinEventHook(
event_min,
event_max,
NULL,
&WinAccessibilityEventMonitor::WinEventHookThunk,
GetCurrentProcessId(),
0, // Hook all threads
WINEVENT_OUTOFCONTEXT);
}
WinAccessibilityEventMonitor::~WinAccessibilityEventMonitor() {
UnhookWinEvent(win_event_hook_handle_);
instance_ = NULL;
}
void WinAccessibilityEventMonitor::WaitForNextEvent(
DWORD* out_event,
HWND* out_hwnd,
UINT* out_role,
UINT* out_state,
std::string* out_name) {
if (event_queue_.empty()) {
loop_runner_ = new content::MessageLoopRunner();
loop_runner_->Run();
loop_runner_ = NULL;
}
EventInfo event_info = event_queue_.front();
event_queue_.pop_front();
*out_event = event_info.event;
*out_hwnd = event_info.hwnd;
base::win::ScopedComPtr<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.Receive(), 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);
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);
else
*out_state = 0;
base::win::ScopedBstr name_bstr;
HRESULT hr = acc_obj->get_accName(child_variant, name_bstr.Receive());
if (S_OK == hr)
*out_name = base::UTF16ToUTF8(base::string16(name_bstr));
else
*out_name = "";
}
void WinAccessibilityEventMonitor::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);
if (loop_runner_.get())
loop_runner_->Quit();
}
// static
void CALLBACK WinAccessibilityEventMonitor::WinEventHookThunk(
HWINEVENTHOOK handle,
DWORD event,
HWND hwnd,
LONG obj_id,
LONG child_id,
DWORD event_thread,
DWORD event_time) {
if (instance_) {
instance_->OnWinEventHook(handle, event, hwnd, obj_id, child_id,
event_thread, event_time);
}
}
class NavigationAccessibilityTest : public InProcessBrowserTest {
protected:
NavigationAccessibilityTest() {}
virtual ~NavigationAccessibilityTest() {}
void SendKeyPress(ui::KeyboardCode key) {
gfx::NativeWindow native_window = browser()->window()->GetNativeWindow();
ASSERT_NO_FATAL_FAILURE(
ASSERT_TRUE(
ui_test_utils::SendKeyPressToWindowSync(
native_window, key, false, false, false, false)));
}
private:
base::win::ScopedCOMInitializer com_initializer_;
DISALLOW_COPY_AND_ASSIGN(NavigationAccessibilityTest);
};
// Tests that when focus is in the omnibox and the user types a url and
// presses enter, no focus events are sent on the old document.
// TODO(dmazzoni): enable this test. http://crbug.com/421116
IN_PROC_BROWSER_TEST_F(NavigationAccessibilityTest,
DISABLED_TestNavigateToNewUrl) {
content::BrowserAccessibilityState::GetInstance()->EnableAccessibility();
ui_test_utils::NavigateToURL(browser(),
GURL("data:text/html;charset=utf-8,"
"<head><title>First Page</title></head>"));
chrome::ExecuteCommand(browser(), IDC_FOCUS_LOCATION);
host_resolver()->AddRule("*", "127.0.0.1");
ASSERT_TRUE(test_server()->Start());
GURL main_url(test_server()->GetURL("files/english_page.html"));
OmniboxViewViews* omnibox_view =
BrowserView::GetBrowserViewForBrowser(browser())->
toolbar()->location_bar()->omnibox_view();
omnibox_view->SetUserText(base::UTF8ToUTF16(main_url.spec()),
base::UTF8ToUTF16(main_url.spec()),
false);
WinAccessibilityEventMonitor monitor(EVENT_OBJECT_FOCUS, EVENT_OBJECT_FOCUS);
SendKeyPress(ui::VKEY_RETURN);
for (;;) {
DWORD event;
HWND hwnd;
UINT role;
UINT state;
std::string name;
monitor.WaitForNextEvent(&event, &hwnd, &role, &state, &name);
LOG(INFO) << "Got event: "
<< " event=" << event
<< " hwnd=" << hwnd
<< " role=" << role
<< " state=" << state
<< " name=" << name;
// We should get only focus events.
EXPECT_EQ(EVENT_OBJECT_FOCUS, event);
// We should get only focus events on document objects. (On a page with
// JavaScript or autofocus, additional focus events would be expected.)
EXPECT_EQ(ROLE_SYSTEM_DOCUMENT, role);
// We shouldn't get any events on the first page because from the time
// we start monitoring, the user has already initiated a load to the
// second page.
EXPECT_NE("First Page", name);
// Finish when we get an event on the second page.
if (name == "This page is in English") {
LOG(INFO) << "Got event on second page, finishing test.";
break;
}
}
}
...@@ -939,6 +939,7 @@ ...@@ -939,6 +939,7 @@
'browser/ui/toolbar/test_toolbar_model.h', 'browser/ui/toolbar/test_toolbar_model.h',
'browser/ui/webui/options/autofill_options_interactive_uitest.cc', 'browser/ui/webui/options/autofill_options_interactive_uitest.cc',
'browser/ui/webui/options/language_options_interactive_uitest.cc', 'browser/ui/webui/options/language_options_interactive_uitest.cc',
'browser/ui/views/accessibility/navigation_accessibility_uitest_win.cc',
'test/base/interactive_test_utils.cc', 'test/base/interactive_test_utils.cc',
'test/base/interactive_test_utils.h', 'test/base/interactive_test_utils.h',
'test/base/interactive_test_utils_aura.cc', 'test/base/interactive_test_utils_aura.cc',
......
...@@ -79,6 +79,7 @@ BrowserAccessibilityManager::BrowserAccessibilityManager( ...@@ -79,6 +79,7 @@ BrowserAccessibilityManager::BrowserAccessibilityManager(
factory_(factory), factory_(factory),
tree_(new ui::AXSerializableTree()), tree_(new ui::AXSerializableTree()),
focus_(NULL), focus_(NULL),
user_is_navigating_away_(false),
osk_state_(OSK_ALLOWED) { osk_state_(OSK_ALLOWED) {
tree_->SetDelegate(this); tree_->SetDelegate(this);
} }
...@@ -91,6 +92,7 @@ BrowserAccessibilityManager::BrowserAccessibilityManager( ...@@ -91,6 +92,7 @@ BrowserAccessibilityManager::BrowserAccessibilityManager(
factory_(factory), factory_(factory),
tree_(new ui::AXSerializableTree()), tree_(new ui::AXSerializableTree()),
focus_(NULL), focus_(NULL),
user_is_navigating_away_(false),
osk_state_(OSK_ALLOWED) { osk_state_(OSK_ALLOWED) {
tree_->SetDelegate(this); tree_->SetDelegate(this);
Initialize(initial_tree); Initialize(initial_tree);
...@@ -152,6 +154,22 @@ void BrowserAccessibilityManager::OnWindowBlurred() { ...@@ -152,6 +154,22 @@ void BrowserAccessibilityManager::OnWindowBlurred() {
NotifyAccessibilityEvent(ui::AX_EVENT_BLUR, GetFromAXNode(focus_)); NotifyAccessibilityEvent(ui::AX_EVENT_BLUR, GetFromAXNode(focus_));
} }
void BrowserAccessibilityManager::UserIsNavigatingAway() {
user_is_navigating_away_ = true;
}
void BrowserAccessibilityManager::UserIsReloading() {
user_is_navigating_away_ = true;
}
void BrowserAccessibilityManager::NavigationSucceeded() {
user_is_navigating_away_ = false;
}
void BrowserAccessibilityManager::NavigationFailed() {
user_is_navigating_away_ = false;
}
void BrowserAccessibilityManager::GotMouseDown() { void BrowserAccessibilityManager::GotMouseDown() {
osk_state_ = OSK_ALLOWED_WITHIN_FOCUSED_OBJECT; osk_state_ = OSK_ALLOWED_WITHIN_FOCUSED_OBJECT;
NotifyAccessibilityEvent(ui::AX_EVENT_FOCUS, GetFromAXNode(focus_)); NotifyAccessibilityEvent(ui::AX_EVENT_FOCUS, GetFromAXNode(focus_));
......
...@@ -146,6 +146,12 @@ class CONTENT_EXPORT BrowserAccessibilityManager : public ui::AXTreeDelegate { ...@@ -146,6 +146,12 @@ class CONTENT_EXPORT BrowserAccessibilityManager : public ui::AXTreeDelegate {
// view lost focus. // view lost focus.
virtual void OnWindowBlurred(); virtual void OnWindowBlurred();
// Notify the accessibility manager about page navigation.
void UserIsNavigatingAway();
virtual void UserIsReloading();
void NavigationSucceeded();
void NavigationFailed();
// Called to notify the accessibility manager that a mouse down event // Called to notify the accessibility manager that a mouse down event
// occurred in the tab. // occurred in the tab.
void GotMouseDown(); void GotMouseDown();
...@@ -304,6 +310,9 @@ class CONTENT_EXPORT BrowserAccessibilityManager : public ui::AXTreeDelegate { ...@@ -304,6 +310,9 @@ class CONTENT_EXPORT BrowserAccessibilityManager : public ui::AXTreeDelegate {
// A mapping from a node id to its wrapper of type BrowserAccessibility. // A mapping from a node id to its wrapper of type BrowserAccessibility.
base::hash_map<int32, BrowserAccessibility*> id_wrapper_map_; base::hash_map<int32, BrowserAccessibility*> id_wrapper_map_;
// True if the user has initiated a navigation to another page.
bool user_is_navigating_away_;
// The on-screen keyboard state. // The on-screen keyboard state.
OnScreenKeyboardState osk_state_; OnScreenKeyboardState osk_state_;
......
...@@ -179,6 +179,10 @@ void BrowserAccessibilityManagerWin::OnWindowFocused() { ...@@ -179,6 +179,10 @@ void BrowserAccessibilityManagerWin::OnWindowFocused() {
BrowserAccessibilityManager::OnWindowFocused(); BrowserAccessibilityManager::OnWindowFocused();
} }
void BrowserAccessibilityManagerWin::UserIsReloading() {
MaybeCallNotifyWinEvent(IA2_EVENT_DOCUMENT_RELOAD, GetRoot());
}
void BrowserAccessibilityManagerWin::NotifyAccessibilityEvent( void BrowserAccessibilityManagerWin::NotifyAccessibilityEvent(
ui::AXEvent event_type, ui::AXEvent event_type,
BrowserAccessibility* node) { BrowserAccessibility* node) {
...@@ -188,6 +192,11 @@ void BrowserAccessibilityManagerWin::NotifyAccessibilityEvent( ...@@ -188,6 +192,11 @@ void BrowserAccessibilityManagerWin::NotifyAccessibilityEvent(
return; return;
} }
// Don't fire events when this document might be stale as the user has
// started navigating to a new document.
if (user_is_navigating_away_)
return;
// Inline text boxes are an internal implementation detail, we don't // Inline text boxes are an internal implementation detail, we don't
// expose them to Windows. // expose them to Windows.
if (node->GetRole() == ui::AX_ROLE_INLINE_TEXT_BOX) if (node->GetRole() == ui::AX_ROLE_INLINE_TEXT_BOX)
......
...@@ -41,8 +41,9 @@ class CONTENT_EXPORT BrowserAccessibilityManagerWin ...@@ -41,8 +41,9 @@ class CONTENT_EXPORT BrowserAccessibilityManagerWin
virtual void OnNodeCreated(ui::AXNode* node) override; virtual void OnNodeCreated(ui::AXNode* node) override;
// BrowserAccessibilityManager methods // BrowserAccessibilityManager methods
virtual void OnWindowFocused() override; void OnWindowFocused() override;
virtual void NotifyAccessibilityEvent( void UserIsReloading() override;
void NotifyAccessibilityEvent(
ui::AXEvent event_type, BrowserAccessibility* node) override; ui::AXEvent event_type, BrowserAccessibility* node) override;
// Track this object and post a VISIBLE_DATA_CHANGED notification when // Track this object and post a VISIBLE_DATA_CHANGED notification when
......
...@@ -2529,6 +2529,17 @@ void WebContentsImpl::DidStartProvisionalLoad( ...@@ -2529,6 +2529,17 @@ void WebContentsImpl::DidStartProvisionalLoad(
observers_, observers_,
DidStartProvisionalLoadForFrame( DidStartProvisionalLoadForFrame(
render_frame_host, validated_url, is_error_page, is_iframe_srcdoc)); render_frame_host, validated_url, is_error_page, is_iframe_srcdoc));
// Notify accessibility if this is a reload.
NavigationEntry* entry = controller_.GetVisibleEntry();
if (entry && ui::PageTransitionCoreTypeIs(
entry->GetTransitionType(), ui::PAGE_TRANSITION_RELOAD)) {
FrameTreeNode* ftn = render_frame_host->frame_tree_node();
BrowserAccessibilityManager* manager =
ftn->current_frame_host()->browser_accessibility_manager();
if (manager)
manager->UserIsReloading();
}
} }
void WebContentsImpl::DidStartNavigationTransition( void WebContentsImpl::DidStartNavigationTransition(
...@@ -2550,6 +2561,12 @@ void WebContentsImpl::DidFailProvisionalLoadWithError( ...@@ -2550,6 +2561,12 @@ void WebContentsImpl::DidFailProvisionalLoadWithError(
validated_url, validated_url,
params.error_code, params.error_code,
params.error_description)); params.error_description));
FrameTreeNode* ftn = render_frame_host->frame_tree_node();
BrowserAccessibilityManager* manager =
ftn->current_frame_host()->browser_accessibility_manager();
if (manager)
manager->NavigationFailed();
} }
void WebContentsImpl::DidFailLoadWithError( void WebContentsImpl::DidFailLoadWithError(
...@@ -2625,6 +2642,11 @@ void WebContentsImpl::DidCommitProvisionalLoad( ...@@ -2625,6 +2642,11 @@ void WebContentsImpl::DidCommitProvisionalLoad(
observers_, observers_,
DidCommitProvisionalLoadForFrame( DidCommitProvisionalLoadForFrame(
render_frame_host, url, transition_type)); render_frame_host, url, transition_type));
BrowserAccessibilityManager* manager =
render_frame_host->browser_accessibility_manager();
if (manager)
manager->NavigationSucceeded();
} }
void WebContentsImpl::DidNavigateMainFramePreCommit( void WebContentsImpl::DidNavigateMainFramePreCommit(
...@@ -3800,6 +3822,17 @@ void WebContentsImpl::DidStartLoading(RenderFrameHost* render_frame_host, ...@@ -3800,6 +3822,17 @@ void WebContentsImpl::DidStartLoading(RenderFrameHost* render_frame_host,
bool to_different_document) { bool to_different_document) {
SetIsLoading(render_frame_host->GetRenderViewHost(), true, SetIsLoading(render_frame_host->GetRenderViewHost(), true,
to_different_document, NULL); to_different_document, NULL);
// Notify accessibility that the user is navigating away from the
// current document.
//
// TODO(dmazzoni): do this using a WebContentsObserver.
FrameTreeNode* ftn = static_cast<RenderFrameHostImpl*>(render_frame_host)->
frame_tree_node();
BrowserAccessibilityManager* manager =
ftn->current_frame_host()->browser_accessibility_manager();
if (manager)
manager->UserIsNavigatingAway();
} }
void WebContentsImpl::DidStopLoading(RenderFrameHost* render_frame_host) { void WebContentsImpl::DidStopLoading(RenderFrameHost* render_frame_host) {
......
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