Commit aeecb377 authored by dtseng@chromium.org's avatar dtseng@chromium.org

Support IAccessibleHypertext.

- This modifies the way we build the browser accessibility tree in CreateAccessibilityTree. We still build the parent child links while recursing depth first through the WebAccessibility structure, but this patch changes the initialization of a node to occur *after* the children have been fully populated into the subtree of any given node.
- implements IAccessibleHypertext on a BrowserAccessibilityWin. Adds:
-- a string for the hypertext which contains the text of the static text children concatinated together along with the embedded characters for the non-text children.
-- map from the character offset within the hypertext to a hyperlink index.
-- a collection of children that are hyperlinks (basically nodes that are not static texts).
- adds no-op implementations of IAccessibleHyperlink and IAccessibleAction as required by the usage and interface inheritance of IAccessibleHyperlink.

BUG=99629

TEST=manually tested with NVDA.

Review URL: http://codereview.chromium.org/8416034

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@108211 0039d316-1c4b-4281-b951-d872f2087c98
parent 053ec8f5
......@@ -368,3 +368,170 @@ TEST_F(BrowserAccessibilityTest, TestTextBoundaries) {
delete manager;
ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_);
}
TEST_F(BrowserAccessibilityTest, TestSimpleHypertext) {
WebAccessibility text1;
text1.id = 11;
text1.role = WebAccessibility::ROLE_STATIC_TEXT;
text1.state = 0;
text1.name = L"One two three.";
WebAccessibility text2;
text2.id = 12;
text2.role = WebAccessibility::ROLE_STATIC_TEXT;
text2.state = 0;
text2.name = L" Four five six.";
WebAccessibility root;
root.id = 1;
root.role = WebAccessibility::ROLE_DOCUMENT;
root.state = 0;
root.children.push_back(text1);
root.children.push_back(text2);
CountedBrowserAccessibility::global_obj_count_ = 0;
BrowserAccessibilityManager* manager = BrowserAccessibilityManager::Create(
GetDesktopWindow(), root, NULL,
new CountedBrowserAccessibilityFactory());
ASSERT_EQ(3, CountedBrowserAccessibility::global_obj_count_);
BrowserAccessibilityWin* root_obj =
manager->GetRoot()->toBrowserAccessibilityWin();
BSTR text;
long text_len;
ASSERT_EQ(S_OK, root_obj->get_nCharacters(&text_len));
ASSERT_EQ(S_OK, root_obj->get_text(0, text_len, &text));
EXPECT_EQ(text, text1.name + text2.name);
SysFreeString(text);
long hyperlink_count;
ASSERT_EQ(S_OK, root_obj->get_nHyperlinks(&hyperlink_count));
EXPECT_EQ(0, hyperlink_count);
base::win::ScopedComPtr<IAccessibleHyperlink> hyperlink;
EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(-1, hyperlink.Receive()));
EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(0, hyperlink.Receive()));
EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(28, hyperlink.Receive()));
EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(29, hyperlink.Receive()));
long hyperlink_index;
EXPECT_EQ(E_FAIL, root_obj->get_hyperlinkIndex(0, &hyperlink_index));
EXPECT_EQ(-1, hyperlink_index);
EXPECT_EQ(E_FAIL, root_obj->get_hyperlinkIndex(28, &hyperlink_index));
EXPECT_EQ(-1, hyperlink_index);
EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlinkIndex(-1, &hyperlink_index));
EXPECT_EQ(-1, hyperlink_index);
EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlinkIndex(29, &hyperlink_index));
EXPECT_EQ(-1, hyperlink_index);
// Delete the manager and test that all BrowserAccessibility instances are
// deleted.
delete manager;
ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_);
}
TEST_F(BrowserAccessibilityTest, TestComplexHypertext) {
WebAccessibility text1;
text1.id = 11;
text1.role = WebAccessibility::ROLE_STATIC_TEXT;
text1.state = 0;
text1.name = L"One two three.";
WebAccessibility text2;
text2.id = 12;
text2.role = WebAccessibility::ROLE_STATIC_TEXT;
text2.state = 0;
text2.name = L" Four five six.";
WebAccessibility button1, button1_text;
button1.id = 13;
button1_text.id = 15;
button1_text.name = L"red";
button1.role = WebAccessibility::ROLE_BUTTON;
button1_text.role = WebAccessibility::ROLE_STATIC_TEXT;
button1.state = 0;
button1.children.push_back(button1_text);
WebAccessibility link1, link1_text;
link1.id = 14;
link1_text.id = 16;
link1_text.name = L"blue";
link1.role = WebAccessibility::ROLE_LINK;
link1_text.role = WebAccessibility::ROLE_STATIC_TEXT;
link1.state = 0;
link1.children.push_back(link1_text);
WebAccessibility root;
root.id = 1;
root.role = WebAccessibility::ROLE_DOCUMENT;
root.state = 0;
root.children.push_back(text1);
root.children.push_back(button1);
root.children.push_back(text2);
root.children.push_back(link1);
CountedBrowserAccessibility::global_obj_count_ = 0;
BrowserAccessibilityManager* manager = BrowserAccessibilityManager::Create(
GetDesktopWindow(), root, NULL,
new CountedBrowserAccessibilityFactory());
ASSERT_EQ(7, CountedBrowserAccessibility::global_obj_count_);
BrowserAccessibilityWin* root_obj =
manager->GetRoot()->toBrowserAccessibilityWin();
BSTR text;
long text_len;
ASSERT_EQ(S_OK, root_obj->get_nCharacters(&text_len));
ASSERT_EQ(S_OK, root_obj->get_text(0, text_len, &text));
const string16 embed = BrowserAccessibilityWin::kEmbeddedCharacter;
EXPECT_EQ(text, text1.name + embed + text2.name + embed);
SysFreeString(text);
long hyperlink_count;
ASSERT_EQ(S_OK, root_obj->get_nHyperlinks(&hyperlink_count));
EXPECT_EQ(2, hyperlink_count);
base::win::ScopedComPtr<IAccessibleHyperlink> hyperlink;
base::win::ScopedComPtr<IAccessibleText> hypertext;
EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(-1, hyperlink.Receive()));
EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(2, hyperlink.Receive()));
EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(28, hyperlink.Receive()));
EXPECT_EQ(S_OK, root_obj->get_hyperlink(0, hyperlink.Receive()));
EXPECT_EQ(S_OK,
hyperlink.QueryInterface<IAccessibleText>(hypertext.Receive()));
EXPECT_EQ(S_OK, hypertext->get_text(0, 3, &text));
EXPECT_EQ(text, string16(L"red"));
SysFreeString(text);
hyperlink.Release();
hypertext.Release();
EXPECT_EQ(S_OK, root_obj->get_hyperlink(1, hyperlink.Receive()));
EXPECT_EQ(S_OK,
hyperlink.QueryInterface<IAccessibleText>(hypertext.Receive()));
EXPECT_EQ(S_OK, hypertext->get_text(0, 4, &text));
EXPECT_EQ(text, string16(L"blue"));
SysFreeString(text);
hyperlink.Release();
hypertext.Release();
long hyperlink_index;
EXPECT_EQ(E_FAIL, root_obj->get_hyperlinkIndex(0, &hyperlink_index));
EXPECT_EQ(-1, hyperlink_index);
EXPECT_EQ(E_FAIL, root_obj->get_hyperlinkIndex(28, &hyperlink_index));
EXPECT_EQ(-1, hyperlink_index);
EXPECT_EQ(S_OK, root_obj->get_hyperlinkIndex(14, &hyperlink_index));
EXPECT_EQ(0, hyperlink_index);
EXPECT_EQ(S_OK, root_obj->get_hyperlinkIndex(30, &hyperlink_index));
EXPECT_EQ(1, hyperlink_index);
// Delete the manager and test that all BrowserAccessibility instances are
// deleted.
delete manager;
ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_);
}
......@@ -321,6 +321,12 @@ BrowserAccessibility* BrowserAccessibilityManager::CreateAccessibilityTree(
children_can_send_show_events = false;
}
for (int i = 0; i < static_cast<int>(src.children.size()); ++i) {
BrowserAccessibility* child = CreateAccessibilityTree(
instance, src.children[i], i, children_can_send_show_events);
instance->AddChild(child);
}
instance->Initialize(this, parent, child_id, index_in_parent, src);
child_id_map_[child_id] = instance;
renderer_id_to_child_id_map_[src.id] = child_id;
......@@ -330,11 +336,6 @@ BrowserAccessibility* BrowserAccessibilityManager::CreateAccessibilityTree(
if ((src.state >> WebAccessibility::STATE_FOCUSED) & 1)
SetFocus(instance, false);
for (int i = 0; i < static_cast<int>(src.children.size()); ++i) {
BrowserAccessibility* child = CreateAccessibilityTree(
instance, src.children[i], i, children_can_send_show_events);
instance->AddChild(child);
}
// Note: the purpose of send_show_events and children_can_send_show_events
// is so that we send a single OBJECT_SHOW event for the root of a subtree
......
......@@ -23,6 +23,8 @@ const GUID GUID_ISimpleDOM = {
0x0c539790, 0x12e4, 0x11cf,
0xb6, 0x61, 0x00, 0xaa, 0x00, 0x4c, 0xd6, 0xd8};
const char16 BrowserAccessibilityWin::kEmbeddedCharacter[] = L"\xfffc";
//
// BrowserAccessibilityRelation
//
......@@ -1600,13 +1602,7 @@ STDMETHODIMP BrowserAccessibilityWin::get_nCharacters(LONG* n_characters) {
if (!n_characters)
return E_INVALIDARG;
if (role_ == WebAccessibility::ROLE_TEXT_FIELD ||
role_ == WebAccessibility::ROLE_TEXTAREA) {
*n_characters = value_.length();
} else {
*n_characters = name_.length();
}
*n_characters = TextForIAccessibleText().length();
return S_OK;
}
......@@ -1848,6 +1844,62 @@ STDMETHODIMP BrowserAccessibilityWin::get_offsetAtPoint(
return S_OK;
}
//
// IAccessibleHypertext methods.
//
STDMETHODIMP BrowserAccessibilityWin::get_nHyperlinks(long* hyperlink_count) {
if (!instance_active_)
return E_FAIL;
if (!hyperlink_count)
return E_INVALIDARG;
*hyperlink_count = hyperlink_offset_to_index_.size();
return S_OK;
}
STDMETHODIMP BrowserAccessibilityWin::get_hyperlink(
long index,
IAccessibleHyperlink** hyperlink) {
if (!instance_active_)
return E_FAIL;
if (!hyperlink ||
index < 0 ||
index >= static_cast<long>(hyperlinks_.size())) {
return E_INVALIDARG;
}
BrowserAccessibilityWin* child =
children_[hyperlinks_[index]]->toBrowserAccessibilityWin();
*hyperlink = static_cast<IAccessibleHyperlink*>(child->NewReference());
return S_OK;
}
STDMETHODIMP BrowserAccessibilityWin::get_hyperlinkIndex(
long char_index,
long* hyperlink_index) {
if (!instance_active_)
return E_FAIL;
if (!hyperlink_index)
return E_INVALIDARG;
*hyperlink_index = -1;
if (char_index < 0 || char_index >= static_cast<long>(hypertext_.size()))
return E_INVALIDARG;
std::map<int32, int32>::iterator it =
hyperlink_offset_to_index_.find(char_index);
if (it == hyperlink_offset_to_index_.end())
return E_FAIL;
*hyperlink_index = it->second;
return S_OK;
}
//
// IAccessibleValue methods.
//
......@@ -2242,6 +2294,9 @@ STDMETHODIMP BrowserAccessibilityWin::QueryService(
if (guidService == IID_IAccessible ||
guidService == IID_IAccessible2 ||
guidService == IID_IAccessibleAction ||
guidService == IID_IAccessibleHyperlink ||
guidService == IID_IAccessibleHypertext ||
guidService == IID_IAccessibleImage ||
guidService == IID_IAccessibleTable ||
guidService == IID_IAccessibleTable2 ||
......@@ -2268,12 +2323,7 @@ HRESULT WINAPI BrowserAccessibilityWin::InternalQueryInterface(
const _ATL_INTMAP_ENTRY* entries,
REFIID iid,
void** object) {
if (iid == IID_IAccessibleText) {
if (ia_role_ != ROLE_SYSTEM_LINK && ia_role_ != ROLE_SYSTEM_TEXT) {
*object = NULL;
return E_NOINTERFACE;
}
} else if (iid == IID_IAccessibleImage) {
if (iid == IID_IAccessibleImage) {
if (ia_role_ != ROLE_SYSTEM_GRAPHIC) {
*object = NULL;
return E_NOINTERFACE;
......@@ -2417,6 +2467,22 @@ void BrowserAccessibilityWin::Initialize() {
relation->AddTarget(title_elem_id);
relations_.push_back(relation);
}
// Construct the hypertext for this node.
hyperlink_offset_to_index_.clear();
hyperlinks_.clear();
hypertext_.clear();
for (unsigned int i = 0; i < children().size(); ++i) {
BrowserAccessibility* child = children()[i];
if (child->role() == WebAccessibility::ROLE_STATIC_TEXT) {
hypertext_ += child->name();
} else {
hyperlink_offset_to_index_[hypertext_.size()] = hyperlinks_.size();
hypertext_ += kEmbeddedCharacter;
hyperlinks_.push_back(i);
}
}
DCHECK_EQ(hyperlink_offset_to_index_.size(), hyperlinks_.size());
}
void BrowserAccessibilityWin::SendNodeUpdateEvents() {
......@@ -2516,8 +2582,10 @@ string16 BrowserAccessibilityWin::Escape(const string16& str) {
const string16& BrowserAccessibilityWin::TextForIAccessibleText() {
if (IsEditableText()) {
return value_;
} else {
} else if (role_ == WebAccessibility::ROLE_STATIC_TEXT) {
return name_;
} else {
return hypertext_;
}
}
......
......@@ -40,11 +40,12 @@ BrowserAccessibilityWin
public CComObjectRootEx<CComMultiThreadModel>,
public IDispatchImpl<IAccessible2, &IID_IAccessible2,
&LIBID_IAccessible2Lib>,
public IAccessibleHyperlink,
public IAccessibleHypertext,
public IAccessibleImage,
public IAccessibleTable,
public IAccessibleTable2,
public IAccessibleTableCell,
public IAccessibleText,
public IAccessibleValue,
public IServiceProvider,
public ISimpleDOMDocument,
......@@ -54,12 +55,14 @@ BrowserAccessibilityWin
BEGIN_COM_MAP(BrowserAccessibilityWin)
COM_INTERFACE_ENTRY2(IDispatch, IAccessible2)
COM_INTERFACE_ENTRY2(IAccessible, IAccessible2)
COM_INTERFACE_ENTRY2(IAccessibleText, IAccessibleHypertext)
COM_INTERFACE_ENTRY(IAccessible2)
COM_INTERFACE_ENTRY(IAccessibleHyperlink)
COM_INTERFACE_ENTRY(IAccessibleHypertext)
COM_INTERFACE_ENTRY(IAccessibleImage)
COM_INTERFACE_ENTRY(IAccessibleTable)
COM_INTERFACE_ENTRY(IAccessibleTable2)
COM_INTERFACE_ENTRY(IAccessibleTableCell)
COM_INTERFACE_ENTRY(IAccessibleText)
COM_INTERFACE_ENTRY(IAccessibleValue)
COM_INTERFACE_ENTRY(IServiceProvider)
COM_INTERFACE_ENTRY(ISimpleDOMDocument)
......@@ -67,6 +70,11 @@ BrowserAccessibilityWin
COM_INTERFACE_ENTRY(ISimpleDOMText)
END_COM_MAP()
// Represents a non-static text node in IAccessibleHypertext. This character
// is embedded in the response to IAccessibleText::get_text, indicating the
// position where a non-static text child object appears.
static const char16 kEmbeddedCharacter[];
CONTENT_EXPORT BrowserAccessibilityWin();
CONTENT_EXPORT virtual ~BrowserAccessibilityWin();
......@@ -477,6 +485,66 @@ BrowserAccessibilityWin
return E_NOTIMPL;
}
//
// IAccessibleHypertext methods.
//
CONTENT_EXPORT STDMETHODIMP get_nHyperlinks(long* hyperlink_count);
CONTENT_EXPORT STDMETHODIMP get_hyperlink(
long index,
IAccessibleHyperlink** hyperlink);
CONTENT_EXPORT STDMETHODIMP get_hyperlinkIndex(long char_index,
long* hyperlink_index);
// IAccessibleHyperlink not implemented.
CONTENT_EXPORT STDMETHODIMP get_anchor(long index, VARIANT* anchor) {
return E_NOTIMPL;
}
CONTENT_EXPORT STDMETHODIMP get_anchorTarget(
long index,
VARIANT* anchor_target) {
return E_NOTIMPL;
}
CONTENT_EXPORT STDMETHODIMP get_startIndex( long* index) {
return E_NOTIMPL;
}
CONTENT_EXPORT STDMETHODIMP get_endIndex( long* index) {
return E_NOTIMPL;
}
CONTENT_EXPORT STDMETHODIMP get_valid(boolean* valid) {
return E_NOTIMPL;
}
// IAccessibleAction not implemented.
CONTENT_EXPORT STDMETHODIMP nActions(long* n_actions) {
return E_NOTIMPL;
}
CONTENT_EXPORT STDMETHODIMP doAction(long action_index) {
return E_NOTIMPL;
}
CONTENT_EXPORT STDMETHODIMP get_description(
long action_index,
BSTR* description) {
return E_NOTIMPL;
}
CONTENT_EXPORT STDMETHODIMP get_keyBinding(
long action_index,
long n_max_bindings,
BSTR** key_bindings,
long* n_bindings) {
return E_NOTIMPL;
}
CONTENT_EXPORT STDMETHODIMP get_name(long action_index, BSTR* name) {
return E_NOTIMPL;
}
CONTENT_EXPORT STDMETHODIMP get_localizedName(
long action_index,
BSTR* localized_name) {
return E_NOTIMPL;
}
//
// IAccessibleValue methods.
//
......@@ -715,6 +783,17 @@ BrowserAccessibilityWin
// Relationships between this node and other nodes.
std::vector<BrowserAccessibilityRelation*> relations_;
// The text of this node including embedded hyperlink characters.
string16 hypertext_;
// Maps the |hypertext_| embedded character offset to an index in
// |hyperlinks_|.
std::map<int32, int32> hyperlink_offset_to_index_;
// Collection of non-static text child indicies, each of which corresponds to
// a hyperlink.
std::vector<int32> hyperlinks_;
// Give BrowserAccessibility::Create access to our constructor.
friend class BrowserAccessibility;
friend class BrowserAccessibilityRelation;
......
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