Commit 57831c0d authored by dmazzoni's avatar dmazzoni Committed by Commit bot

Fire AX text inserted event when embedded obj char changes.

See bug for details. This patch makes it so that we fire
the events  IA2_EVENT_TEXT_INSERTED and IA2_EVENT_TEXT_REMOVED
when the children referenced by the embedded object characters
changed, not just when the text changed.

This patch also avoids incorrectly firing these same two
events on a newly-created node.

BUG=425861

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

Cr-Commit-Position: refs/heads/master@{#314280}
parent 5eef2621
...@@ -58,14 +58,8 @@ class CONTENT_EXPORT BrowserAccessibility { ...@@ -58,14 +58,8 @@ class CONTENT_EXPORT BrowserAccessibility {
// its data changes. // its data changes.
virtual void OnDataChanged() {} virtual void OnDataChanged() {}
// Called after an atomic update to the tree finished and this object
// was created or changed in this update.
virtual void OnUpdateFinished() {}
virtual void OnSubtreeWillBeDeleted() {} virtual void OnSubtreeWillBeDeleted() {}
virtual void OnSubtreeCreationFinished() {}
// Called when the location changed. // Called when the location changed.
virtual void OnLocationChanged() {} virtual void OnLocationChanged() {}
......
...@@ -433,15 +433,6 @@ void BrowserAccessibilityManager::OnNodeChanged(ui::AXNode* node) { ...@@ -433,15 +433,6 @@ void BrowserAccessibilityManager::OnNodeChanged(ui::AXNode* node) {
void BrowserAccessibilityManager::OnAtomicUpdateFinished( void BrowserAccessibilityManager::OnAtomicUpdateFinished(
bool root_changed, bool root_changed,
const std::vector<ui::AXTreeDelegate::Change>& changes) { const std::vector<ui::AXTreeDelegate::Change>& changes) {
for (size_t i = 0; i < changes.size(); ++i) {
BrowserAccessibility* obj = GetFromAXNode(changes[i].node);
if (!obj)
continue;
obj->OnUpdateFinished();
if (changes[i].type == AXTreeDelegate::SUBTREE_CREATED)
GetFromAXNode(changes[i].node)->OnSubtreeCreationFinished();
}
} }
BrowserAccessibilityDelegate* BrowserAccessibilityDelegate*
......
...@@ -306,17 +306,39 @@ void BrowserAccessibilityManagerWin::OnAtomicUpdateFinished( ...@@ -306,17 +306,39 @@ void BrowserAccessibilityManagerWin::OnAtomicUpdateFinished(
OnWindowFocused(); OnWindowFocused();
} }
// BrowserAccessibilityManager::OnAtomicUpdateFinished calls // Do a sequence of Windows-specific updates on each node. Each one is
// OnUpdateFinished() on each node in |changes|. However, the // done in a single pass that must complete before the next step starts.
// IAccessibleText text for a node is a concatenatenation of all of its child // The first step moves win_attributes_ to old_win_attributes_ and then
// text nodes, so we can't compute a node's IAccessibleText in // recomputes all of win_attributes_ other than IAccessibleText.
// OnUpdateFinished because its children may not have been updated yet.
//
// So we make a second pass here to update IAccessibleText.
for (size_t i = 0; i < changes.size(); ++i) { for (size_t i = 0; i < changes.size(); ++i) {
BrowserAccessibility* obj = GetFromAXNode(changes[i].node); BrowserAccessibility* obj = GetFromAXNode(changes[i].node);
if (obj && obj->IsNative()) if (obj && obj->IsNative() && !obj->PlatformIsChildOfLeaf())
obj->ToBrowserAccessibilityWin()->UpdateIAccessibleText(); obj->ToBrowserAccessibilityWin()->UpdateStep1ComputeWinAttributes();
}
// The next step updates the hypertext of each node, which is a
// concatenation of all of its child text nodes, so it can't run until
// the text of all of the nodes was computed in the previous step.
for (size_t i = 0; i < changes.size(); ++i) {
BrowserAccessibility* obj = GetFromAXNode(changes[i].node);
if (obj && obj->IsNative() && !obj->PlatformIsChildOfLeaf())
obj->ToBrowserAccessibilityWin()->UpdateStep2ComputeHypertext();
}
// The third step fires events on nodes based on what's changed - like
// if the name, value, or description changed, or if the hypertext had
// text inserted or removed. It's able to figure out exactly what changed
// because we still have old_win_attributes_ populated.
// This step has to run after the previous two steps complete because the
// client may walk the tree when it receives any of these events.
// At the end, it deletes old_win_attributes_ since they're not needed
// anymore.
for (size_t i = 0; i < changes.size(); ++i) {
BrowserAccessibility* obj = GetFromAXNode(changes[i].node);
if (obj && obj->IsNative() && !obj->PlatformIsChildOfLeaf()) {
obj->ToBrowserAccessibilityWin()->UpdateStep3FireEvents(
changes[i].type == AXTreeDelegate::SUBTREE_CREATED);
}
} }
} }
......
...@@ -82,7 +82,7 @@ BrowserAccessibilityWin ...@@ -82,7 +82,7 @@ BrowserAccessibilityWin
// Represents a non-static text node in IAccessibleHypertext. This character // Represents a non-static text node in IAccessibleHypertext. This character
// is embedded in the response to IAccessibleText::get_text, indicating the // is embedded in the response to IAccessibleText::get_text, indicating the
// position where a non-static text child object appears. // position where a non-static text child object appears.
CONTENT_EXPORT static const base::char16 kEmbeddedCharacter[]; CONTENT_EXPORT static const base::char16 kEmbeddedCharacter;
// Mappings from roles and states to human readable strings. Initialize // Mappings from roles and states to human readable strings. Initialize
// with |InitializeStringMaps|. // with |InitializeStringMaps|.
...@@ -97,15 +97,18 @@ BrowserAccessibilityWin ...@@ -97,15 +97,18 @@ BrowserAccessibilityWin
// like NotifyWinEvent, and as the unique ID for IAccessible2 and ISimpleDOM. // like NotifyWinEvent, and as the unique ID for IAccessible2 and ISimpleDOM.
LONG unique_id_win() const { return unique_id_win_; } LONG unique_id_win() const { return unique_id_win_; }
CONTENT_EXPORT void UpdateIAccessibleText(); // Called after an atomic tree update completes. See
// BrowserAccessibilityManagerWin::OnAtomicUpdateFinished for more
// details on what these do.
CONTENT_EXPORT void UpdateStep1ComputeWinAttributes();
CONTENT_EXPORT void UpdateStep2ComputeHypertext();
CONTENT_EXPORT void UpdateStep3FireEvents(bool is_subtree_creation);
CONTENT_EXPORT void UpdateStep4DeleteOldWinAttributes();
// //
// BrowserAccessibility methods. // BrowserAccessibility methods.
// //
CONTENT_EXPORT virtual void OnDataChanged() override;
CONTENT_EXPORT virtual void OnUpdateFinished() override;
CONTENT_EXPORT virtual void OnSubtreeWillBeDeleted() override; CONTENT_EXPORT virtual void OnSubtreeWillBeDeleted() override;
CONTENT_EXPORT virtual void OnSubtreeCreationFinished() override;
CONTENT_EXPORT virtual void NativeAddReference() override; CONTENT_EXPORT virtual void NativeAddReference() override;
CONTENT_EXPORT virtual void NativeReleaseReference() override; CONTENT_EXPORT virtual void NativeReleaseReference() override;
CONTENT_EXPORT virtual bool IsNative() const override; CONTENT_EXPORT virtual bool IsNative() const override;
...@@ -791,6 +794,11 @@ BrowserAccessibilityWin ...@@ -791,6 +794,11 @@ BrowserAccessibilityWin
base::string16 description() const { return win_attributes_->description; } base::string16 description() const { return win_attributes_->description; }
base::string16 help() const { return win_attributes_->help; } base::string16 help() const { return win_attributes_->help; }
base::string16 value() const { return win_attributes_->value; } base::string16 value() const { return win_attributes_->value; }
base::string16 hypertext() const { return win_attributes_->hypertext; }
std::map<int32, int32>& hyperlink_offset_to_index() const {
return win_attributes_->hyperlink_offset_to_index;
}
std::vector<int32>& hyperlinks() const { return win_attributes_->hyperlinks; }
private: private:
// Add one to the reference count and return the same object. Always // Add one to the reference count and return the same object. Always
...@@ -842,6 +850,7 @@ BrowserAccessibilityWin ...@@ -842,6 +850,7 @@ BrowserAccessibilityWin
// be the name, it may be the value, etc. depending on the role. // be the name, it may be the value, etc. depending on the role.
base::string16 TextForIAccessibleText(); base::string16 TextForIAccessibleText();
bool IsSameHypertextCharacter(size_t old_char_index, size_t new_char_index);
void ComputeHypertextRemovedAndInserted( void ComputeHypertextRemovedAndInserted(
int* start, int* old_len, int* new_len); int* start, int* old_len, int* new_len);
...@@ -889,6 +898,16 @@ BrowserAccessibilityWin ...@@ -889,6 +898,16 @@ BrowserAccessibilityWin
// IAccessible2 attributes. // IAccessible2 attributes.
std::vector<base::string16> ia2_attributes; std::vector<base::string16> ia2_attributes;
// Hypertext.
base::string16 hypertext;
// Maps the |hypertext_| embedded character offset to an index in
// |hyperlinks_|.
std::map<int32, int32> hyperlink_offset_to_index;
// The id of a BrowserAccessibilityWin for each hyperlink.
std::vector<int32> hyperlinks;
}; };
scoped_ptr<WinAttributes> win_attributes_; scoped_ptr<WinAttributes> win_attributes_;
...@@ -900,19 +919,6 @@ BrowserAccessibilityWin ...@@ -900,19 +919,6 @@ BrowserAccessibilityWin
// Relationships between this node and other nodes. // Relationships between this node and other nodes.
std::vector<BrowserAccessibilityRelation*> relations_; std::vector<BrowserAccessibilityRelation*> relations_;
// IAccessibleText text of this node including
// embedded hyperlink characters.
base::string16 old_hypertext_;
base::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_;
// The previous scroll position, so we can tell if this object scrolled. // The previous scroll position, so we can tell if this object scrolled.
int previous_scroll_x_; int previous_scroll_x_;
int previous_scroll_y_; int previous_scroll_y_;
......
...@@ -442,7 +442,7 @@ TEST_F(BrowserAccessibilityTest, TestSimpleHypertext) { ...@@ -442,7 +442,7 @@ TEST_F(BrowserAccessibilityTest, TestSimpleHypertext) {
CountedBrowserAccessibility::reset(); CountedBrowserAccessibility::reset();
scoped_ptr<BrowserAccessibilityManager> manager( scoped_ptr<BrowserAccessibilityManager> manager(
BrowserAccessibilityManager::Create( BrowserAccessibilityManager::Create(
MakeAXTreeUpdate(root, root, text1, text2), MakeAXTreeUpdate(root, text1, text2),
NULL, new CountedBrowserAccessibilityFactory())); NULL, new CountedBrowserAccessibilityFactory()));
ASSERT_EQ(3, CountedBrowserAccessibility::num_instances()); ASSERT_EQ(3, CountedBrowserAccessibility::num_instances());
...@@ -547,7 +547,7 @@ TEST_F(BrowserAccessibilityTest, TestComplexHypertext) { ...@@ -547,7 +547,7 @@ TEST_F(BrowserAccessibilityTest, TestComplexHypertext) {
base::win::ScopedBstr text; base::win::ScopedBstr text;
ASSERT_EQ(S_OK, root_obj->get_text(0, text_len, text.Receive())); ASSERT_EQ(S_OK, root_obj->get_text(0, text_len, text.Receive()));
const std::string embed = base::UTF16ToUTF8( const std::string embed = base::UTF16ToUTF8(
BrowserAccessibilityWin::kEmbeddedCharacter); base::string16(1, BrowserAccessibilityWin::kEmbeddedCharacter));
EXPECT_EQ(text1_name + embed + text2_name + embed, EXPECT_EQ(text1_name + embed + text2_name + embed,
base::UTF16ToUTF8(base::string16(text))); base::UTF16ToUTF8(base::string16(text)));
text.Reset(); text.Reset();
......
...@@ -194,6 +194,11 @@ IN_PROC_BROWSER_TEST_F(DumpAccessibilityEventsTest, ...@@ -194,6 +194,11 @@ IN_PROC_BROWSER_TEST_F(DumpAccessibilityEventsTest,
RunEventTest(FILE_PATH_LITERAL("description-change.html")); RunEventTest(FILE_PATH_LITERAL("description-change.html"));
} }
IN_PROC_BROWSER_TEST_F(DumpAccessibilityEventsTest,
AccessibilityEventsInnerHtmlChange) {
RunEventTest(FILE_PATH_LITERAL("inner-html-change.html"));
}
IN_PROC_BROWSER_TEST_F(DumpAccessibilityEventsTest, IN_PROC_BROWSER_TEST_F(DumpAccessibilityEventsTest,
AccessibilityEventsInputTypeTextValueChanged) { AccessibilityEventsInputTypeTextValueChanged) {
RunEventTest(FILE_PATH_LITERAL("input-type-text-value-changed.html")); RunEventTest(FILE_PATH_LITERAL("input-type-text-value-changed.html"));
......
EVENT_OBJECT_SHOW on role=ROLE_SYSTEM_LISTITEM name="Item 3"
IA2_EVENT_TEXT_INSERTED on role=ROLE_SYSTEM_LIST new_text={'<obj>' start=2 end=3} IA2_EVENT_TEXT_INSERTED on role=ROLE_SYSTEM_LIST new_text={'<obj>' start=2 end=3}
EVENT_OBJECT_SHOW on role=ROLE_SYSTEM_LISTITEM name="Item 3"
EVENT_OBJECT_REORDER on role=ROLE_SYSTEM_LIST EVENT_OBJECT_REORDER on role=ROLE_SYSTEM_LIST
EVENT_OBJECT_SHOW on role=ROLE_SYSTEM_LISTITEM name="Item 3"
IA2_EVENT_TEXT_INSERTED on role=ROLE_SYSTEM_LIST new_text={'<obj>' start=2 end=3} IA2_EVENT_TEXT_INSERTED on role=ROLE_SYSTEM_LIST new_text={'<obj>' start=2 end=3}
IA2_EVENT_TEXT_INSERTED on role=ROLE_SYSTEM_LISTITEM name="Item 3" new_text={'<obj>Item 3' start=0 end=7} EVENT_OBJECT_SHOW on role=ROLE_SYSTEM_LISTITEM name="Item 3"
EVENT_OBJECT_REORDER on role=ROLE_SYSTEM_LIST EVENT_OBJECT_REORDER on role=ROLE_SYSTEM_LIST
EVENT_OBJECT_HIDE on role=div name="Heading" EVENT_OBJECT_HIDE on role=div name="Heading"
IA2_EVENT_TEXT_REMOVED on role=ROLE_SYSTEM_TOOLBAR old_text={'<obj>' start=0 end=1}
IA2_EVENT_TEXT_INSERTED on role=ROLE_SYSTEM_TOOLBAR new_text={'<obj>' start=0 end=1}
EVENT_OBJECT_SHOW on role=ROLE_SYSTEM_GROUPING name="Banner" EVENT_OBJECT_SHOW on role=ROLE_SYSTEM_GROUPING name="Banner"
EVENT_OBJECT_REORDER on role=ROLE_SYSTEM_TOOLBAR EVENT_OBJECT_REORDER on role=ROLE_SYSTEM_TOOLBAR
EVENT_OBJECT_HIDE on role=div name="Heading" EVENT_OBJECT_HIDE on role=div name="Heading"
IA2_EVENT_TEXT_REMOVED on role=ROLE_SYSTEM_TOOLBAR old_text={'<obj>' start=0 end=1}
IA2_EVENT_TEXT_INSERTED on role=ROLE_SYSTEM_TOOLBAR new_text={'<obj>' start=0 end=1}
EVENT_OBJECT_SHOW on role=ROLE_SYSTEM_GROUPING name="Banner" EVENT_OBJECT_SHOW on role=ROLE_SYSTEM_GROUPING name="Banner"
EVENT_OBJECT_REORDER on role=ROLE_SYSTEM_TOOLBAR EVENT_OBJECT_REORDER on role=ROLE_SYSTEM_TOOLBAR
EVENT_OBJECT_HIDE on role=h1 name="A"
EVENT_OBJECT_HIDE on role=P
IA2_EVENT_TEXT_REMOVED on role=ROLE_SYSTEM_GROUPING old_text={'<obj><obj>' start=0 end=2}
IA2_EVENT_TEXT_INSERTED on role=ROLE_SYSTEM_GROUPING new_text={'<obj><obj>' start=0 end=2}
EVENT_OBJECT_SHOW on role=h1 name="C"
EVENT_OBJECT_SHOW on role=P
<!--
The children changed / reorder event is fired an unpredictable number of times.
@WIN-DENY:EVENT_OBJECT_REORDER*
-->
<!DOCTYPE html>
<html>
<body>
<div role="group" id="main"><h1>A</h1><p>B</p></div>
<script>
function go() {
document.querySelector('#main').innerHTML = '<h1>C</h1><p>D</p>';
}
</script>
</body>
</html>
EVENT_OBJECT_SHOW on role=ROLE_SYSTEM_LISTITEM name="Item 3"
IA2_EVENT_TEXT_INSERTED on role=ROLE_SYSTEM_LIST new_text={'<obj>' start=2 end=3} IA2_EVENT_TEXT_INSERTED on role=ROLE_SYSTEM_LIST new_text={'<obj>' start=2 end=3}
EVENT_OBJECT_SHOW on role=ROLE_SYSTEM_LISTITEM name="Item 3"
EVENT_OBJECT_REORDER on role=ROLE_SYSTEM_LIST EVENT_OBJECT_REORDER on role=ROLE_SYSTEM_LIST
EVENT_OBJECT_SHOW on role=ROLE_SYSTEM_LISTITEM name="Item 3"
IA2_EVENT_TEXT_INSERTED on role=ROLE_SYSTEM_LIST new_text={'<obj>' start=2 end=3} IA2_EVENT_TEXT_INSERTED on role=ROLE_SYSTEM_LIST new_text={'<obj>' start=2 end=3}
IA2_EVENT_TEXT_INSERTED on role=ROLE_SYSTEM_LISTITEM name="Item 3" new_text={'<obj>Item 3' start=0 end=7} EVENT_OBJECT_SHOW on role=ROLE_SYSTEM_LISTITEM name="Item 3"
EVENT_OBJECT_REORDER on role=ROLE_SYSTEM_LIST EVENT_OBJECT_REORDER on role=ROLE_SYSTEM_LIST
EVENT_OBJECT_HIDE on role=ROLE_SYSTEM_STATICTEXT name="Para" EVENT_OBJECT_HIDE on role=ROLE_SYSTEM_STATICTEXT name="Para"
EVENT_OBJECT_SHOW on role=ROLE_SYSTEM_STATICTEXT name="Modified Para"
IA2_EVENT_TEXT_INSERTED on role=P new_text={'Modified ' start=0 end=9} IA2_EVENT_TEXT_INSERTED on role=P new_text={'Modified ' start=0 end=9}
EVENT_OBJECT_SHOW on role=ROLE_SYSTEM_STATICTEXT name="Modified Para"
EVENT_OBJECT_NAMECHANGE on role=ROLE_SYSTEM_STATICTEXT name="Modified Heading" EVENT_OBJECT_NAMECHANGE on role=ROLE_SYSTEM_STATICTEXT name="Modified Heading"
IA2_EVENT_TEXT_INSERTED on role=h2 name="Heading" new_text={'Modified ' start=0 end=9} IA2_EVENT_TEXT_INSERTED on role=h2 name="Heading" new_text={'Modified ' start=0 end=9}
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