diff options
author | ctguil@chromium.org <ctguil@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-08-31 11:54:31 +0000 |
---|---|---|
committer | ctguil@chromium.org <ctguil@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-08-31 11:54:31 +0000 |
commit | 520cb7d74997ad71aa26cbb842dc7eec75be4f49 (patch) | |
tree | 01761cfd3fb461bd60d612138e77c8a1c4dfdb2b | |
parent | 4a188ca7f4aeb5b84187029bc2b61dcbee1797a8 (diff) | |
download | chromium_src-520cb7d74997ad71aa26cbb842dc7eec75be4f49.zip chromium_src-520cb7d74997ad71aa26cbb842dc7eec75be4f49.tar.gz chromium_src-520cb7d74997ad71aa26cbb842dc7eec75be4f49.tar.bz2 |
Update browser cache of accessibility tree on renderer sub-tree changes.
WebKit sister patch: http://trac.webkit.org/changeset/66305
BUG=13291
TEST=interactive_ui_tests --gtest_filter=AccessibilityWinBrowserTest.TestDynamicAccessibilityTree
TEST=unit_tests --gtest_filter=BrowserAccessibilityTest.TestChildrenChange
TEST=unit_tests --gtest_filter=BrowserAccessibilityTest.TestChildrenChangeNoLeaks
Review URL: http://codereview.chromium.org/3117036
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@57983 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/browser/accessibility_win_browsertest.cc | 115 | ||||
-rw-r--r-- | chrome/browser/browser_accessibility_manager_win.cc | 77 | ||||
-rw-r--r-- | chrome/browser/browser_accessibility_manager_win.h | 17 | ||||
-rw-r--r-- | chrome/browser/browser_accessibility_win.cc | 16 | ||||
-rw-r--r-- | chrome/browser/browser_accessibility_win.h | 10 | ||||
-rw-r--r-- | chrome/browser/browser_accessibility_win_unittest.cc | 157 | ||||
-rw-r--r-- | chrome/browser/renderer_host/render_view_host.cc | 14 | ||||
-rw-r--r-- | chrome/browser/renderer_host/render_view_host.h | 2 | ||||
-rw-r--r-- | chrome/browser/renderer_host/render_widget_host.cc | 4 | ||||
-rw-r--r-- | chrome/browser/renderer_host/render_widget_host.h | 3 | ||||
-rw-r--r-- | chrome/browser/renderer_host/render_widget_host_view.h | 5 | ||||
-rw-r--r-- | chrome/browser/renderer_host/render_widget_host_view_win.cc | 12 | ||||
-rw-r--r-- | chrome/browser/renderer_host/render_widget_host_view_win.h | 3 | ||||
-rw-r--r-- | chrome/common/notification_type.h | 6 | ||||
-rw-r--r-- | chrome/common/render_messages_internal.h | 28 | ||||
-rw-r--r-- | chrome/renderer/render_view.cc | 32 | ||||
-rw-r--r-- | chrome/renderer/render_view.h | 5 |
17 files changed, 455 insertions, 51 deletions
diff --git a/chrome/browser/accessibility_win_browsertest.cc b/chrome/browser/accessibility_win_browsertest.cc index 4ee4236..7b2d377 100644 --- a/chrome/browser/accessibility_win_browsertest.cc +++ b/chrome/browser/accessibility_win_browsertest.cc @@ -15,6 +15,10 @@ #include "chrome/test/in_process_browser_test.h" #include "chrome/test/ui_test_utils.h" +using std::auto_ptr; +using std::vector; +using std::wstring; + namespace { class AccessibilityWinBrowserTest : public InProcessBrowserTest { @@ -57,8 +61,14 @@ void AccessibilityWinBrowserTest::TearDownInProcessBrowserTestFixture() { class AccessibleChecker { public: - AccessibleChecker(std::wstring expected_name, int32 expected_role); - AccessibleChecker(std::wstring expected_name, std::wstring expected_role); + AccessibleChecker( + wstring expected_name, + int32 expected_role, + wstring expected_value); + AccessibleChecker( + wstring expected_name, + wstring expected_role, + wstring expected_value); // Append an AccessibleChecker that verifies accessibility information for // a child IAccessible. Order is important. @@ -69,20 +79,27 @@ class AccessibleChecker { // initialized with. void CheckAccessible(IAccessible* accessible); - typedef std::vector<AccessibleChecker*> AccessibleCheckerVector; + // Set the expected value for this AccessibleChecker. + void SetExpectedValue(wstring expected_value); private: void CheckAccessibleName(IAccessible* accessible); void CheckAccessibleRole(IAccessible* accessible); + void CheckAccessibleValue(IAccessible* accessible); void CheckAccessibleChildren(IAccessible* accessible); private: + typedef vector<AccessibleChecker*> AccessibleCheckerVector; + // Expected accessible name. Checked against IAccessible::get_accName. - std::wstring name_; + wstring name_; // Expected accessible role. Checked against IAccessible::get_accRole. CComVariant role_; + // Expected accessible value. Checked against IAccessible::get_accValue. + wstring value_; + // Expected accessible children. Checked using IAccessible::get_accChildCount // and ::AccessibleChildren. AccessibleCheckerVector children_; @@ -134,15 +151,17 @@ AccessibilityWinBrowserTest::GetRenderWidgetHostViewClientAccessible() { } AccessibleChecker::AccessibleChecker( - std::wstring expected_name, int32 expected_role) : + wstring expected_name, int32 expected_role, wstring expected_value) : name_(expected_name), - role_(expected_role) { + role_(expected_role), + value_(expected_value) { } AccessibleChecker::AccessibleChecker( - std::wstring expected_name, std::wstring expected_role) : + wstring expected_name, wstring expected_role, wstring expected_value) : name_(expected_name), - role_(expected_role.c_str()) { + role_(expected_role.c_str()), + value_(expected_value) { } void AccessibleChecker::AppendExpectedChild( @@ -153,9 +172,14 @@ void AccessibleChecker::AppendExpectedChild( void AccessibleChecker::CheckAccessible(IAccessible* accessible) { CheckAccessibleName(accessible); CheckAccessibleRole(accessible); + CheckAccessibleValue(accessible); CheckAccessibleChildren(accessible); } +void AccessibleChecker::SetExpectedValue(wstring expected_value) { + value_ = expected_value; +} + void AccessibleChecker::CheckAccessibleName(IAccessible* accessible) { CComBSTR name; HRESULT hr = @@ -167,9 +191,8 @@ void AccessibleChecker::CheckAccessibleName(IAccessible* accessible) { } else { // Test that the correct string was returned. EXPECT_EQ(hr, S_OK); - EXPECT_EQ(CompareString(LOCALE_NEUTRAL, 0, name, SysStringLen(name), - name_.c_str(), name_.length()), - CSTR_EQUAL); + EXPECT_STREQ(name_.c_str(), + wstring(name.m_str, SysStringLen(name)).c_str()); } } @@ -181,13 +204,24 @@ void AccessibleChecker::CheckAccessibleRole(IAccessible* accessible) { ASSERT_TRUE(role_ == var_role); } +void AccessibleChecker::CheckAccessibleValue(IAccessible* accessible) { + CComBSTR value; + HRESULT hr = + accessible->get_accValue(CreateI4Variant(CHILDID_SELF), &value); + EXPECT_EQ(hr, S_OK); + + // Test that the correct string was returned. + EXPECT_STREQ(value_.c_str(), + wstring(value.m_str, SysStringLen(value)).c_str()); +} + void AccessibleChecker::CheckAccessibleChildren(IAccessible* parent) { LONG child_count = 0; HRESULT hr = parent->get_accChildCount(&child_count); EXPECT_EQ(hr, S_OK); ASSERT_EQ(child_count, children_.size()); - std::auto_ptr<VARIANT> child_array(new VARIANT[child_count]); + auto_ptr<VARIANT> child_array(new VARIANT[child_count]); LONG obtained_count = 0; hr = AccessibleChildren(parent, 0, child_count, child_array.get(), &obtained_count); @@ -235,14 +269,14 @@ IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest, document_accessible = GetRenderWidgetHostViewClientAccessible(); ASSERT_NE(document_accessible.get(), reinterpret_cast<IAccessible*>(NULL)); - AccessibleChecker button_checker(L"push", ROLE_SYSTEM_PUSHBUTTON); - AccessibleChecker checkbox_checker(L"", ROLE_SYSTEM_CHECKBUTTON); + AccessibleChecker button_checker(L"push", ROLE_SYSTEM_PUSHBUTTON, L"push"); + AccessibleChecker checkbox_checker(L"", ROLE_SYSTEM_CHECKBUTTON, L""); - AccessibleChecker grouping_checker(L"", L"div"); + AccessibleChecker grouping_checker(L"", L"div", L""); grouping_checker.AppendExpectedChild(&button_checker); grouping_checker.AppendExpectedChild(&checkbox_checker); - AccessibleChecker document_checker(L"", ROLE_SYSTEM_DOCUMENT); + AccessibleChecker document_checker(L"", ROLE_SYSTEM_DOCUMENT, L""); document_checker.AppendExpectedChild(&grouping_checker); // Check the accessible tree of the renderer. @@ -261,13 +295,52 @@ IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest, // Verify that the IAccessible reference still points to a valid object and // that calls to its methods fail since the tree is no longer valid after // the page navagation. - // Todo(ctguil): Currently this is giving a false positive because E_FAIL is - // returned when BrowserAccessibilityManager::RequestAccessibilityInfo fails - // since the previous render view host connection is lost. Verify that - // instances are actually marked as invalid once the browse side cache is - // checked in. CComBSTR name; hr = document_accessible->get_accName(CreateI4Variant(CHILDID_SELF), &name); ASSERT_EQ(E_FAIL, hr); } + +IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest, + TestDynamicAccessibilityTree) { + // By requesting an accessible chrome will believe a screen reader has been + // detected. Request and wait for the accessibility tree to be updated. + GURL tree_url( + "data:text/html,<html><body><div onclick=\"this.innerHTML='<b>new text" + "</b>';\"><b>old text</b></div></body></html>"); + browser()->OpenURL(tree_url, GURL(), CURRENT_TAB, PageTransition::TYPED); + ScopedComPtr<IAccessible> document_accessible( + GetRenderWidgetHostViewClientAccessible()); + ui_test_utils::WaitForNotification( + NotificationType::RENDER_VIEW_HOST_ACCESSIBILITY_TREE_UPDATED); + + AccessibleChecker text_checker(L"", ROLE_SYSTEM_TEXT, L"old text"); + AccessibleChecker checkbox_checker(L"", ROLE_SYSTEM_CHECKBUTTON, L""); + + AccessibleChecker div_checker(L"", L"div", L""); + div_checker.AppendExpectedChild(&text_checker); + + AccessibleChecker document_checker(L"", ROLE_SYSTEM_DOCUMENT, L""); + document_checker.AppendExpectedChild(&div_checker); + + // Check the accessible tree of the browser. + document_accessible = GetRenderWidgetHostViewClientAccessible(); + ASSERT_NE(document_accessible.get(), reinterpret_cast<IAccessible*>(NULL)); + document_checker.CheckAccessible(document_accessible); + + // Perform the default action on the div which executes the script that + // updates text node within the div. + CComPtr<IDispatch> div_dispatch; + HRESULT hr = document_accessible->get_accChild(CreateI4Variant(1), + &div_dispatch); + EXPECT_EQ(hr, S_OK); + CComQIPtr<IAccessible> div_accessible(div_dispatch); + hr = div_accessible->accDoDefaultAction(CreateI4Variant(CHILDID_SELF)); + EXPECT_EQ(hr, S_OK); + ui_test_utils::WaitForNotification( + NotificationType::RENDER_VIEW_HOST_ACCESSIBILITY_TREE_UPDATED); + + // Check that the accessibility tree of the browser has been updated. + text_checker.SetExpectedValue(L"new text"); + document_checker.CheckAccessible(document_accessible); +} } // namespace. diff --git a/chrome/browser/browser_accessibility_manager_win.cc b/chrome/browser/browser_accessibility_manager_win.cc index c1ce8a7..d17a0bd 100644 --- a/chrome/browser/browser_accessibility_manager_win.cc +++ b/chrome/browser/browser_accessibility_manager_win.cc @@ -39,7 +39,7 @@ BrowserAccessibilityManager::BrowserAccessibilityManager( parent_hwnd_, OBJID_WINDOW, IID_IAccessible, reinterpret_cast<void **>(&window_iaccessible_)); DCHECK(SUCCEEDED(hr)); - root_ = CreateAccessibilityTree(NULL, src, 0); + root_ = CreateAccessibilityTree(NULL, GetNextChildID(), src, 0); if (!focus_) focus_ = root_; } @@ -57,6 +57,10 @@ BrowserAccessibility* BrowserAccessibilityManager::GetRoot() { return root_; } +void BrowserAccessibilityManager::Remove(LONG child_id) { + child_id_map_.erase(child_id); +} + BrowserAccessibility* BrowserAccessibilityManager::GetFromChildID( LONG child_id) { base::hash_map<LONG, BrowserAccessibility*>::iterator iter = @@ -117,24 +121,79 @@ void BrowserAccessibilityManager::OnAccessibilityObjectStateChange( return; LONG child_id = iter->second; - ::NotifyWinEvent(EVENT_OBJECT_FOCUS, parent_hwnd_, OBJID_CLIENT, child_id); + ::NotifyWinEvent( + EVENT_OBJECT_STATECHANGE, + parent_hwnd_, + OBJID_CLIENT, + child_id); } -BrowserAccessibility* BrowserAccessibilityManager::CreateAccessibilityTree( - BrowserAccessibility* parent, - const webkit_glue::WebAccessibility& src, - int index_in_parent) { - BrowserAccessibility* instance = factory_->Create(); +void BrowserAccessibilityManager::OnAccessibilityObjectChildrenChange( + const std::vector<webkit_glue::WebAccessibility>& acc_changes) { + if (delegate_) + delegate_->AccessibilityObjectChildrenChangeAck(); + + // For each accessibility object child change. + for (unsigned int index = 0; index < acc_changes.size(); index++) { + const webkit_glue::WebAccessibility& acc_obj = acc_changes[index]; + base::hash_map<int, LONG>::iterator iter = + renderer_id_to_child_id_map_.find(acc_obj.id); + if (iter == renderer_id_to_child_id_map_.end()) + continue; + + LONG child_id = iter->second; + BrowserAccessibility* old_browser_acc = GetFromChildID(child_id); + + BrowserAccessibility* new_browser_acc = CreateAccessibilityTree( + old_browser_acc->GetParent(), + child_id, + acc_obj, + old_browser_acc->index_in_parent()); + + if (focus_->IsDescendantOf(old_browser_acc)) + focus_ = new_browser_acc; + + if (old_browser_acc->GetParent()) { + old_browser_acc->GetParent()->ReplaceChild( + old_browser_acc, + new_browser_acc); + } else { + DCHECK_EQ(old_browser_acc, root_); + root_ = new_browser_acc; + } + old_browser_acc->InactivateTree(); + old_browser_acc->Release(); + child_id_map_[child_id] = new_browser_acc; + + if (root_ != new_browser_acc) { + NotifyWinEvent( + EVENT_OBJECT_REORDER, parent_hwnd_, OBJID_CLIENT, child_id); + } else { + NotifyWinEvent( + EVENT_OBJECT_REORDER, parent_hwnd_, OBJID_CLIENT, CHILDID_SELF); + } + } +} +LONG BrowserAccessibilityManager::GetNextChildID() { // Get the next child ID, and wrap around when we get near the end // of a 32-bit integer range. It's okay to wrap around; we just want // to avoid it as long as possible because clients may cache the ID of // an object for a while to determine if they've seen it before. - LONG child_id = next_child_id_; next_child_id_--; if (next_child_id_ == -2000000000) next_child_id_ = -1; + return next_child_id_; +} + +BrowserAccessibility* BrowserAccessibilityManager::CreateAccessibilityTree( + BrowserAccessibility* parent, + int child_id, + const webkit_glue::WebAccessibility& src, + int index_in_parent) { + BrowserAccessibility* instance = factory_->Create(); + 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; @@ -142,7 +201,7 @@ BrowserAccessibility* BrowserAccessibilityManager::CreateAccessibilityTree( focus_ = instance; for (int i = 0; i < static_cast<int>(src.children.size()); ++i) { BrowserAccessibility* child = CreateAccessibilityTree( - instance, src.children[i], i); + instance, GetNextChildID(), src.children[i], i); instance->AddChild(child); } diff --git a/chrome/browser/browser_accessibility_manager_win.h b/chrome/browser/browser_accessibility_manager_win.h index 32a8843..838bbda 100644 --- a/chrome/browser/browser_accessibility_manager_win.h +++ b/chrome/browser/browser_accessibility_manager_win.h @@ -10,6 +10,9 @@ #include <atlcom.h> #include <oleacc.h> +#include <hash_map> +#include <vector> + #include "base/hash_tables.h" #include "base/scoped_comptr_win.h" #include "base/scoped_ptr.h" @@ -32,6 +35,7 @@ class BrowserAccessibilityDelegate { virtual ~BrowserAccessibilityDelegate() {} virtual void SetAccessibilityFocus(int acc_obj_id) = 0; virtual void AccessibilityDoDefaultAction(int acc_obj_id) = 0; + virtual void AccessibilityObjectChildrenChangeAck() = 0; }; // Manages a tree of BrowserAccessibility objects. @@ -48,6 +52,9 @@ class BrowserAccessibilityManager { // Return a pointer to the root of the tree, does not make a new reference. BrowserAccessibility* GetRoot(); + // Removes the BrowserAccessibility child_id from the manager. + void Remove(LONG child_id); + // Return a pointer to the object corresponding to the given child_id, // does not make a new reference. BrowserAccessibility* GetFromChildID(LONG child_id); @@ -69,16 +76,22 @@ class BrowserAccessibilityManager { // Tell the renderer to do the default action for this node. void DoDefaultAction(const BrowserAccessibility& node); - // Called when the renderer process has notified us of a focus or state - // change. Send a notification to MSAA clients of the change. + // Called when the renderer process has notified us of a focus, state, + // or children change. Send a notification to MSAA clients of the change. void OnAccessibilityFocusChange(int acc_obj_id); void OnAccessibilityObjectStateChange(int acc_obj_id); + void OnAccessibilityObjectChildrenChange( + const std::vector<webkit_glue::WebAccessibility>& acc_changes); private: + // Returns the next MSAA child id. + static LONG GetNextChildID(); + // Recursively build a tree of BrowserAccessibility objects from // the WebAccessibility tree received from the renderer process. BrowserAccessibility* CreateAccessibilityTree( BrowserAccessibility* parent, + int child_id, const webkit_glue::WebAccessibility& src, int index_in_parent); diff --git a/chrome/browser/browser_accessibility_win.cc b/chrome/browser/browser_accessibility_win.cc index aed9bbc..06a67a5 100644 --- a/chrome/browser/browser_accessibility_win.cc +++ b/chrome/browser/browser_accessibility_win.cc @@ -55,6 +55,9 @@ void BrowserAccessibility::AddChild(BrowserAccessibility* child) { } void BrowserAccessibility::InactivateTree() { + if (!instance_active_) + return; + // Mark this object as inactive, so calls to all COM methods will return // failure. instance_active_ = false; @@ -70,6 +73,7 @@ void BrowserAccessibility::InactivateTree() { (*iter)->Release(); } children_.clear(); + manager_->Remove(child_id_); } bool BrowserAccessibility::IsDescendantOf(BrowserAccessibility* ancestor) { @@ -82,6 +86,10 @@ bool BrowserAccessibility::IsDescendantOf(BrowserAccessibility* ancestor) { return false; } +BrowserAccessibility* BrowserAccessibility::GetParent() { + return parent_; +} + BrowserAccessibility* BrowserAccessibility::GetPreviousSibling() { if (parent_ && index_in_parent_ > 0) return parent_->children_[index_in_parent_ - 1]; @@ -98,6 +106,14 @@ BrowserAccessibility* BrowserAccessibility::GetNextSibling() { return NULL; } +void BrowserAccessibility::ReplaceChild( + const BrowserAccessibility* old_acc, BrowserAccessibility* new_acc) { + DCHECK_EQ(children_[old_acc->index_in_parent_], old_acc); + + old_acc = children_[old_acc->index_in_parent_]; + children_[old_acc->index_in_parent_] = new_acc; +} + BrowserAccessibility* BrowserAccessibility::NewReference() { AddRef(); return this; diff --git a/chrome/browser/browser_accessibility_win.h b/chrome/browser/browser_accessibility_win.h index f66a6fb..17f7107 100644 --- a/chrome/browser/browser_accessibility_win.h +++ b/chrome/browser/browser_accessibility_win.h @@ -67,6 +67,10 @@ class ATL_NO_VTABLE BrowserAccessibility // Return true if this object is equal to or a descendant of |ancestor|. bool IsDescendantOf(BrowserAccessibility* ancestor); + // Returns the parent of this object, or NULL if it's the BrowserAccessibility + // root. + BrowserAccessibility* GetParent(); + // Return the previous sibling of this object, or NULL if it's the first // child of its parent. BrowserAccessibility* GetPreviousSibling(); @@ -75,9 +79,15 @@ class ATL_NO_VTABLE BrowserAccessibility // of its parent. BrowserAccessibility* GetNextSibling(); + // Replace a child BrowserAccessibility object. Used when updating the + // accessibility tree. + void ReplaceChild( + const BrowserAccessibility* old_acc, BrowserAccessibility* new_acc); + // Accessors LONG child_id() const { return child_id_; } int32 renderer_id() const { return renderer_id_; } + LONG index_in_parent() const { return index_in_parent_; } // Add one to the reference count and return the same object. Always // use this method when returning a BrowserAccessibility object as diff --git a/chrome/browser/browser_accessibility_win_unittest.cc b/chrome/browser/browser_accessibility_win_unittest.cc index 6d85671..95a89eb 100644 --- a/chrome/browser/browser_accessibility_win_unittest.cc +++ b/chrome/browser/browser_accessibility_win_unittest.cc @@ -3,6 +3,7 @@ // found in the LICENSE file. #include "base/scoped_ptr.h" +#include "base/scoped_comptr_win.h" #include "chrome/browser/browser_accessibility_manager_win.h" #include "chrome/browser/browser_accessibility_win.h" #include "testing/gtest/include/gtest/gtest.h" @@ -32,15 +33,35 @@ class CountedBrowserAccessibilityFactory : public BrowserAccessibilityFactory { } }; +VARIANT CreateI4Variant(LONG value) { + VARIANT variant = {0}; + + V_VT(&variant) = VT_I4; + V_I4(&variant) = value; + + return variant; +} + +class BrowserAccessibilityTest : public testing::Test { + protected: + virtual void SetUp() { + // ATL needs a pointer to a COM module. + static CComModule module; + _pAtlModule = &module; + + // Make sure COM is initialized for this thread; it's safe to call twice. + ::CoInitialize(NULL); + } + + virtual void TearDown() + { + ::CoUninitialize(); + } +}; + // Test that BrowserAccessibilityManager correctly releases the tree of // BrowserAccessibility instances upon delete. -TEST(BrowserAccessibilityTest, TestNoLeaks) { - // ATL needs a pointer to a COM module. - CComModule module; - _pAtlModule = &module; - // Make sure COM is initialized for this thread; it's safe to call twice. - ::CoInitialize(NULL); - +TEST_F(BrowserAccessibilityTest, TestNoLeaks) { // Create WebAccessibility objects for a simple document tree, // representing the accessibility information used to initialize // BrowserAccessibilityManager. @@ -109,3 +130,125 @@ TEST(BrowserAccessibilityTest, TestNoLeaks) { child1_iaccessible->Release(); ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_); } + +TEST_F(BrowserAccessibilityTest, TestChildrenChange) { + // Create WebAccessibility objects for a simple document tree, + // representing the accessibility information used to initialize + // BrowserAccessibilityManager. + WebAccessibility text; + text.id = 2; + text.role = WebAccessibility::ROLE_STATIC_TEXT; + text.value = L"old text"; + text.state = 0; + + WebAccessibility root; + root.id = 1; + root.name = L"Document"; + root.role = WebAccessibility::ROLE_DOCUMENT; + root.state = 0; + root.children.push_back(text); + + // Construct a BrowserAccessibilityManager with this WebAccessibility tree + // and a factory for an instance-counting BrowserAccessibility. + CountedBrowserAccessibility::global_obj_count_ = 0; + BrowserAccessibilityManager* manager = + new BrowserAccessibilityManager( + GetDesktopWindow(), root, NULL, + new CountedBrowserAccessibilityFactory()); + + // Query for the text IAccessible and verify that it returns "old text" as its + // value. + ScopedComPtr<IDispatch> text_dispatch; + HRESULT hr = manager->GetRoot()->get_accChild(CreateI4Variant(1), + text_dispatch.Receive()); + ASSERT_EQ(S_OK, hr); + + ScopedComPtr<IAccessible> text_accessible; + hr = text_dispatch.QueryInterface(text_accessible.Receive()); + ASSERT_EQ(S_OK, hr); + + CComBSTR value; + hr = text_accessible->get_accValue(CreateI4Variant(CHILDID_SELF), &value); + ASSERT_EQ(S_OK, hr); + EXPECT_STREQ(L"old text", value.m_str); + + text_dispatch.Release(); + text_accessible.Release(); + + // Notify the BrowserAccessibilityManager that the text child has changed. + text.value = L"new text"; + std::vector<WebAccessibility> acc_changes; + acc_changes.push_back(text); + manager->OnAccessibilityObjectChildrenChange(acc_changes); + + // Query for the text IAccessible and verify that it now returns "new text" + // as its value. + hr = manager->GetRoot()->get_accChild( + CreateI4Variant(1), + text_dispatch.Receive()); + ASSERT_EQ(S_OK, hr); + + hr = text_dispatch.QueryInterface(text_accessible.Receive()); + ASSERT_EQ(S_OK, hr); + + hr = text_accessible->get_accValue(CreateI4Variant(CHILDID_SELF), &value); + ASSERT_EQ(S_OK, hr); + EXPECT_STREQ(L"new text", value.m_str); + + text_dispatch.Release(); + text_accessible.Release(); + + // Delete the manager and test that all BrowserAccessibility instances are + // deleted. + delete manager; + ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_); +} + +TEST_F(BrowserAccessibilityTest, TestChildrenChangeNoLeaks) { + // Create WebAccessibility objects for a simple document tree, + // representing the accessibility information used to initialize + // BrowserAccessibilityManager. + WebAccessibility text; + text.id = 3; + text.role = WebAccessibility::ROLE_STATIC_TEXT; + text.state = 0; + + WebAccessibility div; + div.id = 2; + div.role = WebAccessibility::ROLE_GROUP; + div.state = 0; + + div.children.push_back(text); + text.id = 4; + div.children.push_back(text); + + WebAccessibility root; + root.id = 1; + root.role = WebAccessibility::ROLE_DOCUMENT; + root.state = 0; + root.children.push_back(div); + + // Construct a BrowserAccessibilityManager with this WebAccessibility tree + // and a factory for an instance-counting BrowserAccessibility and ensure + // that exactly 4 instances were created. Note that the manager takes + // ownership of the factory. + CountedBrowserAccessibility::global_obj_count_ = 0; + BrowserAccessibilityManager* manager = + new BrowserAccessibilityManager( + GetDesktopWindow(), root, NULL, + new CountedBrowserAccessibilityFactory()); + ASSERT_EQ(4, CountedBrowserAccessibility::global_obj_count_); + + // Notify the BrowserAccessibilityManager that the div node and its children + // were removed and ensure that only one BrowserAccessibility instance exists. + root.children.clear(); + std::vector<WebAccessibility> acc_changes; + acc_changes.push_back(root); + manager->OnAccessibilityObjectChildrenChange(acc_changes); + ASSERT_EQ(1, CountedBrowserAccessibility::global_obj_count_); + + // Delete the manager and test that all BrowserAccessibility instances are + // deleted. + delete manager; + ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_); +} diff --git a/chrome/browser/renderer_host/render_view_host.cc b/chrome/browser/renderer_host/render_view_host.cc index 70c6b96..9850ab1 100644 --- a/chrome/browser/renderer_host/render_view_host.cc +++ b/chrome/browser/renderer_host/render_view_host.cc @@ -837,6 +837,8 @@ void RenderViewHost::OnMessageReceived(const IPC::Message& msg) { OnAccessibilityFocusChange) IPC_MESSAGE_HANDLER(ViewHostMsg_AccessibilityObjectStateChange, OnAccessibilityObjectStateChange) + IPC_MESSAGE_HANDLER(ViewHostMsg_AccessibilityObjectChildrenChange, + OnAccessibilityObjectChildrenChange) IPC_MESSAGE_HANDLER(ViewHostMsg_OnCSSInserted, OnCSSInserted) IPC_MESSAGE_HANDLER(ViewHostMsg_PageContents, OnPageContents) IPC_MESSAGE_HANDLER(ViewHostMsg_PageTranslated, OnPageTranslated) @@ -1970,6 +1972,18 @@ void RenderViewHost::OnAccessibilityObjectStateChange(int acc_obj_id) { view()->OnAccessibilityObjectStateChange(acc_obj_id); } +void RenderViewHost::OnAccessibilityObjectChildrenChange( + const std::vector<webkit_glue::WebAccessibility>& acc_changes) { + view()->OnAccessibilityObjectChildrenChange(acc_changes); + + if (acc_changes.size() > 0) { + NotificationService::current()->Notify( + NotificationType::RENDER_VIEW_HOST_ACCESSIBILITY_TREE_UPDATED, + Source<RenderViewHost>(this), + NotificationService::NoDetails()); + } +} + void RenderViewHost::OnAccessibilityTree( const webkit_glue::WebAccessibility& tree) { if (view()) diff --git a/chrome/browser/renderer_host/render_view_host.h b/chrome/browser/renderer_host/render_view_host.h index 3010910..1d92cd9 100644 --- a/chrome/browser/renderer_host/render_view_host.h +++ b/chrome/browser/renderer_host/render_view_host.h @@ -654,6 +654,8 @@ class RenderViewHost : public RenderWidgetHost { void OnExtensionPostMessage(int port_id, const std::string& message); void OnAccessibilityFocusChange(int acc_obj_id); void OnAccessibilityObjectStateChange(int acc_obj_id); + void OnAccessibilityObjectChildrenChange( + const std::vector<webkit_glue::WebAccessibility>& acc_changes); void OnAccessibilityTree(const webkit_glue::WebAccessibility& tree); void OnCSSInserted(); void OnPageContents(const GURL& url, diff --git a/chrome/browser/renderer_host/render_widget_host.cc b/chrome/browser/renderer_host/render_widget_host.cc index 0a06602..30766ea 100644 --- a/chrome/browser/renderer_host/render_widget_host.cc +++ b/chrome/browser/renderer_host/render_widget_host.cc @@ -1166,6 +1166,10 @@ void RenderWidgetHost::AccessibilityDoDefaultAction(int acc_obj_id) { Send(new ViewMsg_AccessibilityDoDefaultAction(routing_id(), acc_obj_id)); } +void RenderWidgetHost::AccessibilityObjectChildrenChangeAck() { + Send(new ViewMsg_AccessibilityObjectChildrenChange_ACK(routing_id())); +} + void RenderWidgetHost::ProcessKeyboardEventAck(int type, bool processed) { if (key_queue_.size() == 0) { LOG(ERROR) << "Got a KeyEvent back from the renderer but we " diff --git a/chrome/browser/renderer_host/render_widget_host.h b/chrome/browser/renderer_host/render_widget_host.h index eac130e..ad31f2e 100644 --- a/chrome/browser/renderer_host/render_widget_host.h +++ b/chrome/browser/renderer_host/render_widget_host.h @@ -385,6 +385,9 @@ class RenderWidgetHost : public IPC::Channel::Listener, // on a node with this accessibility object id. void AccessibilityDoDefaultAction(int acc_obj_id); + // Acknowledges a ViewHostMsg_AccessibilityObjectChildrenChange message. + void AccessibilityObjectChildrenChangeAck(); + // Sets the active state (i.e., control tints). virtual void SetActive(bool active); diff --git a/chrome/browser/renderer_host/render_widget_host_view.h b/chrome/browser/renderer_host/render_widget_host_view.h index d651943..d4c09de 100644 --- a/chrome/browser/renderer_host/render_widget_host_view.h +++ b/chrome/browser/renderer_host/render_widget_host_view.h @@ -10,6 +10,9 @@ #include <OpenGL/OpenGL.h> #endif +#include <string> +#include <vector> + #include "app/surface/transport_dib.h" #include "gfx/native_widget_types.h" #include "third_party/skia/include/core/SkBitmap.h" @@ -250,6 +253,8 @@ class RenderWidgetHostView { const webkit_glue::WebAccessibility& tree) { } virtual void OnAccessibilityFocusChange(int acc_obj_id) { } virtual void OnAccessibilityObjectStateChange(int acc_obj_id) { } + virtual void OnAccessibilityObjectChildrenChange( + const std::vector<webkit_glue::WebAccessibility>& acc_changes) { } protected: // Interface class only, do not construct. diff --git a/chrome/browser/renderer_host/render_widget_host_view_win.cc b/chrome/browser/renderer_host/render_widget_host_view_win.cc index ac1be0b..defdc61 100644 --- a/chrome/browser/renderer_host/render_widget_host_view_win.cc +++ b/chrome/browser/renderer_host/render_widget_host_view_win.cc @@ -1511,6 +1511,14 @@ void RenderWidgetHostViewWin::OnAccessibilityObjectStateChange(int acc_obj_id) { } } +void RenderWidgetHostViewWin::OnAccessibilityObjectChildrenChange( + const std::vector<webkit_glue::WebAccessibility>& acc_changes) { + if (browser_accessibility_manager_.get()) { + browser_accessibility_manager_->OnAccessibilityObjectChildrenChange( + acc_changes); + } +} + void RenderWidgetHostViewWin::Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { @@ -1553,6 +1561,10 @@ void RenderWidgetHostViewWin::AccessibilityDoDefaultAction(int acc_obj_id) { render_widget_host_->AccessibilityDoDefaultAction(acc_obj_id); } +void RenderWidgetHostViewWin::AccessibilityObjectChildrenChangeAck() { + render_widget_host_->AccessibilityObjectChildrenChangeAck(); +} + LRESULT RenderWidgetHostViewWin::OnGetObject(UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) { if (lparam != OBJID_CLIENT) { diff --git a/chrome/browser/renderer_host/render_widget_host_view_win.h b/chrome/browser/renderer_host/render_widget_host_view_win.h index 9d17aa6..00f8c38 100644 --- a/chrome/browser/renderer_host/render_widget_host_view_win.h +++ b/chrome/browser/renderer_host/render_widget_host_view_win.h @@ -157,6 +157,8 @@ class RenderWidgetHostViewWin const webkit_glue::WebAccessibility& tree); virtual void OnAccessibilityFocusChange(int acc_obj_id); virtual void OnAccessibilityObjectStateChange(int acc_obj_id); + virtual void OnAccessibilityObjectChildrenChange( + const std::vector<webkit_glue::WebAccessibility>& acc_obj); // Implementation of NotificationObserver: virtual void Observe(NotificationType type, @@ -166,6 +168,7 @@ class RenderWidgetHostViewWin // Implementation of BrowserAccessibilityDelegate: virtual void SetAccessibilityFocus(int acc_obj_id); virtual void AccessibilityDoDefaultAction(int acc_obj_id); + virtual void AccessibilityObjectChildrenChangeAck(); protected: // Windows Message Handlers diff --git a/chrome/common/notification_type.h b/chrome/common/notification_type.h index 08bc47f..df520c6 100644 --- a/chrome/common/notification_type.h +++ b/chrome/common/notification_type.h @@ -431,9 +431,9 @@ class NotificationType { // first RenderViewHost is set). RENDER_VIEW_HOST_CHANGED, - // Indicates that the render view host has received a new accessibility tree - // from the render view. The source is the RenderViewHost, the details - // are not used. + // Indicates that the render view host has received an accessibility tree + // update, either partial or full, from the render view. The source is the + // RenderViewHost, the details are not used. RENDER_VIEW_HOST_ACCESSIBILITY_TREE_UPDATED, // This is sent when a RenderWidgetHost is being destroyed. The source is diff --git a/chrome/common/render_messages_internal.h b/chrome/common/render_messages_internal.h index 9d4fa57..97b17d9 100644 --- a/chrome/common/render_messages_internal.h +++ b/chrome/common/render_messages_internal.h @@ -990,6 +990,11 @@ IPC_BEGIN_MESSAGES(View) IPC_MESSAGE_ROUTED1(ViewMsg_AccessibilityDoDefaultAction, int /* object id */) + // Tells the render view that a ViewHostMsg_AccessibilityObjectChildrenChange + // message was processed, and the render view host is ready for additional + // children change messages. + IPC_MESSAGE_ROUTED0(ViewMsg_AccessibilityObjectChildrenChange_ACK) + // Relay a speech recognition result, either partial or final. IPC_MESSAGE_ROUTED2(ViewMsg_SpeechInput_SetRecognitionResult, int /* request id */, @@ -2216,12 +2221,22 @@ IPC_BEGIN_MESSAGES(ViewHost) IPC_MESSAGE_ROUTED1(ViewHostMsg_AccessibilityFocusChange, int /* accessibility object id */) - // Send as a result of a state change in the renderer (if accessibility is + // Sent as a result of a state change in the renderer (if accessibility is // enabled), to notify the browser side. Takes the id of the accessibility // object that had a state change IPC_MESSAGE_ROUTED1(ViewHostMsg_AccessibilityObjectStateChange, int /* accessibility object id */) + // Sent by the renderer as a result of a accessibility node children change. + // The browser responds with a ViewMsg_AccessibilityObjectChildrenChange_ACK. + IPC_MESSAGE_ROUTED1(ViewHostMsg_AccessibilityObjectChildrenChange, + std::vector<webkit_glue::WebAccessibility>) + + // Send the tree of accessibility data to the browser, where it's cached + // in order to respond to OS accessibility queries immediately. + IPC_MESSAGE_ROUTED1(ViewHostMsg_AccessibilityTree, + webkit_glue::WebAccessibility) + // Message sent from the renderer to the browser to request that the browser // close all sockets. Used for debugging/testing. IPC_MESSAGE_CONTROL0(ViewHostMsg_CloseCurrentConnections) @@ -2441,9 +2456,9 @@ IPC_BEGIN_MESSAGES(ViewHost) int32, /* response_id */ string16 /* name */) - // WebIDBObjectStore::openCursor() message. - IPC_MESSAGE_CONTROL1(ViewHostMsg_IDBObjectStoreOpenCursor, - ViewHostMsg_IDBObjectStoreOpenCursor_Params) + // WebIDBObjectStore::openCursor() message. + IPC_MESSAGE_CONTROL1(ViewHostMsg_IDBObjectStoreOpenCursor, + ViewHostMsg_IDBObjectStoreOpenCursor_Params) // WebIDBObjectStore::~WebIDBObjectStore() message. IPC_MESSAGE_CONTROL1(ViewHostMsg_IDBObjectStoreDestroyed, @@ -2668,11 +2683,6 @@ IPC_BEGIN_MESSAGES(ViewHost) int /* render_view_id */, int /* bridge_id */) - // Send the tree of accessibility data to the browser, where it's cached - // in order to respond to OS accessibility queries immediately. - IPC_MESSAGE_ROUTED1(ViewHostMsg_AccessibilityTree, - webkit_glue::WebAccessibility) - // Notifies the TabContents that the content being displayed is PDF. // This allows the browser to handle things such as zooming differently. IPC_MESSAGE_ROUTED0(ViewHostMsg_SetDisplayingPDFContent) diff --git a/chrome/renderer/render_view.cc b/chrome/renderer/render_view.cc index 5af03eb..49e6934 100644 --- a/chrome/renderer/render_view.cc +++ b/chrome/renderer/render_view.cc @@ -782,6 +782,8 @@ void RenderView::OnMessageReceived(const IPC::Message& message) { IPC_MESSAGE_HANDLER(ViewMsg_SetAccessibilityFocus, OnSetAccessibilityFocus) IPC_MESSAGE_HANDLER(ViewMsg_AccessibilityDoDefaultAction, OnAccessibilityDoDefaultAction) + IPC_MESSAGE_HANDLER(ViewMsg_AccessibilityObjectChildrenChange_ACK, + OnAccessibilityObjectChildrenChangeAck) IPC_MESSAGE_HANDLER(ViewMsg_OpenFileSystemRequest_Complete, OnOpenFileSystemRequestComplete) @@ -1443,6 +1445,7 @@ void RenderView::UpdateURL(WebFrame* frame) { accessibility_->clear(); accessibility_.reset(); } + accessibility_changes_.clear(); } // Tell the embedding application that the title of the active page has changed @@ -4311,6 +4314,7 @@ void RenderView::OnGetAccessibilityTree() { accessibility_->clear(); accessibility_.reset(WebAccessibilityCache::create()); accessibility_->initialize(webview()); + accessibility_changes_.clear(); WebAccessibilityObject src_tree = webview()->accessibilityObject(); webkit_glue::WebAccessibility dst_tree(src_tree, accessibility_.get()); @@ -4340,6 +4344,19 @@ void RenderView::OnAccessibilityDoDefaultAction(int acc_obj_id) { } } +void RenderView::OnAccessibilityObjectChildrenChangeAck() { + if (!accessibility_.get()) + return; + + if (!accessibility_changes_.empty()) { + Send(new ViewHostMsg_AccessibilityObjectChildrenChange( + routing_id_, + accessibility_changes_)); + } + + accessibility_changes_.clear(); +} + void RenderView::OnGetAllSavableResourceLinksForCurrentPage( const GURL& page_url) { // Prepare list to storage all savable resource links. @@ -5351,6 +5368,21 @@ void RenderView::didChangeAccessibilityObjectState( #endif } +void RenderView::didChangeAccessibilityObjectChildren( + const WebKit::WebAccessibilityObject& acc_obj) { + if (!accessibility_.get()) + return; + + if (accessibility_changes_.empty()) { + Send(new ViewHostMsg_AccessibilityObjectChildrenChange( + routing_id_, + std::vector<webkit_glue::WebAccessibility>())); + } + + accessibility_changes_.push_back( + webkit_glue::WebAccessibility(acc_obj, accessibility_.get())); +} + void RenderView::Print(WebFrame* frame, bool script_initiated) { DCHECK(frame); if (print_helper_.get() == NULL) { diff --git a/chrome/renderer/render_view.h b/chrome/renderer/render_view.h index 326acac..8e4d807 100644 --- a/chrome/renderer/render_view.h +++ b/chrome/renderer/render_view.h @@ -398,6 +398,8 @@ class RenderView : public RenderWidget, const WebKit::WebAccessibilityObject& acc_obj); virtual void didChangeAccessibilityObjectState( const WebKit::WebAccessibilityObject& acc_obj); + virtual void didChangeAccessibilityObjectChildren( + const WebKit::WebAccessibilityObject& acc_obj); virtual void didUpdateInspectorSetting(const WebKit::WebString& key, const WebKit::WebString& value); virtual void queryAutofillSuggestions(const WebKit::WebNode& node, @@ -707,6 +709,7 @@ class RenderView : public RenderWidget, // render_messages_internal.h for the message that the function is handling. void OnAccessibilityDoDefaultAction(int acc_obj_id); + void OnAccessibilityObjectChildrenChangeAck(); void OnAllowBindings(int enabled_bindings_flags); void OnAddMessageToConsole(const string16& frame_xpath, const string16& message, @@ -1239,6 +1242,8 @@ class RenderView : public RenderWidget, // maintains the cache and other features of the accessibility tree. scoped_ptr<WebKit::WebAccessibilityCache> accessibility_; + std::vector<webkit_glue::WebAccessibility> accessibility_changes_; + // The speech dispatcher attached to this view, lazily initialized. scoped_ptr<SpeechInputDispatcher> speech_input_dispatcher_; |