Commit ad8ae0f7 authored by dtseng's avatar dtseng Committed by Commit bot

Implement automatic load of composed/embedded automation trees

Introduces the metadata required to link together the desktop tree and a views::WebView. Additionally, implements the js side logic to resolve these links.

A views::WebView now gets assigned an AXRole of webView and its immediate child resolves to the rootWebArea it hosts. This resolution is done dynamically at runtime using the underlying AX tree id which itself is mapped using AXTreeIDRegistry back in the browser. If the hosting webView has yet to load its child rootWebArea, enableFrame will be called to and a callback waiting for the subroot's data; this auto load occurs as soon as a webView is seen during a tree update and results in a childrenChanged event once its child rootWebArea is available. When a caller asks for the parent of a rootWebArea, a dynamic lookup is also performed to retrieve a hosting webView, if any.

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

Cr-Commit-Position: refs/heads/master@{#302648}
parent 78a73470
// Copyright 2014 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 "chrome/browser/accessibility/ax_tree_id_registry.h"
#include "base/memory/singleton.h"
// static
AXTreeIDRegistry* AXTreeIDRegistry::GetInstance() {
return Singleton<AXTreeIDRegistry>::get();
}
int AXTreeIDRegistry::GetOrCreateAXTreeID(int process_id, int routing_id) {
FrameID frame_id(process_id, routing_id);
std::map<FrameID, AXTreeID>::iterator it;
it = frame_to_ax_tree_id_map_.find(frame_id);
if (it != frame_to_ax_tree_id_map_.end())
return it->second;
int new_id = ++ax_tree_id_counter_;
frame_to_ax_tree_id_map_[frame_id] = new_id;
ax_tree_to_frame_id_map_[new_id] = frame_id;
return new_id;
}
AXTreeIDRegistry::FrameID AXTreeIDRegistry::GetFrameID(int ax_tree_id) {
std::map<AXTreeID, FrameID>::iterator it;
it = ax_tree_to_frame_id_map_.find(ax_tree_id);
if (it != ax_tree_to_frame_id_map_.end())
return it->second;
return FrameID(-1, -1);
}
void AXTreeIDRegistry::RemoveAXTreeID(int ax_tree_id) {
std::map<AXTreeID, FrameID>::iterator it;
it = ax_tree_to_frame_id_map_.find(ax_tree_id);
if (it != ax_tree_to_frame_id_map_.end()) {
frame_to_ax_tree_id_map_.erase(it->second);
ax_tree_to_frame_id_map_.erase(it);
}
}
AXTreeIDRegistry::AXTreeIDRegistry() : ax_tree_id_counter_(-1) {
// Always populate default desktop tree value (0 -> 0, 0).
GetOrCreateAXTreeID(0, 0);
}
AXTreeIDRegistry::~AXTreeIDRegistry() {
}
// Copyright 2014 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 CHROME_BROWSER_ACCESSIBILITY_AX_TREE_ID_REGISTRY_H_
#define CHROME_BROWSER_ACCESSIBILITY_AX_TREE_ID_REGISTRY_H_
#include <map>
#include "base/stl_util.h"
template <typename T>
struct DefaultSingletonTraits;
// A class which generates a unique id given a process id and routing id.
// Currently, placeholder implementation pending finalization of out of process
// iframes.
class AXTreeIDRegistry {
public:
typedef int AXTreeID;
typedef std::pair<int, int> FrameID;
// Get the single instance of this class.
static AXTreeIDRegistry* GetInstance();
// Obtains a unique id given a |process_id| and |routing_id|. Placeholder
// for full implementation once out of process iframe accessibility finalizes.
int GetOrCreateAXTreeID(int process_id, int routing_id);
FrameID GetFrameID(int ax_tree_id);
void RemoveAXTreeID(int ax_tree_id);
private:
friend struct DefaultSingletonTraits<AXTreeIDRegistry>;
AXTreeIDRegistry();
virtual ~AXTreeIDRegistry();
// Tracks the current unique ax frame id.
int ax_tree_id_counter_;
// Maps an accessibility tree to its frame via ids.
std::map<AXTreeID, FrameID> ax_tree_to_frame_id_map_;
// Maps frames to an accessibility tree via ids.
std::map<FrameID, AXTreeID> frame_to_ax_tree_id_map_;
DISALLOW_COPY_AND_ASSIGN(AXTreeIDRegistry);
};
#endif // CHROME_BROWSER_ACCESSIBILITY_AX_TREE_ID_REGISTRY_H_
...@@ -159,6 +159,11 @@ IN_PROC_BROWSER_TEST_F(AutomationApiTest, DesktopActions) { ...@@ -159,6 +159,11 @@ IN_PROC_BROWSER_TEST_F(AutomationApiTest, DesktopActions) {
ASSERT_TRUE(RunExtensionSubtest("automation/tests/desktop", "actions.html")) ASSERT_TRUE(RunExtensionSubtest("automation/tests/desktop", "actions.html"))
<< message_; << message_;
} }
IN_PROC_BROWSER_TEST_F(AutomationApiTest, DesktopLoadTabs) {
ASSERT_TRUE(RunExtensionSubtest("automation/tests/desktop", "load_tabs.html"))
<< message_;
}
#else #else
IN_PROC_BROWSER_TEST_F(AutomationApiTest, DesktopNotSupported) { IN_PROC_BROWSER_TEST_F(AutomationApiTest, DesktopNotSupported) {
ASSERT_TRUE(RunExtensionSubtest("automation/tests/desktop", ASSERT_TRUE(RunExtensionSubtest("automation/tests/desktop",
...@@ -336,9 +341,9 @@ class FakeAutomationInternalEnableTabFunction ...@@ -336,9 +341,9 @@ class FakeAutomationInternalEnableTabFunction
base::Bind(&TreeSerializationState::InitializeTree0, base::Bind(&TreeSerializationState::InitializeTree0,
base::Unretained(&state), base::Unretained(&state),
base::Unretained(browser_context()))); base::Unretained(browser_context())));
return RespondNow( // TODO(aboxhall): Need to rewrite this test in terms of tree ids.
ArgumentList(api::automation_internal::EnableTab::Results::Create( return RespondNow(ArgumentList(
kPid, kTab0Rid))); api::automation_internal::EnableTab::Results::Create(0)));
} }
if (tab_id == 1) { if (tab_id == 1) {
// tab 1 <--> tree1 // tab 1 <--> tree1
...@@ -347,9 +352,8 @@ class FakeAutomationInternalEnableTabFunction ...@@ -347,9 +352,8 @@ class FakeAutomationInternalEnableTabFunction
base::Bind(&TreeSerializationState::InitializeTree1, base::Bind(&TreeSerializationState::InitializeTree1,
base::Unretained(&state), base::Unretained(&state),
base::Unretained(browser_context()))); base::Unretained(browser_context())));
return RespondNow( return RespondNow(ArgumentList(
ArgumentList(api::automation_internal::EnableTab::Results::Create( api::automation_internal::EnableTab::Results::Create(0)));
kPid, kTab1Rid)));
} }
return RespondNow(Error("Unrecognised tab_id")); return RespondNow(Error("Unrecognised tab_id"));
} }
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include <vector> #include <vector>
#include "base/strings/string_number_conversions.h" #include "base/strings/string_number_conversions.h"
#include "chrome/browser/accessibility/ax_tree_id_registry.h"
#include "chrome/browser/extensions/api/automation_internal/automation_action_adapter.h" #include "chrome/browser/extensions/api/automation_internal/automation_action_adapter.h"
#include "chrome/browser/extensions/api/automation_internal/automation_util.h" #include "chrome/browser/extensions/api/automation_internal/automation_util.h"
#include "chrome/browser/extensions/api/tabs/tabs_constants.h" #include "chrome/browser/extensions/api/tabs/tabs_constants.h"
...@@ -17,6 +18,7 @@ ...@@ -17,6 +18,7 @@
#include "chrome/common/extensions/api/automation_internal.h" #include "chrome/common/extensions/api/automation_internal.h"
#include "chrome/common/extensions/manifest_handlers/automation.h" #include "chrome/common/extensions/manifest_handlers/automation.h"
#include "content/public/browser/ax_event_notification_details.h" #include "content/public/browser/ax_event_notification_details.h"
#include "content/public/browser/browser_accessibility_state.h"
#include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h" #include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_widget_host.h" #include "content/public/browser/render_widget_host.h"
...@@ -35,9 +37,7 @@ class AutomationWebContentsObserver; ...@@ -35,9 +37,7 @@ class AutomationWebContentsObserver;
DEFINE_WEB_CONTENTS_USER_DATA_KEY(extensions::AutomationWebContentsObserver); DEFINE_WEB_CONTENTS_USER_DATA_KEY(extensions::AutomationWebContentsObserver);
namespace { namespace {
const int kDesktopProcessID = 0; const int kDesktopTreeID = 0;
const int kDesktopRoutingID = 0;
const char kCannotRequestAutomationOnPage[] = const char kCannotRequestAutomationOnPage[] =
"Cannot request automation tree on url \"*\". " "Cannot request automation tree on url \"*\". "
"Extension manifest must request permission to access this host."; "Extension manifest must request permission to access this host.";
...@@ -164,10 +164,35 @@ AutomationInternalEnableTabFunction::Run() { ...@@ -164,10 +164,35 @@ AutomationInternalEnableTabFunction::Run() {
} }
AutomationWebContentsObserver::CreateForWebContents(contents); AutomationWebContentsObserver::CreateForWebContents(contents);
contents->EnableTreeOnlyAccessibilityMode(); contents->EnableTreeOnlyAccessibilityMode();
return RespondNow( int ax_tree_id = AXTreeIDRegistry::GetInstance()->GetOrCreateAXTreeID(
ArgumentList(api::automation_internal::EnableTab::Results::Create( rfh->GetProcess()->GetID(), rfh->GetRoutingID());
rfh->GetProcess()->GetID(), rfh->GetRoutingID()))); return RespondNow(ArgumentList(
} api::automation_internal::EnableTab::Results::Create(ax_tree_id)));
}
ExtensionFunction::ResponseAction AutomationInternalEnableFrameFunction::Run() {
// TODO(dtseng): Limited to desktop tree for now pending out of proc iframes.
#if defined(OS_CHROMEOS)
using api::automation_internal::EnableFrame::Params;
scoped_ptr<Params> params(Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
AXTreeIDRegistry::FrameID frame_id =
AXTreeIDRegistry::GetInstance()->GetFrameID(params->tree_id);
content::RenderFrameHost* rfh =
content::RenderFrameHost::FromID(frame_id.first, frame_id.second);
if (!rfh)
return RespondNow(Error("unable to load tab"));
content::WebContents* contents =
content::WebContents::FromRenderFrameHost(rfh);
AutomationWebContentsObserver::CreateForWebContents(contents);
contents->EnableTreeOnlyAccessibilityMode();
return RespondNow(NoArguments());
#endif
return RespondNow(Error("enableFrame is only supported on Chrome OS"));
}
ExtensionFunction::ResponseAction ExtensionFunction::ResponseAction
AutomationInternalPerformActionFunction::Run() { AutomationInternalPerformActionFunction::Run() {
...@@ -178,8 +203,7 @@ AutomationInternalPerformActionFunction::Run() { ...@@ -178,8 +203,7 @@ AutomationInternalPerformActionFunction::Run() {
scoped_ptr<Params> params(Params::Create(*args_)); scoped_ptr<Params> params(Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get()); EXTENSION_FUNCTION_VALIDATE(params.get());
if (params->args.process_id == kDesktopProcessID && if (params->args.tree_id == kDesktopTreeID) {
params->args.routing_id == kDesktopRoutingID) {
#if defined(OS_CHROMEOS) #if defined(OS_CHROMEOS)
return RouteActionToAdapter( return RouteActionToAdapter(
params.get(), AutomationManagerAsh::GetInstance()); params.get(), AutomationManagerAsh::GetInstance());
...@@ -189,9 +213,10 @@ AutomationInternalPerformActionFunction::Run() { ...@@ -189,9 +213,10 @@ AutomationInternalPerformActionFunction::Run() {
" platform does not support desktop automation")); " platform does not support desktop automation"));
#endif // defined(OS_CHROMEOS) #endif // defined(OS_CHROMEOS)
} }
AXTreeIDRegistry::FrameID frame_id =
AXTreeIDRegistry::GetInstance()->GetFrameID(params->args.tree_id);
content::RenderFrameHost* rfh = content::RenderFrameHost* rfh =
content::RenderFrameHost::FromID(params->args.process_id, content::RenderFrameHost::FromID(frame_id.first, frame_id.second);
params->args.routing_id);
if (!rfh) if (!rfh)
return RespondNow(Error("Ignoring action on destroyed node")); return RespondNow(Error("Ignoring action on destroyed node"));
......
...@@ -58,6 +58,15 @@ class AutomationInternalPerformActionFunction ...@@ -58,6 +58,15 @@ class AutomationInternalPerformActionFunction
AutomationActionAdapter* adapter); AutomationActionAdapter* adapter);
}; };
class AutomationInternalEnableFrameFunction : public UIThreadExtensionFunction {
DECLARE_EXTENSION_FUNCTION("automationInternal.enableFrame",
AUTOMATIONINTERNAL_PERFORMACTION)
protected:
~AutomationInternalEnableFrameFunction() override {}
ExtensionFunction::ResponseAction Run() override;
};
class AutomationInternalEnableDesktopFunction class AutomationInternalEnableDesktopFunction
: public UIThreadExtensionFunction { : public UIThreadExtensionFunction {
DECLARE_EXTENSION_FUNCTION("automationInternal.enableDesktop", DECLARE_EXTENSION_FUNCTION("automationInternal.enableDesktop",
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include <utility> #include <utility>
#include "base/values.h" #include "base/values.h"
#include "chrome/browser/accessibility/ax_tree_id_registry.h"
#include "chrome/common/extensions/api/automation_internal.h" #include "chrome/common/extensions/api/automation_internal.h"
#include "extensions/browser/event_router.h" #include "extensions/browser/event_router.h"
#include "ui/accessibility/ax_enums.h" #include "ui/accessibility/ax_enums.h"
...@@ -136,8 +137,9 @@ void DispatchAccessibilityEventsToAutomation( ...@@ -136,8 +137,9 @@ void DispatchAccessibilityEventsToAutomation(
const content::AXEventNotificationDetails& event = *iter; const content::AXEventNotificationDetails& event = *iter;
AXEventParams ax_event_params; AXEventParams ax_event_params;
ax_event_params.process_id = event.process_id; ax_event_params.tree_id =
ax_event_params.routing_id = event.routing_id; AXTreeIDRegistry::GetInstance()->GetOrCreateAXTreeID(event.process_id,
event.routing_id);
ax_event_params.event_type = ToString(iter->event_type); ax_event_params.event_type = ToString(iter->event_type);
ax_event_params.target_id = event.id; ax_event_params.target_id = event.id;
...@@ -168,11 +170,13 @@ void DispatchTreeDestroyedEventToAutomation( ...@@ -168,11 +170,13 @@ void DispatchTreeDestroyedEventToAutomation(
int process_id, int process_id,
int routing_id, int routing_id,
content::BrowserContext* browser_context) { content::BrowserContext* browser_context) {
int tree_id = AXTreeIDRegistry::GetInstance()->GetOrCreateAXTreeID(
process_id, routing_id);
DispatchEventInternal( DispatchEventInternal(
browser_context, browser_context,
api::automation_internal::OnAccessibilityTreeDestroyed::kEventName, api::automation_internal::OnAccessibilityTreeDestroyed::kEventName,
api::automation_internal::OnAccessibilityTreeDestroyed::Create( api::automation_internal::OnAccessibilityTreeDestroyed::Create(tree_id));
process_id, routing_id)); AXTreeIDRegistry::GetInstance()->RemoveAXTreeID(tree_id);
} }
} // namespace automation_util } // namespace automation_util
......
...@@ -44,8 +44,6 @@ void AutomationManagerAsh::HandleEvent(BrowserContext* context, ...@@ -44,8 +44,6 @@ void AutomationManagerAsh::HandleEvent(BrowserContext* context,
return; return;
} }
// TODO(dtseng): Events should only be delivered to extensions with the
// desktop permission.
views::Widget* widget = view->GetWidget(); views::Widget* widget = view->GetWidget();
if (!widget) if (!widget)
return; return;
...@@ -83,7 +81,8 @@ void AutomationManagerAsh::SetSelection(int32 id, int32 start, int32 end) { ...@@ -83,7 +81,8 @@ void AutomationManagerAsh::SetSelection(int32 id, int32 start, int32 end) {
current_tree_->SetSelection(id, start, end); current_tree_->SetSelection(id, start, end);
} }
AutomationManagerAsh::AutomationManagerAsh() : enabled_(false) {} AutomationManagerAsh::AutomationManagerAsh() : enabled_(false) {
}
AutomationManagerAsh::~AutomationManagerAsh() {} AutomationManagerAsh::~AutomationManagerAsh() {}
......
...@@ -7,7 +7,6 @@ ...@@ -7,7 +7,6 @@
#include "base/basictypes.h" #include "base/basictypes.h"
#include "base/memory/scoped_ptr.h" #include "base/memory/scoped_ptr.h"
#include "chrome/browser/extensions/api/automation_internal/automation_action_adapter.h" #include "chrome/browser/extensions/api/automation_internal/automation_action_adapter.h"
#include "chrome/browser/ui/ash/accessibility/ax_tree_source_ash.h" #include "chrome/browser/ui/ash/accessibility/ax_tree_source_ash.h"
#include "ui/accessibility/ax_tree_serializer.h" #include "ui/accessibility/ax_tree_serializer.h"
......
...@@ -6,8 +6,15 @@ ...@@ -6,8 +6,15 @@
#include <vector> #include <vector>
#include "chrome/browser/accessibility/ax_tree_id_registry.h"
#include "chrome/browser/ui/ash/accessibility/automation_manager_ash.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "ui/views/accessibility/ax_aura_obj_cache.h" #include "ui/views/accessibility/ax_aura_obj_cache.h"
#include "ui/views/accessibility/ax_aura_obj_wrapper.h" #include "ui/views/accessibility/ax_aura_obj_wrapper.h"
#include "ui/views/accessibility/ax_view_obj_wrapper.h"
#include "ui/views/controls/webview/webview.h"
using views::AXAuraObjCache; using views::AXAuraObjCache;
using views::AXAuraObjWrapper; using views::AXAuraObjWrapper;
...@@ -90,6 +97,20 @@ AXAuraObjWrapper* AXTreeSourceAsh::GetNull() const { ...@@ -90,6 +97,20 @@ AXAuraObjWrapper* AXTreeSourceAsh::GetNull() const {
void AXTreeSourceAsh::SerializeNode( void AXTreeSourceAsh::SerializeNode(
AXAuraObjWrapper* node, ui::AXNodeData* out_data) const { AXAuraObjWrapper* node, ui::AXNodeData* out_data) const {
node->Serialize(out_data); node->Serialize(out_data);
if (out_data->role == ui::AX_ROLE_WEB_VIEW) {
views::View* view = static_cast<views::AXViewObjWrapper*>(node)->view();
content::WebContents* contents =
static_cast<views::WebView*>(view)->GetWebContents();
content::RenderFrameHost* rfh = contents->GetMainFrame();
if (rfh) {
int process_id = rfh->GetProcess()->GetID();
int routing_id = rfh->GetRoutingID();
int ax_tree_id = AXTreeIDRegistry::GetInstance()->GetOrCreateAXTreeID(
process_id, routing_id);
out_data->AddIntAttribute(ui::AX_ATTR_CHILD_TREE_ID, ax_tree_id);
}
}
} }
std::string AXTreeSourceAsh::ToString( std::string AXTreeSourceAsh::ToString(
......
...@@ -1916,6 +1916,8 @@ ...@@ -1916,6 +1916,8 @@
'browser/accessibility/accessibility_events.h', 'browser/accessibility/accessibility_events.h',
'browser/accessibility/accessibility_extension_api_constants.cc', 'browser/accessibility/accessibility_extension_api_constants.cc',
'browser/accessibility/accessibility_extension_api_constants.h', 'browser/accessibility/accessibility_extension_api_constants.h',
'browser/accessibility/ax_tree_id_registry.cc',
'browser/accessibility/ax_tree_id_registry.h',
'browser/accessibility/invert_bubble_prefs.cc', 'browser/accessibility/invert_bubble_prefs.cc',
'browser/accessibility/invert_bubble_prefs.h', 'browser/accessibility/invert_bubble_prefs.h',
'browser/auto_launch_trial.cc', 'browser/auto_launch_trial.cc',
......
...@@ -180,6 +180,7 @@ ...@@ -180,6 +180,7 @@
tooltip, tooltip,
valueIndicator, valueIndicator,
webArea, webArea,
webView,
window window
}; };
...@@ -238,14 +239,11 @@ ...@@ -238,14 +239,11 @@
// A single node in an Automation tree. // A single node in an Automation tree.
[nocompile, noinline_doc] dictionary AutomationNode { [nocompile, noinline_doc] dictionary AutomationNode {
// The root node of the tree containing this AutomationNode. // The root node of the tree containing this AutomationNode.
AutomationRootNode root; AutomationNode root;
// Whether this AutomationNode is an AutomationRootNode. // Whether this AutomationNode is root node.
boolean isRootNode; boolean isRootNode;
// Unique ID to identify this node.
long id;
// The role of this node. // The role of this node.
automation.RoleType role; automation.RoleType role;
...@@ -293,35 +291,8 @@ ...@@ -293,35 +291,8 @@
EventType eventType, AutomationListener listener, boolean capture); EventType eventType, AutomationListener listener, boolean capture);
}; };
// Called when the <code>AutomationRootNode</code> for the page is available. // Called when the <code>AutomationNode</code> for the page is available.
callback RootCallback = void(AutomationRootNode rootNode); callback RootCallback = void(AutomationNode rootNode);
// The root node of the automation tree for a single frame or desktop.
// This may be:
// <ul>
// <li> The desktop
// <li> The top frame of a page
// <li> A frame or iframe within a page
// </ul>
// Thus, an <code>AutomationRootNode</code> may be a descendant of one or
// more <code>AutomationRootNode</code>s, and in turn have one or more
// <code>AutomationRootNode</code>s in its descendants. Thus, the
// <code>root</code> property of the <code>AutomationRootNode</code> will be
// the immediate parent <code>AutomationRootNode</code>, or <code>null</code>
// if this is the top-level <code>AutomationRootNode</code>.
//
// Extends $(ref:automation.AutomationNode).
[nocompile, noinline_doc] dictionary AutomationRootNode {
// TODO(aboxhall/dtseng): implement loading. Kept separate to not include
// in generated docs.
// Whether this AutomationRootNode is loaded or not. If false, call load()
// to get the contents.
boolean loaded;
// Load the accessibility tree for this AutomationRootNode.
static void load(RootCallback callback);
};
interface Functions { interface Functions {
// Get the automation tree for the tab with the given tabId, or the current // Get the automation tree for the tab with the given tabId, or the current
......
...@@ -46,11 +46,8 @@ namespace automationInternal { ...@@ -46,11 +46,8 @@ namespace automationInternal {
// tree. See ui/accessibility/ax_tree_update.h for an extended explanation of // tree. See ui/accessibility/ax_tree_update.h for an extended explanation of
// the tree update format. // the tree update format.
dictionary AXEventParams { dictionary AXEventParams {
// The process id of the renderer host. // The tree id of the web contents that this update is for.
long processID; long treeID;
// The routing id of the web contents that this update is for.
long routingID;
// ID of the node that the event applies to. // ID of the node that the event applies to.
long targetID; long targetID;
...@@ -73,8 +70,7 @@ namespace automationInternal { ...@@ -73,8 +70,7 @@ namespace automationInternal {
// Arguments required for all actions supplied to performAction. // Arguments required for all actions supplied to performAction.
dictionary PerformActionRequiredParams { dictionary PerformActionRequiredParams {
long processID; long treeID;
long routingID;
long automationNodeID; long automationNodeID;
ActionType actionType; ActionType actionType;
}; };
...@@ -85,19 +81,22 @@ namespace automationInternal { ...@@ -85,19 +81,22 @@ namespace automationInternal {
long endIndex; long endIndex;
}; };
// Returns the process id and routing id of the tab whose accessibility was // Returns the accessibility tree id of the web contents who's accessibility
// enabled using enable(). // was enabled using enableTab().
callback EnableTabCallback = void(long processID, long routingID); callback EnableTabCallback = void(long tree_id);
// Callback called when enableDesktop() returns. // Callback called when enableDesktop() returns.
callback EnableDesktopCallback = void(); callback EnableDesktopCallback = void();
interface Functions { interface Functions {
// Enable automation of the tab with the given id, or the active tab if no // Enable automation of the tab with the given id, or the active tab if no
// tab id is given, and retrieves its process and routing ids for use in // tab id is given, and retrieves accessibility tree id for use in
// future updates. // future updates.
static void enableTab(optional long tabId, EnableTabCallback callback); static void enableTab(optional long tabId, EnableTabCallback callback);
// Enable automation of the frame with the given tree id.
static void enableFrame(long tree_id);
// Enables desktop automation. // Enables desktop automation.
static void enableDesktop(EnableDesktopCallback callback); static void enableDesktop(EnableDesktopCallback callback);
...@@ -110,6 +109,6 @@ namespace automationInternal { ...@@ -110,6 +109,6 @@ namespace automationInternal {
// Fired when an accessibility event occurs // Fired when an accessibility event occurs
static void onAccessibilityEvent(AXEventParams update); static void onAccessibilityEvent(AXEventParams update);
static void onAccessibilityTreeDestroyed(long processID, long routingID); static void onAccessibilityTreeDestroyed(long treeID);
}; };
}; };
...@@ -40,18 +40,16 @@ AutomationNodeImpl.prototype = { ...@@ -40,18 +40,16 @@ AutomationNodeImpl.prototype = {
}, },
parent: function() { parent: function() {
return this.rootImpl.get(this.parentID); return this.hostTree || this.rootImpl.get(this.parentID);
}, },
firstChild: function() { firstChild: function() {
var node = this.rootImpl.get(this.childIds[0]); return this.childTree || this.rootImpl.get(this.childIds[0]);
return node;
}, },
lastChild: function() { lastChild: function() {
var childIds = this.childIds; var childIds = this.childIds;
var node = this.rootImpl.get(childIds[childIds.length - 1]); return this.childTree || this.rootImpl.get(childIds[childIds.length - 1]);
return node;
}, },
children: function() { children: function() {
...@@ -113,12 +111,18 @@ AutomationNodeImpl.prototype = { ...@@ -113,12 +111,18 @@ AutomationNodeImpl.prototype = {
} }
}, },
toJSON: function() {
return { treeID: this.treeID,
id: this.id,
role: this.role,
attributes: this.attributes };
},
dispatchEvent: function(eventType) { dispatchEvent: function(eventType) {
var path = []; var path = [];
var parent = this.parent(); var parent = this.parent();
while (parent) { while (parent) {
path.push(parent); path.push(parent);
// TODO(aboxhall/dtseng): handle unloaded parent node
parent = parent.parent(); parent = parent.parent();
} }
var event = new AutomationEvent(eventType, this.wrapper); var event = new AutomationEvent(eventType, this.wrapper);
...@@ -194,8 +198,7 @@ AutomationNodeImpl.prototype = { ...@@ -194,8 +198,7 @@ AutomationNodeImpl.prototype = {
performAction_: function(actionType, opt_args) { performAction_: function(actionType, opt_args) {
// Not yet initialized. // Not yet initialized.
if (this.rootImpl.processID === undefined || if (this.rootImpl.treeID === undefined ||
this.rootImpl.routingID === undefined ||
this.id === undefined) { this.id === undefined) {
return; return;
} }
...@@ -206,8 +209,7 @@ AutomationNodeImpl.prototype = { ...@@ -206,8 +209,7 @@ AutomationNodeImpl.prototype = {
' {"interact": true} in the "automation" manifest key.'); ' {"interact": true} in the "automation" manifest key.');
} }
automationInternal.performAction({ processID: this.rootImpl.processID, automationInternal.performAction({ treeID: this.rootImpl.treeID,
routingID: this.rootImpl.routingID,
automationNodeID: this.id, automationNodeID: this.id,
actionType: actionType }, actionType: actionType },
opt_args || {}); opt_args || {});
...@@ -255,6 +257,7 @@ var ATTRIBUTE_NAME_TO_ATTRIBUTE_ID = { ...@@ -255,6 +257,7 @@ var ATTRIBUTE_NAME_TO_ATTRIBUTE_ID = {
* @const * @const
*/ */
var ATTRIBUTE_BLACKLIST = {'activedescendantId': true, var ATTRIBUTE_BLACKLIST = {'activedescendantId': true,
'childTreeId': true,
'controlsIds': true, 'controlsIds': true,
'describedbyIds': true, 'describedbyIds': true,
'flowtoIds': true, 'flowtoIds': true,
...@@ -276,14 +279,13 @@ var ATTRIBUTE_BLACKLIST = {'activedescendantId': true, ...@@ -276,14 +279,13 @@ var ATTRIBUTE_BLACKLIST = {'activedescendantId': true,
* AutomationNode object. * AutomationNode object.
* Thus, tree traversals amount to a lookup in our hash. * Thus, tree traversals amount to a lookup in our hash.
* *
* The tree itself is identified by the process id and routing id of the * The tree itself is identified by the accessibility tree id of the
* renderer widget host. * renderer widget host.
* @constructor * @constructor
*/ */
function AutomationRootNodeImpl(processID, routingID) { function AutomationRootNodeImpl(treeID) {
AutomationNodeImpl.call(this, this); AutomationNodeImpl.call(this, this);
this.processID = processID; this.treeID = treeID;
this.routingID = routingID;
this.axNodeDataCache_ = {}; this.axNodeDataCache_ = {};
} }
...@@ -351,6 +353,10 @@ AutomationRootNodeImpl.prototype = { ...@@ -351,6 +353,10 @@ AutomationRootNodeImpl.prototype = {
}, },
destroy: function() { destroy: function() {
if (this.hostTree)
this.hostTree.childTree = undefined;
this.hostTree = undefined;
this.dispatchEvent(schema.EventType.destroyed); this.dispatchEvent(schema.EventType.destroyed);
this.invalidate_(this.wrapper); this.invalidate_(this.wrapper);
}, },
...@@ -406,19 +412,10 @@ AutomationRootNodeImpl.prototype = { ...@@ -406,19 +412,10 @@ AutomationRootNodeImpl.prototype = {
nodeImpl[key] = AutomationAttributeDefaults[key]; nodeImpl[key] = AutomationAttributeDefaults[key];
} }
nodeImpl.childIds = []; nodeImpl.childIds = [];
nodeImpl.loaded = false;
nodeImpl.id = id; nodeImpl.id = id;
delete this.axNodeDataCache_[id]; delete this.axNodeDataCache_[id];
}, },
load: function(callback) {
// TODO(dtseng/aboxhall): Implement.
if (!this.loaded)
throw 'Unsupported state: root node is not loaded.';
setTimeout(callback, 0);
},
deleteOldChildren_: function(node, newChildIds) { deleteOldChildren_: function(node, newChildIds) {
// Create a set of child ids in |src| for fast lookup, and return false // Create a set of child ids in |src| for fast lookup, and return false
// if a duplicate is found; // if a duplicate is found;
...@@ -457,6 +454,7 @@ AutomationRootNodeImpl.prototype = { ...@@ -457,6 +454,7 @@ AutomationRootNodeImpl.prototype = {
createNewChildren_: function(node, newChildIds, updateState) { createNewChildren_: function(node, newChildIds, updateState) {
logging.CHECK(node); logging.CHECK(node);
var success = true; var success = true;
for (var i = 0; i < newChildIds.length; i++) { for (var i = 0; i < newChildIds.length; i++) {
var childId = newChildIds[i]; var childId = newChildIds[i];
var childNode = this.axNodeDataCache_[childId]; var childNode = this.axNodeDataCache_[childId];
...@@ -495,6 +493,23 @@ AutomationRootNodeImpl.prototype = { ...@@ -495,6 +493,23 @@ AutomationRootNodeImpl.prototype = {
setData_: function(node, nodeData) { setData_: function(node, nodeData) {
var nodeImpl = privates(node).impl; var nodeImpl = privates(node).impl;
// TODO(dtseng): Make into set listing all hosting node roles.
if (nodeData.role == schema.RoleType.webView) {
if (nodeImpl.pendingChildFrame === undefined)
nodeImpl.pendingChildFrame = true;
if (nodeImpl.pendingChildFrame) {
nodeImpl.childTreeID = nodeData.intAttributes.childTreeId;
automationInternal.enableFrame(nodeImpl.childTreeID);
automationUtil.storeTreeCallback(nodeImpl.childTreeID, function(root) {
nodeImpl.pendingChildFrame = false;
nodeImpl.childTree = root;
privates(root).impl.hostTree = node;
nodeImpl.dispatchEvent(schema.EventType.childrenChanged);
});
}
}
for (var key in AutomationAttributeDefaults) { for (var key in AutomationAttributeDefaults) {
if (key in nodeData) if (key in nodeData)
nodeImpl[key] = nodeData[key]; nodeImpl[key] = nodeData[key];
...@@ -599,19 +614,19 @@ var AutomationNode = utils.expose('AutomationNode', ...@@ -599,19 +614,19 @@ var AutomationNode = utils.expose('AutomationNode',
'makeVisible', 'makeVisible',
'setSelection', 'setSelection',
'addEventListener', 'addEventListener',
'removeEventListener'], 'removeEventListener',
'toJSON'],
readonly: ['isRootNode', readonly: ['isRootNode',
'role', 'role',
'state', 'state',
'location', 'location',
'attributes', 'attributes',
'indexInParent',
'root'] }); 'root'] });
var AutomationRootNode = utils.expose('AutomationRootNode', var AutomationRootNode = utils.expose('AutomationRootNode',
AutomationRootNodeImpl, AutomationRootNodeImpl,
{ superclass: AutomationNode, { superclass: AutomationNode });
functions: ['load'],
readonly: ['loaded'] });
exports.AutomationNode = AutomationNode; exports.AutomationNode = AutomationNode;
exports.AutomationRootNode = AutomationRootNode; exports.AutomationRootNode = AutomationRootNode;
...@@ -15,21 +15,33 @@ var lastError = require('lastError'); ...@@ -15,21 +15,33 @@ var lastError = require('lastError');
var logging = requireNative('logging'); var logging = requireNative('logging');
var schema = requireNative('automationInternal').GetSchemaAdditions(); var schema = requireNative('automationInternal').GetSchemaAdditions();
/**
* A namespace to export utility functions to other files in automation.
*/
window.automationUtil = function() {};
// TODO(aboxhall): Look into using WeakMap // TODO(aboxhall): Look into using WeakMap
var idToAutomationRootNode = {}; var idToAutomationRootNode = {};
var idToCallback = {}; var idToCallback = {};
// TODO(dtseng): Move out to automation/automation_util.js or as a static member var DESKTOP_TREE_ID = 0;
// of AutomationRootNode to keep this file clean.
/*
* Creates an id associated with a particular AutomationRootNode based upon a
* renderer/renderer host pair's process and routing id.
*/
var createAutomationRootNodeID = function(pid, rid) {
return pid + '_' + rid;
};
var DESKTOP_TREE_ID = createAutomationRootNodeID(0, 0); automationUtil.storeTreeCallback = function(id, callback) {
if (!callback)
return;
var targetTree = idToAutomationRootNode[id];
if (!targetTree) {
// If we haven't cached the tree, hold the callback until the tree is
// populated by the initial onAccessibilityEvent call.
if (id in idToCallback)
idToCallback[id].push(callback);
else
idToCallback[id] = [callback];
} else {
callback(targetTree);
}
};
automation.registerCustomHook(function(bindingsAPI) { automation.registerCustomHook(function(bindingsAPI) {
var apiFunctions = bindingsAPI.apiFunctions; var apiFunctions = bindingsAPI.apiFunctions;
...@@ -37,35 +49,25 @@ automation.registerCustomHook(function(bindingsAPI) { ...@@ -37,35 +49,25 @@ automation.registerCustomHook(function(bindingsAPI) {
// TODO(aboxhall, dtseng): Make this return the speced AutomationRootNode obj. // TODO(aboxhall, dtseng): Make this return the speced AutomationRootNode obj.
apiFunctions.setHandleRequest('getTree', function getTree(tabId, callback) { apiFunctions.setHandleRequest('getTree', function getTree(tabId, callback) {
// enableTab() ensures the renderer for the active or specified tab has // enableTab() ensures the renderer for the active or specified tab has
// accessibility enabled, and fetches its process and routing ids to use as // accessibility enabled, and fetches its ax tree id to use as
// a key in the idToAutomationRootNode map. The callback to enableTab is is // a key in the idToAutomationRootNode map. The callback to
// bound to the callback passed in to getTree(), so that once the tree is // enableTab is bound to the callback passed in to getTree(), so that once
// available (either due to having been cached earlier, or after an // the tree is available (either due to having been cached earlier, or after
// accessibility event occurs which causes the tree to be populated), the // an accessibility event occurs which causes the tree to be populated), the
// callback can be called. // callback can be called.
automationInternal.enableTab(tabId, function onEnable(pid, rid) { automationInternal.enableTab(tabId, function onEnable(id) {
if (lastError.hasError(chrome)) { if (lastError.hasError(chrome)) {
callback(); callback();
return; return;
} }
var id = createAutomationRootNodeID(pid, rid); automationUtil.storeTreeCallback(id, callback);
var targetTree = idToAutomationRootNode[id];
if (!targetTree) {
// If we haven't cached the tree, hold the callback until the tree is
// populated by the initial onAccessibilityEvent call.
if (id in idToCallback)
idToCallback[id].push(callback);
else
idToCallback[id] = [callback];
} else {
callback(targetTree);
}
}); });
}); });
var desktopTree = null; var desktopTree = null;
apiFunctions.setHandleRequest('getDesktop', function(callback) { apiFunctions.setHandleRequest('getDesktop', function(callback) {
desktopTree = idToAutomationRootNode[DESKTOP_TREE_ID]; desktopTree =
idToAutomationRootNode[DESKTOP_TREE_ID];
if (!desktopTree) { if (!desktopTree) {
if (DESKTOP_TREE_ID in idToCallback) if (DESKTOP_TREE_ID in idToCallback)
idToCallback[DESKTOP_TREE_ID].push(callback); idToCallback[DESKTOP_TREE_ID].push(callback);
...@@ -76,7 +78,8 @@ automation.registerCustomHook(function(bindingsAPI) { ...@@ -76,7 +78,8 @@ automation.registerCustomHook(function(bindingsAPI) {
// scope. // scope.
automationInternal.enableDesktop(function() { automationInternal.enableDesktop(function() {
if (lastError.hasError(chrome)) { if (lastError.hasError(chrome)) {
delete idToAutomationRootNode[DESKTOP_TREE_ID]; delete idToAutomationRootNode[
DESKTOP_TREE_ID];
callback(); callback();
return; return;
} }
...@@ -91,15 +94,13 @@ automation.registerCustomHook(function(bindingsAPI) { ...@@ -91,15 +94,13 @@ automation.registerCustomHook(function(bindingsAPI) {
// essentially a proxy for the AccessibilityHostMsg_Events IPC from the // essentially a proxy for the AccessibilityHostMsg_Events IPC from the
// renderer. // renderer.
automationInternal.onAccessibilityEvent.addListener(function(data) { automationInternal.onAccessibilityEvent.addListener(function(data) {
var pid = data.processID; var id = data.treeID;
var rid = data.routingID;
var id = createAutomationRootNodeID(pid, rid);
var targetTree = idToAutomationRootNode[id]; var targetTree = idToAutomationRootNode[id];
if (!targetTree) { if (!targetTree) {
// If this is the first time we've gotten data for this tree, it will // If this is the first time we've gotten data for this tree, it will
// contain all of the tree's data, so create a new tree which will be // contain all of the tree's data, so create a new tree which will be
// bootstrapped from |data|. // bootstrapped from |data|.
targetTree = new AutomationRootNode(pid, rid); targetTree = new AutomationRootNode(id);
idToAutomationRootNode[id] = targetTree; idToAutomationRootNode[id] = targetTree;
} }
if (!privates(targetTree).impl.onAccessibilityEvent(data)) if (!privates(targetTree).impl.onAccessibilityEvent(data))
...@@ -129,8 +130,7 @@ automationInternal.onAccessibilityEvent.addListener(function(data) { ...@@ -129,8 +130,7 @@ automationInternal.onAccessibilityEvent.addListener(function(data) {
delete idToCallback[id]; delete idToCallback[id];
}); });
automationInternal.onAccessibilityTreeDestroyed.addListener(function(pid, rid) { automationInternal.onAccessibilityTreeDestroyed.addListener(function(id) {
var id = createAutomationRootNodeID(pid, rid);
var targetTree = idToAutomationRootNode[id]; var targetTree = idToAutomationRootNode[id];
if (targetTree) { if (targetTree) {
privates(targetTree).impl.destroy(); privates(targetTree).impl.destroy();
......
...@@ -25,9 +25,31 @@ function findAutomationNode(root, condition) { ...@@ -25,9 +25,31 @@ function findAutomationNode(root, condition) {
return null; return null;
} }
function setupAndRunTests(allTests) { function runWithDocument(docString, callback) {
chrome.automation.getDesktop(function(rootNodeArg) { var url = 'data:text/html,<!doctype html>' + docString;
rootNode = rootNodeArg; var createParams = {
chrome.test.runTests(allTests); active: true,
url: url
};
chrome.tabs.create(createParams, function(tab) {
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo) {
if (tabId == tab.id && changeInfo.status == 'complete') {
callback();
}
});
}); });
} }
function setupAndRunTests(allTests, opt_docString) {
function runTestInternal() {
chrome.automation.getDesktop(function(rootNodeArg) {
rootNode = rootNodeArg;
chrome.test.runTests(allTests);
});
}
if (opt_docString)
runWithDocument(opt_docString, runTestInternal);
else
runTestInternal();
}
<!--
* Copyright 2014 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.
-->
<script src="common.js"></script>
<script src="load_tabs.js"></script>
// Copyright 2014 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.
function getAllWebViews() {
function findAllWebViews(node, nodes) {
if (node.role == chrome.automation.RoleType.webView)
nodes.push(node);
var children = node.children();
for (var i = 0; i < children.length; i++) {
var child = findAllWebViews(children[i], nodes);
}
}
var webViews = [];
findAllWebViews(rootNode, webViews);
return webViews;
};
var allTests = [
function testLoadTabs() {
var webViews = getAllWebViews();
assertEq(2, webViews.length);
// Test spins up more quickly than the load; listen for the childrenChanged
// event.
webViews[1].addEventListener(chrome.automation.EventType.childrenChanged,
function(evt) {
var subroot = webViews[1].firstChild();
assertEq(evt.target, subroot.parent());
var button = subroot.firstChild().firstChild();
assertEq(chrome.automation.RoleType.button, button.role);
var input = subroot.firstChild().lastChild().previousSibling();
assertEq(chrome.automation.RoleType.textField, input.role);
chrome.test.succeed();
}, true);
},
function testSubevents() {
var button = null;
var webViews = getAllWebViews();
var subroot = webViews[1].firstChild();
rootNode.addEventListener(chrome.automation.EventType.focus,
function(evt) {
assertEq(button, evt.target);
chrome.test.succeed();
},
false);
button = subroot.firstChild().firstChild();
button.focus();
}
];
setupAndRunTests(allTests,
'<button>alpha</button><input type="text">hello</input>');
...@@ -6,6 +6,9 @@ chrome.test.runWithModuleSystem(function(moduleSystem) { ...@@ -6,6 +6,9 @@ chrome.test.runWithModuleSystem(function(moduleSystem) {
window.AutomationRootNode = window.AutomationRootNode =
moduleSystem.require('automationNode').AutomationRootNode; moduleSystem.require('automationNode').AutomationRootNode;
window.privates = moduleSystem.privates; window.privates = moduleSystem.privates;
// Unused.
window.automationUtil = function() {};
window.automationUtil.storeTreeCallback = function() {};
}); });
var assertEq = chrome.test.assertEq; var assertEq = chrome.test.assertEq;
......
...@@ -189,6 +189,7 @@ ...@@ -189,6 +189,7 @@
tooltip, tooltip,
value_indicator, value_indicator,
web_area, web_area,
web_view,
window window
}; };
...@@ -293,7 +294,13 @@ ...@@ -293,7 +294,13 @@
color_value_blue, color_value_blue,
// Inline text attributes. // Inline text attributes.
text_direction text_direction,
// Uniquely identifies an AXTree.
tree_id,
// Identifies a child tree which this node hosts.
child_tree_id
}; };
[cpp_enum_prefix_override="ax_attr"] enum AXFloatAttribute { [cpp_enum_prefix_override="ax_attr"] enum AXFloatAttribute {
......
...@@ -203,6 +203,12 @@ std::string AXNodeData::ToString() const { ...@@ -203,6 +203,12 @@ std::string AXNodeData::ToString() const {
case AX_ATTR_COLOR_VALUE_BLUE: case AX_ATTR_COLOR_VALUE_BLUE:
result += " color_value_blue=" + value; result += " color_value_blue=" + value;
break; break;
case AX_ATTR_TREE_ID:
result += " tree_id=" + value;
break;
case AX_ATTR_CHILD_TREE_ID:
result += " child_tree_id=" + value;
break;
case AX_ATTR_TEXT_DIRECTION: case AX_ATTR_TEXT_DIRECTION:
switch (int_attributes[i].second) { switch (int_attributes[i].second) {
case AX_TEXT_DIRECTION_LR: case AX_TEXT_DIRECTION_LR:
......
...@@ -42,6 +42,7 @@ void AXViewObjWrapper::GetChildren( ...@@ -42,6 +42,7 @@ void AXViewObjWrapper::GetChildren(
void AXViewObjWrapper::Serialize(ui::AXNodeData* out_node_data) { void AXViewObjWrapper::Serialize(ui::AXNodeData* out_node_data) {
ui::AXViewState view_data; ui::AXViewState view_data;
view_->GetAccessibleState(&view_data); view_->GetAccessibleState(&view_data);
out_node_data->id = GetID(); out_node_data->id = GetID();
out_node_data->role = view_data.role; out_node_data->role = view_data.role;
...@@ -58,10 +59,13 @@ void AXViewObjWrapper::Serialize(ui::AXNodeData* out_node_data) { ...@@ -58,10 +59,13 @@ void AXViewObjWrapper::Serialize(ui::AXNodeData* out_node_data) {
out_node_data->AddStringAttribute( out_node_data->AddStringAttribute(
ui::AX_ATTR_VALUE, base::UTF16ToUTF8(view_data.value)); ui::AX_ATTR_VALUE, base::UTF16ToUTF8(view_data.value));
out_node_data->AddIntAttribute(ui::AX_ATTR_TEXT_SEL_START, if (view_data.selection_start > -1 && view_data.selection_end > -1) {
view_data.selection_start); out_node_data->AddIntAttribute(ui::AX_ATTR_TEXT_SEL_START,
out_node_data->AddIntAttribute(ui::AX_ATTR_TEXT_SEL_END, view_data.selection_start);
view_data.selection_end);
out_node_data->AddIntAttribute(ui::AX_ATTR_TEXT_SEL_END,
view_data.selection_end);
}
} }
int32 AXViewObjWrapper::GetID() { int32 AXViewObjWrapper::GetID() {
......
...@@ -16,6 +16,8 @@ class AXViewObjWrapper : public AXAuraObjWrapper { ...@@ -16,6 +16,8 @@ class AXViewObjWrapper : public AXAuraObjWrapper {
explicit AXViewObjWrapper(View* view); explicit AXViewObjWrapper(View* view);
~AXViewObjWrapper() override; ~AXViewObjWrapper() override;
View* view() { return view_; }
// AXAuraObjWrapper overrides. // AXAuraObjWrapper overrides.
AXAuraObjWrapper* GetParent() override; AXAuraObjWrapper* GetParent() override;
void GetChildren(std::vector<AXAuraObjWrapper*>* out_children) override; void GetChildren(std::vector<AXAuraObjWrapper*>* out_children) override;
......
...@@ -1331,6 +1331,8 @@ int32 NativeViewAccessibilityWin::MSAARole(ui::AXRole role) { ...@@ -1331,6 +1331,8 @@ int32 NativeViewAccessibilityWin::MSAARole(ui::AXRole role) {
return ROLE_SYSTEM_TITLEBAR; return ROLE_SYSTEM_TITLEBAR;
case ui::AX_ROLE_TOOLBAR: case ui::AX_ROLE_TOOLBAR:
return ROLE_SYSTEM_TOOLBAR; return ROLE_SYSTEM_TOOLBAR;
case ui::AX_ROLE_WEB_VIEW:
return ROLE_SYSTEM_GROUPING;
case ui::AX_ROLE_WINDOW: case ui::AX_ROLE_WINDOW:
return ROLE_SYSTEM_WINDOW; return ROLE_SYSTEM_WINDOW;
case ui::AX_ROLE_CLIENT: case ui::AX_ROLE_CLIENT:
......
...@@ -210,7 +210,7 @@ void WebView::AboutToRequestFocusFromTabTraversal(bool reverse) { ...@@ -210,7 +210,7 @@ void WebView::AboutToRequestFocusFromTabTraversal(bool reverse) {
} }
void WebView::GetAccessibleState(ui::AXViewState* state) { void WebView::GetAccessibleState(ui::AXViewState* state) {
state->role = ui::AX_ROLE_GROUP; state->role = ui::AX_ROLE_WEB_VIEW;
} }
gfx::NativeViewAccessible WebView::GetNativeViewAccessible() { gfx::NativeViewAccessible WebView::GetNativeViewAccessible() {
......
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