Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(189)

Side by Side Diff: content/browser/accessibility/accessibility_win_browsertest.cc

Issue 660633002: Fixed IAccessibleText::TextAtOffset with IA2_TEXT_BOUNDARY_WORD to return text that spans from the … (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Added semi-automated NVDA test for word navigation and removed dependency on pywinauto. Created 6 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include <vector> 5 #include <vector>
6 6
7 #include "base/memory/scoped_ptr.h" 7 #include "base/memory/scoped_ptr.h"
8 #include "base/strings/utf_string_conversions.h" 8 #include "base/strings/utf_string_conversions.h"
9 #include "base/win/scoped_bstr.h" 9 #include "base/win/scoped_bstr.h"
10 #include "base/win/scoped_comptr.h" 10 #include "base/win/scoped_comptr.h"
(...skipping 13 matching lines...) Expand all
24 #include "content/test/accessibility_browser_test_utils.h" 24 #include "content/test/accessibility_browser_test_utils.h"
25 #include "third_party/iaccessible2/ia2_api_all.h" 25 #include "third_party/iaccessible2/ia2_api_all.h"
26 #include "third_party/isimpledom/ISimpleDOMNode.h" 26 #include "third_party/isimpledom/ISimpleDOMNode.h"
27 #include "ui/aura/window.h" 27 #include "ui/aura/window.h"
28 #include "ui/aura/window_tree_host.h" 28 #include "ui/aura/window_tree_host.h"
29 29
30 namespace content { 30 namespace content {
31 31
32 namespace { 32 namespace {
33 33
34 // Helpers -------------------------------------------------------------------- 34 // AccessibilityWinBrowserTest ------------------------------------------------
35 35
36 base::win::ScopedComPtr<IAccessible> GetAccessibleFromResultVariant( 36 class AccessibilityWinBrowserTest : public ContentBrowserTest {
37 IAccessible* parent, 37 public:
38 VARIANT* var) { 38 AccessibilityWinBrowserTest();
39 virtual ~AccessibilityWinBrowserTest();
40
41 protected:
42 void LoadInitialAccessibilityTreeFromHtml(const std::string& html);
43 IAccessible* GetRendererAccessible();
44 void ExecuteScript(const std::wstring& script);
45
46 static base::win::ScopedComPtr<IAccessible>
47 AccessibilityWinBrowserTest::GetAccessibleFromResultVariant(
48 IAccessible* parent, VARIANT* var);
49 static HRESULT QueryIAccessible2(IAccessible* accessible, IAccessible2** acces sible2);
dmazzoni 2014/11/03 16:26:49 nit: wrap to 80 chars
50 static void RecursiveFindNodeInAccessibilityTree(IAccessible* node,
51 int32 expected_role, const std::wstring& expected_name,
52 int32 depth, bool* found);
53 static void CheckTextAtOffset(IAccessible2 element,
54 LONG offset, IA2TextBoundaryType boundary_type,
55 LONG expected_start_offset, LONG expected_end_offset,
56 std::wstring& expected_text);
57
58 private:
59 DISALLOW_COPY_AND_ASSIGN(AccessibilityWinBrowserTest);
60 };
61
62 AccessibilityWinBrowserTest::AccessibilityWinBrowserTest() {
63 }
64
65 AccessibilityWinBrowserTest::~AccessibilityWinBrowserTest() {
66 }
67
68 void AccessibilityWinBrowserTest::LoadInitialAccessibilityTreeFromHtml(
69 const std::string& html) {
70 AccessibilityNotificationWaiter waiter(
71 shell(), AccessibilityModeComplete,
72 ui::AX_EVENT_LOAD_COMPLETE);
73 GURL html_data_url("data:text/html," + html);
74 NavigateToURL(shell(), html_data_url);
75 waiter.WaitForNotification();
76 }
77
78 // Retrieve the MSAA client accessibility object for the Render Widget Host View
79 // of the selected tab.
80 IAccessible* AccessibilityWinBrowserTest::GetRendererAccessible() {
81 content::WebContents* web_contents = shell()->web_contents();
82 return web_contents->GetRenderWidgetHostView()->GetNativeViewAccessible();
83 }
84
85 void AccessibilityWinBrowserTest::ExecuteScript(const std::wstring& script) {
86 shell()->web_contents()->GetMainFrame()->ExecuteJavaScript(script);
87 }
88
89 // Static helpers ------------------------------------------------
90
91 base::win::ScopedComPtr<IAccessible>
92 AccessibilityWinBrowserTest::GetAccessibleFromResultVariant(
93 IAccessible* parent, VARIANT* var) {
39 base::win::ScopedComPtr<IAccessible> ptr; 94 base::win::ScopedComPtr<IAccessible> ptr;
40 switch (V_VT(var)) { 95 switch (V_VT(var)) {
41 case VT_DISPATCH: { 96 case VT_DISPATCH: {
42 IDispatch* dispatch = V_DISPATCH(var); 97 IDispatch* dispatch = V_DISPATCH(var);
43 if (dispatch) 98 if (dispatch)
44 ptr.QueryFrom(dispatch); 99 ptr.QueryFrom(dispatch);
45 break; 100 break;
46 } 101 }
47 102
48 case VT_I4: { 103 case VT_I4: {
49 base::win::ScopedComPtr<IDispatch> dispatch; 104 base::win::ScopedComPtr<IDispatch> dispatch;
50 HRESULT hr = parent->get_accChild(*var, dispatch.Receive()); 105 HRESULT hr = parent->get_accChild(*var, dispatch.Receive());
51 EXPECT_TRUE(SUCCEEDED(hr)); 106 EXPECT_TRUE(SUCCEEDED(hr));
52 if (dispatch) 107 if (dispatch)
53 dispatch.QueryInterface(ptr.Receive()); 108 dispatch.QueryInterface(ptr.Receive());
54 break; 109 break;
55 } 110 }
56 } 111 }
57 return ptr; 112 return ptr;
58 } 113 }
59 114
60 HRESULT QueryIAccessible2(IAccessible* accessible, IAccessible2** accessible2) { 115 HRESULT AccessibilityWinBrowserTest::QueryIAccessible2(IAccessible* accessible, IAccessible2** accessible2) {
61 // TODO(ctguil): For some reason querying the IAccessible2 interface from 116 // IA2 Spec dictates that IServiceProvider should be used instead of
62 // IAccessible fails. 117 // QueryInterface when retrieving IAccessible2.
63 base::win::ScopedComPtr<IServiceProvider> service_provider; 118 base::win::ScopedComPtr<IServiceProvider> service_provider;
64 HRESULT hr = accessible->QueryInterface(service_provider.Receive()); 119 HRESULT hr = accessible->QueryInterface(service_provider.Receive());
65 return SUCCEEDED(hr) ? 120 return SUCCEEDED(hr) ?
66 service_provider->QueryService(IID_IAccessible2, accessible2) : hr; 121 service_provider->QueryService(IID_IAccessible2, accessible2) : hr;
67 } 122 }
68 123
69 // Recursively search through all of the descendants reachable from an 124 // Recursively search through all of the descendants reachable from an
70 // IAccessible node and return true if we find one with the given role 125 // IAccessible node and return true if we find one with the given role
71 // and name. 126 // and name.
72 void RecursiveFindNodeInAccessibilityTree(IAccessible* node, 127 void AccessibilityWinBrowserTest::RecursiveFindNodeInAccessibilityTree(
73 int32 expected_role, 128 IAccessible* node, int32 expected_role, const std::wstring& expected_name,
74 const std::wstring& expected_name, 129 int32 depth, bool* found) {
75 int32 depth,
76 bool* found) {
77 base::win::ScopedBstr name_bstr; 130 base::win::ScopedBstr name_bstr;
78 base::win::ScopedVariant childid_self(CHILDID_SELF); 131 base::win::ScopedVariant childid_self(CHILDID_SELF);
79 node->get_accName(childid_self, name_bstr.Receive()); 132 node->get_accName(childid_self, name_bstr.Receive());
80 std::wstring name(name_bstr, name_bstr.Length()); 133 std::wstring name(name_bstr, name_bstr.Length());
81 base::win::ScopedVariant role; 134 base::win::ScopedVariant role;
82 node->get_accRole(childid_self, role.Receive()); 135 node->get_accRole(childid_self, role.Receive());
83 ASSERT_EQ(VT_I4, role.type()); 136 ASSERT_EQ(VT_I4, role.type());
84 137
85 // Print the accessibility tree as we go, because if this test fails 138 // Print the accessibility tree as we go, because if this test fails
86 // on the bots, this is really helpful in figuring out why. 139 // on the bots, this is really helpful in figuring out why.
(...skipping 25 matching lines...) Expand all
112 if (child_accessible) { 165 if (child_accessible) {
113 RecursiveFindNodeInAccessibilityTree( 166 RecursiveFindNodeInAccessibilityTree(
114 child_accessible.get(), expected_role, expected_name, depth + 1, 167 child_accessible.get(), expected_role, expected_name, depth + 1,
115 found); 168 found);
116 if (*found) 169 if (*found)
117 return; 170 return;
118 } 171 }
119 } 172 }
120 } 173 }
121 174
175 // Ensures that the text, the start and end offsets retrieved using
176 // get_textAtOffset match the expected values.
177 void AccessibilityWinBrowserTest::CheckTextAtOffset(IAccessible2 element,
178 LONG offset, IA2TextBoundaryType boundary_type,
179 LONG expected_start_offset, LONG expected_end_offset,
180 std::wstring& expected_text) {
181 const std::wstring message = L"while checking for " + expected_text +
182 L" at " + expected_start_offset + L'-' + expected_end_offset;
dmazzoni 2014/11/03 16:26:49 I don't think you can append integers to strings i
183 SCOPED_TRACE(message);
122 184
123 // AccessibilityWinBrowserTest ------------------------------------------------ 185 LONG start_offset = 0;
124 186 LONG end_offset = 0;
125 class AccessibilityWinBrowserTest : public ContentBrowserTest { 187 base::win::ScopedBstr text;
126 public: 188 HRESULT hr = element->get_textAtOffset(
127 AccessibilityWinBrowserTest(); 189 offset, boundary_type,
128 virtual ~AccessibilityWinBrowserTest(); 190 &start_offset, &end_offset, text.Receive());
129 191 EXPECT_EQ(S_OK, hr);
130 protected: 192 EXPECT_EQ(expected_start_offset, start_offset);
131 void LoadInitialAccessibilityTreeFromHtml(const std::string& html); 193 EXPECT_EQ(expected_end_offset, end_offset);
132 IAccessible* GetRendererAccessible(); 194 EXPECT_EQ(expected_text, std:wstring(text, text.Length()));
133 void ExecuteScript(const std::wstring& script);
134
135 private:
136 DISALLOW_COPY_AND_ASSIGN(AccessibilityWinBrowserTest);
137 };
138
139 AccessibilityWinBrowserTest::AccessibilityWinBrowserTest() {
140 }
141
142 AccessibilityWinBrowserTest::~AccessibilityWinBrowserTest() {
143 }
144
145 void AccessibilityWinBrowserTest::LoadInitialAccessibilityTreeFromHtml(
146 const std::string& html) {
147 AccessibilityNotificationWaiter waiter(
148 shell(), AccessibilityModeComplete,
149 ui::AX_EVENT_LOAD_COMPLETE);
150 GURL html_data_url("data:text/html," + html);
151 NavigateToURL(shell(), html_data_url);
152 waiter.WaitForNotification();
153 }
154
155 // Retrieve the MSAA client accessibility object for the Render Widget Host View
156 // of the selected tab.
157 IAccessible* AccessibilityWinBrowserTest::GetRendererAccessible() {
158 content::WebContents* web_contents = shell()->web_contents();
159 return web_contents->GetRenderWidgetHostView()->GetNativeViewAccessible();
160 }
161
162 void AccessibilityWinBrowserTest::ExecuteScript(const std::wstring& script) {
163 shell()->web_contents()->GetMainFrame()->ExecuteJavaScript(script);
164 } 195 }
165 196
166 197
167 // AccessibleChecker ---------------------------------------------------------- 198 // AccessibleChecker ----------------------------------------------------------
168 199
169 class AccessibleChecker { 200 class AccessibleChecker {
170 public: 201 public:
171 // This constructor can be used if the IA2 role will be the same as the MSAA 202 // This constructor can be used if the IA2 role will be the same as the MSAA
172 // role. 203 // role.
173 AccessibleChecker(const std::wstring& expected_name, 204 AccessibleChecker(const std::wstring& expected_name,
(...skipping 572 matching lines...) Expand 10 before | Expand all | Expand 10 after
746 std::wstring()); 777 std::wstring());
747 AccessibleChecker grouping2_checker(std::wstring(), ROLE_SYSTEM_GROUPING, 778 AccessibleChecker grouping2_checker(std::wstring(), ROLE_SYSTEM_GROUPING,
748 std::wstring()); 779 std::wstring());
749 AccessibleChecker document_checker(std::wstring(), ROLE_SYSTEM_DOCUMENT, 780 AccessibleChecker document_checker(std::wstring(), ROLE_SYSTEM_DOCUMENT,
750 std::wstring()); 781 std::wstring());
751 document_checker.AppendExpectedChild(&grouping1_checker); 782 document_checker.AppendExpectedChild(&grouping1_checker);
752 document_checker.AppendExpectedChild(&grouping2_checker); 783 document_checker.AppendExpectedChild(&grouping2_checker);
753 document_checker.CheckAccessible(GetRendererAccessible()); 784 document_checker.CheckAccessible(GetRendererAccessible());
754 } 785 }
755 786
787 IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest, TestTextAtOffset) {
788 const char input_contents[] = "Mozilla/5.0 (NT 6.x; WOW64) "
789 "WebKit (KHTML, like).";
790 const char textarea_contents[] = "Mozilla/5.0 (NT 6.x; WOW64)\n"
791 "WebKit \n(KHTML, like).";
792 LONG contents_length = sizeof(input_contents) / sizeof(char);
793 LoadInitialAccessibilityTreeFromHtml("<!DOCTYPE html>"
794 "<html><body><form>"
795 "<label for='name'>Browser name:</label>"
796 "<input type='text' id='name' name='name' value='"
797 "Mozilla/5.0 (NT 6.x; WOW64) WebKit "
798 "(KHTML, like).'>"
799 "</form><textarea rows='3' cols='60'>"
800 "Mozilla/5.0 (NT 6.x; WOW64)<br>"
801 "WebKit <br>(KHTML, like)."
802 "</textarea></body></html>");
803
804 // Retrieve the IAccessible interface for the web page.
805 base::win::ScopedComPtr<IAccessible> document = GetRendererAccessible();
806 LONG child_count = 0;
807 HRESULT hr = document->get_accChildCount(&child_count);
808 ASSERT_EQ(S_OK, hr);
809 ASSERT_EQ(2, child_count);
810 // Get all the document's children.
811 scoped_ptr<VARIANT[]> children(new VARIANT[child_count]);
812 LONG obtained_count = 0;
813 hr = AccessibleChildren(
814 document, 0, child_count, children.get(), &obtained_count);
815 ASSERT_EQ(S_OK, hr);
816 ASSERT_EQ(child_count, obtained_count);
817
818 // Find the two edit fields (simple and text area).
819 base::win::ScopedComPtr<IAccessible2> input;
820 hr = QueryIAccessible2(GetAccessibleFromResultVariant(
821 document, children.get()[0]), input.Receive());
822 ASSERT_EQ(S_OK, hr);
823 base::win::ScopedComPtr<IAccessible2> textarea;
824 hr = QueryIAccessible2(GetAccessibleFromResultVariant(
825 document, children.get()[1]), textarea.Receive());
826 ASSERT_EQ(S_OK, hr);
827
828 // Retrieve the IAccessibleText interface for both of the fields.
829 base::win::ScopedComPtr<IAccessibleText> input_text;
830 hr = input->QueryInterface(input_text.Receive());
831 ASSERT_EQ(S_OK, hr);
832 base::win::ScopedComPtr<IAccessibleText> textarea_text;
833 hr = textarea->QueryInterface(textarea_text.Receive());
834 ASSERT_EQ(S_OK, hr);
835
836 // Test invalid arguments.
837 hr = input_text->get_textAtOffset(
838 0, IA2_TEXT_BOUNDARY_CHAR, NULL, NULL, NULL);
839 EXPECT_EQ(E_INVALIDARG, hr);
840 hr = textarea_text->get_textAtOffset(
841 0, IA2_TEXT_BOUNDARY_CHAR, NULL, NULL, NULL);
842 EXPECT_EQ(E_INVALIDARG, hr);
843
844 // Test invalid offset.
845 LONG invalid_offset = -5;
846 LONG start_offset = 0;
847 LONG end_offset = 0;
848 base::win::ScopedBstr text;
849 hr = input_text->get_textAtOffset(
850 invalid_offset, IA2_TEXT_BOUNDARY_CHAR,
851 &start_offset, &end_offset, text.Receive());
852 EXPECT_EQ(E_FAIL, hr);
853 hr = textarea_text->get_textAtOffset(
854 invalid_offset, IA2_TEXT_BOUNDARY_CHAR,
855 &start_offset, &end_offset, text.Receive());
856 EXPECT_EQ(E_FAIL, hr);
857 invalid_offset = contents_length;
858 hr = input_text->get_textAtOffset(
859 invalid_offset, IA2_TEXT_BOUNDARY_CHAR,
860 &start_offset, &end_offset, text.Receive());
861 EXPECT_EQ(E_FAIL, hr);
862 hr = textarea_text->get_textAtOffset(
863 invalid_offset, IA2_TEXT_BOUNDARY_CHAR,
864 &start_offset, &end_offset, text.Receive());
865 EXPECT_EQ(E_FAIL, hr);
866
867 // Test character navigation.
868 for (LONG offset = 0; offset < contents_length; ++offset) {
869 std::wstring expected_text(input_contents[offset], 1);
870 LONG expected_start_offset = offset;
871 LONG expected_end_offset = offset + 1;
872 CheckTextAtOffset(input_text, offset, IA2_TEXT_BOUNDARY_CHAR,
873 expected_start_offset, expected_end_offset, expected_text);
874 }
875 for (LONG offset = contents_length - 1; offset >= 0; --offset) {
876 std::wstring expected_text(textarea_contents[offset], 1);
877 LONG expected_start_offset = offset;
878 LONG expected_end_offset = offset + 1;
879 CheckTextAtOffset(textarea_text, offset, IA2_TEXT_BOUNDARY_CHAR,
880 expected_start_offset, expected_end_offset, expected_text);
881 }
882
883 // Test word navigation.
dmazzoni 2014/11/03 16:26:49 Please add tests for passing IA2_TEXT_OFFSET_LENGT
884 // (Imitate Firefox behavior.)
885 // Trailing punctuation should be included as part of the previous word.
886 CheckTextAtOffset(input_text, 0, IA2_TEXT_BOUNDARY_WORD,
887 0, 8, L"Mozilla/");
888 CheckTextAtOffset(textarea_text, 6, IA2_TEXT_BOUNDARY_WORD,
889 0, 8, L"Mozilla/");
890 // If the offset is at the punctuation, it should return
891 // the previous word.
892 CheckTextAtOffset(textarea_text, 7, IA2_TEXT_BOUNDARY_WORD,
893 0, 8, L"Mozilla/");
894 // Numbers with a decimal point (U.S), should be treated as two words.
895 CheckTextAtOffset(input_text, 8, IA2_TEXT_BOUNDARY_WORD,
896 8, 10, L"5.");
897 CheckTextAtOffset(textarea_text, 9, IA2_TEXT_BOUNDARY_WORD,
898 8, 10, L"5.");
899 // Also, trailing punctuation that occurs after empty space should not be
900 // part of the word. ("0 " and not "0 (".)
901 CheckTextAtOffset(input_text, 10, IA2_TEXT_BOUNDARY_WORD,
902 10, 12, L"0 ");
903 CheckTextAtOffset(textarea_text, 11, IA2_TEXT_BOUNDARY_WORD,
904 10, 12, L"0 ");
905 // Leading punctuation should be included with the word after it.
906 CheckTextAtOffset(input_text, 12, IA2_TEXT_BOUNDARY_WORD,
907 12, 16, L"(NT ");
908 CheckTextAtOffset(textarea_text, 15, IA2_TEXT_BOUNDARY_WORD,
909 12, 16, L"(NT ");
910 // Numbers separated from letters with trailing punctuation should
911 // behave like decimal numbers above.
912 CheckTextAtOffset(input_text, 16, IA2_TEXT_BOUNDARY_WORD,
913 16, 18, L"6.");
914 CheckTextAtOffset(textarea_text, 19, IA2_TEXT_BOUNDARY_WORD,
915 18, 21, L"x; ");
916 // Words with numbers should be treated like ordinary words.
917 CheckTextAtOffset(input_text, 24, IA2_TEXT_BOUNDARY_WORD,
918 21, 28, L"WOW64) ");
919 CheckTextAtOffset(textarea_text, 26, IA2_TEXT_BOUNDARY_WORD,
920 21, 28, L"WOW64)\n");
921 // Multiple trailing empty spaces should be part of the word preceding it.
922 CheckTextAtOffset(input_text, 30, IA2_TEXT_BOUNDARY_WORD,
923 28, 36, L"WebKit ");
924 CheckTextAtOffset(textarea_text, 35, IA2_TEXT_BOUNDARY_WORD,
925 28, 36, L"WebKit \n");
926
927 // Test sentence navigation (not currently implemented).
928 hr = input_text->get_textAtOffset(
929 5, IA2_TEXT_BOUNDARY_SENTENCE,
930 &start_offset, &end_offset, text.Receive());
931 EXPECT_EQ(S_FALSE, hr);
932 hr = textarea_text->get_textAtOffset(
933 25, IA2_TEXT_BOUNDARY_CHAR,
934 &start_offset, &end_offset, text.Receive());
935 EXPECT_EQ(S_FALSE, hr);
936
937 // Test line navigation.
938 CheckTextAtOffset(textarea_text, 0, IA2_TEXT_BOUNDARY_LINE,
939 0, 28, L"Mozilla/5.0 (NT 6.x; WOW64)\n");
940 // If the offset is at the newline, return the line preceding it.
941 CheckTextAtOffset(textarea_text, 35, IA2_TEXT_BOUNDARY_LINE,
942 28, 36, L"WebKit \n");
943 // Last line does not have a trailing newline.
944 CheckTextAtOffset(textarea_text, 36, IA2_TEXT_BOUNDARY_LINE,
945 36, contents_length, L"(KHTML, like).");
946
947 // Return the whole of the text.
948 CheckTextAtOffset(input_text, 0, IA2_TEXT_BOUNDARY_ALL,
949 0, contents_length, std::string(input_contents, contents_length));
950 CheckTextAtOffset(textarea_text, contents_length - 1, IA2_TEXT_BOUNDARY_ALL,
951 0, contents_length, std::string(textarea_contents, contents_length));
952 }
953
756 } // namespace content 954 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698