1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "ui/views/accessibility/view_ax_platform_node_delegate_win.h"
6
7 #include <oleacc.h>
8 #include <wrl/client.h>
9
10 #include <memory>
11 #include <utility>
12
13 #include "base/win/scoped_bstr.h"
14 #include "base/win/scoped_variant.h"
15 #include "third_party/iaccessible2/ia2_api_all.h"
16 #include "ui/accessibility/ax_constants.mojom.h"
17 #include "ui/accessibility/platform/ax_platform_node_win.h"
18 #include "ui/views/accessibility/test_list_grid_view.h"
19 #include "ui/views/controls/label.h"
20 #include "ui/views/controls/scroll_view.h"
21 #include "ui/views/controls/textfield/textfield.h"
22 #include "ui/views/test/views_test_base.h"
23
24 using base::win::ScopedBstr;
25 using base::win::ScopedVariant;
26 using Microsoft::WRL::ComPtr;
27
28 namespace views {
29 namespace test {
30
31 #define EXPECT_UIA_BOOL_EQ(node, property_id, expected) \
32 { \
33 ScopedVariant expectedVariant(expected); \
34 ASSERT_EQ(VT_BOOL, expectedVariant.type()); \
35 ScopedVariant actual; \
36 ASSERT_HRESULT_SUCCEEDED( \
37 node->GetPropertyValue(property_id, actual.Receive())); \
38 EXPECT_EQ(expectedVariant.ptr()->boolVal, actual.ptr()->boolVal); \
39 }
40
41 namespace {
42
43 // Whether |left| represents the same COM object as |right|.
44 template <typename T, typename U>
IsSameObject(T * left,U * right)45 bool IsSameObject(T* left, U* right) {
46 if (!left && !right)
47 return true;
48
49 if (!left || !right)
50 return false;
51
52 ComPtr<IUnknown> left_unknown;
53 left->QueryInterface(IID_PPV_ARGS(&left_unknown));
54
55 ComPtr<IUnknown> right_unknown;
56 right->QueryInterface(IID_PPV_ARGS(&right_unknown));
57
58 return left_unknown == right_unknown;
59 }
60
61 } // namespace
62
63 class ViewAXPlatformNodeDelegateWinTest : public ViewsTestBase {
64 public:
65 ViewAXPlatformNodeDelegateWinTest() = default;
66 ~ViewAXPlatformNodeDelegateWinTest() override = default;
67
68 protected:
GetIAccessible2InterfaceForView(View * view,IAccessible2_2 ** result)69 void GetIAccessible2InterfaceForView(View* view, IAccessible2_2** result) {
70 ComPtr<IAccessible> view_accessible(view->GetNativeViewAccessible());
71 ComPtr<IServiceProvider> service_provider;
72 ASSERT_EQ(S_OK, view_accessible.As(&service_provider));
73 ASSERT_EQ(S_OK, service_provider->QueryService(IID_IAccessible2_2, result));
74 }
75
GetIRawElementProviderSimple(View * view)76 ComPtr<IRawElementProviderSimple> GetIRawElementProviderSimple(View* view) {
77 ComPtr<IRawElementProviderSimple> result;
78 EXPECT_HRESULT_SUCCEEDED(view->GetNativeViewAccessible()->QueryInterface(
79 __uuidof(IRawElementProviderSimple), &result));
80 return result;
81 }
82 };
83
TEST_F(ViewAXPlatformNodeDelegateWinTest,TextfieldAccessibility)84 TEST_F(ViewAXPlatformNodeDelegateWinTest, TextfieldAccessibility) {
85 Widget widget;
86 Widget::InitParams init_params = CreateParams(Widget::InitParams::TYPE_POPUP);
87 init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
88 widget.Init(std::move(init_params));
89
90 View* content = widget.SetContentsView(std::make_unique<View>());
91
92 Textfield* textfield = new Textfield;
93 textfield->SetAccessibleName(L"Name");
94 textfield->SetText(L"Value");
95 content->AddChildView(textfield);
96
97 ComPtr<IAccessible> content_accessible(content->GetNativeViewAccessible());
98 LONG child_count = 0;
99 ASSERT_EQ(S_OK, content_accessible->get_accChildCount(&child_count));
100 EXPECT_EQ(1, child_count);
101
102 ComPtr<IDispatch> textfield_dispatch;
103 ComPtr<IAccessible> textfield_accessible;
104 ScopedVariant child_index(1);
105 ASSERT_EQ(S_OK,
106 content_accessible->get_accChild(child_index, &textfield_dispatch));
107 ASSERT_EQ(S_OK, textfield_dispatch.As(&textfield_accessible));
108
109 ASSERT_EQ(S_OK, textfield_accessible->get_accChildCount(&child_count));
110 EXPECT_EQ(0, child_count)
111 << "Text fields should be leaf nodes on this platform, otherwise no "
112 "descendants will be recognized by assistive software.";
113
114 ScopedBstr name;
115 ScopedVariant childid_self(CHILDID_SELF);
116 ASSERT_EQ(S_OK,
117 textfield_accessible->get_accName(childid_self, name.Receive()));
118 EXPECT_STREQ(L"Name", name.Get());
119
120 ScopedBstr value;
121 ASSERT_EQ(S_OK,
122 textfield_accessible->get_accValue(childid_self, value.Receive()));
123 EXPECT_STREQ(L"Value", value.Get());
124
125 ScopedBstr new_value(L"New value");
126 ASSERT_EQ(S_OK,
127 textfield_accessible->put_accValue(childid_self, new_value.Get()));
128 EXPECT_STREQ(L"New value", textfield->GetText().c_str());
129 }
130
TEST_F(ViewAXPlatformNodeDelegateWinTest,TextfieldAssociatedLabel)131 TEST_F(ViewAXPlatformNodeDelegateWinTest, TextfieldAssociatedLabel) {
132 Widget widget;
133 Widget::InitParams init_params = CreateParams(Widget::InitParams::TYPE_POPUP);
134 init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
135 widget.Init(std::move(init_params));
136
137 View* content = widget.SetContentsView(std::make_unique<View>());
138
139 Label* label = new Label(L"Label");
140 content->AddChildView(label);
141 Textfield* textfield = new Textfield;
142 textfield->SetAssociatedLabel(label);
143 content->AddChildView(textfield);
144
145 ComPtr<IAccessible> content_accessible(content->GetNativeViewAccessible());
146 LONG child_count = 0;
147 ASSERT_EQ(S_OK, content_accessible->get_accChildCount(&child_count));
148 ASSERT_EQ(2L, child_count);
149
150 ComPtr<IDispatch> textfield_dispatch;
151 ComPtr<IAccessible> textfield_accessible;
152 ScopedVariant child_index(2);
153 ASSERT_EQ(S_OK,
154 content_accessible->get_accChild(child_index, &textfield_dispatch));
155 ASSERT_EQ(S_OK, textfield_dispatch.As(&textfield_accessible));
156
157 ScopedBstr name;
158 ScopedVariant childid_self(CHILDID_SELF);
159 ASSERT_EQ(S_OK,
160 textfield_accessible->get_accName(childid_self, name.Receive()));
161 ASSERT_STREQ(L"Label", name.Get());
162
163 ComPtr<IAccessible2_2> textfield_ia2;
164 EXPECT_EQ(S_OK, textfield_accessible.As(&textfield_ia2));
165 ScopedBstr type(IA2_RELATION_LABELLED_BY);
166 IUnknown** targets;
167 LONG n_targets;
168 EXPECT_EQ(S_OK, textfield_ia2->get_relationTargetsOfType(
169 type.Get(), 0, &targets, &n_targets));
170 ASSERT_EQ(1, n_targets);
171 ComPtr<IUnknown> label_unknown(targets[0]);
172 ComPtr<IAccessible> label_accessible;
173 ASSERT_EQ(S_OK, label_unknown.As(&label_accessible));
174 ScopedVariant role;
175 EXPECT_EQ(S_OK, label_accessible->get_accRole(childid_self, role.Receive()));
176 EXPECT_EQ(ROLE_SYSTEM_STATICTEXT, V_I4(role.ptr()));
177 }
178
179 // A subclass of ViewAXPlatformNodeDelegateWinTest that we run twice,
180 // first where we create an transient child widget (child = false), the second
181 // time where we create a child widget (child = true).
182 class ViewAXPlatformNodeDelegateWinTestWithBoolChildFlag
183 : public ViewAXPlatformNodeDelegateWinTest,
184 public testing::WithParamInterface<bool> {
185 public:
186 ViewAXPlatformNodeDelegateWinTestWithBoolChildFlag() = default;
187 ViewAXPlatformNodeDelegateWinTestWithBoolChildFlag(
188 const ViewAXPlatformNodeDelegateWinTestWithBoolChildFlag&) = delete;
189 ViewAXPlatformNodeDelegateWinTestWithBoolChildFlag& operator=(
190 const ViewAXPlatformNodeDelegateWinTestWithBoolChildFlag&) = delete;
191 ~ViewAXPlatformNodeDelegateWinTestWithBoolChildFlag() override = default;
192 };
193
194 INSTANTIATE_TEST_SUITE_P(All,
195 ViewAXPlatformNodeDelegateWinTestWithBoolChildFlag,
196 testing::Bool());
197
TEST_P(ViewAXPlatformNodeDelegateWinTestWithBoolChildFlag,AuraChildWidgets)198 TEST_P(ViewAXPlatformNodeDelegateWinTestWithBoolChildFlag, AuraChildWidgets) {
199 // Create the parent widget.
200 Widget widget;
201 Widget::InitParams init_params =
202 CreateParams(Widget::InitParams::TYPE_WINDOW);
203 init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
204 init_params.bounds = gfx::Rect(0, 0, 400, 200);
205 widget.Init(std::move(init_params));
206 widget.Show();
207
208 // Initially it has 1 child.
209 ComPtr<IAccessible> root_view_accessible(
210 widget.GetRootView()->GetNativeViewAccessible());
211 LONG child_count = 0;
212 ASSERT_EQ(S_OK, root_view_accessible->get_accChildCount(&child_count));
213 ASSERT_EQ(1L, child_count);
214
215 // Create the child widget, one of two ways (see below).
216 Widget child_widget;
217 Widget::InitParams child_init_params =
218 CreateParams(Widget::InitParams::TYPE_BUBBLE);
219 child_init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
220 child_init_params.parent = widget.GetNativeView();
221 child_init_params.bounds = gfx::Rect(30, 40, 100, 50);
222
223 // NOTE: this test is run two times, GetParam() returns a different
224 // value each time. The first time we test with child = false,
225 // making this an owned widget (a transient child). The second time
226 // we test with child = true, making it a child widget.
227 child_init_params.child = GetParam();
228
229 child_widget.Init(std::move(child_init_params));
230 child_widget.Show();
231
232 // Now the IAccessible for the parent widget should have 2 children.
233 ASSERT_EQ(S_OK, root_view_accessible->get_accChildCount(&child_count));
234 ASSERT_EQ(2L, child_count);
235
236 // Ensure the bounds of the parent widget are as expected.
237 ScopedVariant childid_self(CHILDID_SELF);
238 LONG x, y, width, height;
239 ASSERT_EQ(S_OK, root_view_accessible->accLocation(&x, &y, &width, &height,
240 childid_self));
241 EXPECT_EQ(0, x);
242 EXPECT_EQ(0, y);
243 EXPECT_EQ(400, width);
244 EXPECT_EQ(200, height);
245
246 // Get the IAccessible for the second child of the parent widget,
247 // which should be the one for our child widget.
248 ComPtr<IDispatch> child_widget_dispatch;
249 ComPtr<IAccessible> child_widget_accessible;
250 ScopedVariant child_index_2(2);
251 ASSERT_EQ(S_OK, root_view_accessible->get_accChild(child_index_2,
252 &child_widget_dispatch));
253 ASSERT_EQ(S_OK, child_widget_dispatch.As(&child_widget_accessible));
254
255 // Check the bounds of the IAccessible for the child widget.
256 // This is a sanity check to make sure we have the right object
257 // and not some other view.
258 ASSERT_EQ(S_OK, child_widget_accessible->accLocation(&x, &y, &width, &height,
259 childid_self));
260 EXPECT_EQ(30, x);
261 EXPECT_EQ(40, y);
262 EXPECT_EQ(100, width);
263 EXPECT_EQ(50, height);
264
265 // Now make sure that querying the parent of the child gets us back to
266 // the original parent.
267 ComPtr<IDispatch> child_widget_parent_dispatch;
268 ComPtr<IAccessible> child_widget_parent_accessible;
269 ASSERT_EQ(S_OK, child_widget_accessible->get_accParent(
270 &child_widget_parent_dispatch));
271 ASSERT_EQ(S_OK,
272 child_widget_parent_dispatch.As(&child_widget_parent_accessible));
273 EXPECT_EQ(root_view_accessible.Get(), child_widget_parent_accessible.Get());
274 }
275
276 // Flaky on Windows: https://crbug.com/461837.
TEST_F(ViewAXPlatformNodeDelegateWinTest,DISABLED_RetrieveAllAlerts)277 TEST_F(ViewAXPlatformNodeDelegateWinTest, DISABLED_RetrieveAllAlerts) {
278 Widget widget;
279 Widget::InitParams init_params = CreateParams(Widget::InitParams::TYPE_POPUP);
280 init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
281 widget.Init(std::move(init_params));
282
283 View* content = widget.SetContentsView(std::make_unique<View>());
284
285 View* infobar = new View;
286 content->AddChildView(infobar);
287
288 View* infobar2 = new View;
289 content->AddChildView(infobar2);
290
291 View* root_view = content->parent();
292 ASSERT_EQ(nullptr, root_view->parent());
293
294 ComPtr<IAccessible2_2> root_view_accessible;
295 GetIAccessible2InterfaceForView(root_view, &root_view_accessible);
296
297 ComPtr<IAccessible2_2> infobar_accessible;
298 GetIAccessible2InterfaceForView(infobar, &infobar_accessible);
299
300 ComPtr<IAccessible2_2> infobar2_accessible;
301 GetIAccessible2InterfaceForView(infobar2, &infobar2_accessible);
302
303 // Initially, there are no alerts
304 ScopedBstr alerts_bstr(L"alerts");
305 IUnknown** targets;
306 LONG n_targets;
307 ASSERT_EQ(S_FALSE, root_view_accessible->get_relationTargetsOfType(
308 alerts_bstr.Get(), 0, &targets, &n_targets));
309 ASSERT_EQ(0, n_targets);
310
311 // Fire alert events on the infobars.
312 infobar->NotifyAccessibilityEvent(ax::mojom::Event::kAlert, true);
313 infobar2->NotifyAccessibilityEvent(ax::mojom::Event::kAlert, true);
314
315 // Now calling get_relationTargetsOfType should retrieve the alerts.
316 ASSERT_EQ(S_OK, root_view_accessible->get_relationTargetsOfType(
317 alerts_bstr.Get(), 0, &targets, &n_targets));
318 ASSERT_EQ(2, n_targets);
319 ASSERT_TRUE(IsSameObject(infobar_accessible.Get(), targets[0]));
320 ASSERT_TRUE(IsSameObject(infobar2_accessible.Get(), targets[1]));
321 CoTaskMemFree(targets);
322
323 // If we set max_targets to 1, we should only get the first one.
324 ASSERT_EQ(S_OK, root_view_accessible->get_relationTargetsOfType(
325 alerts_bstr.Get(), 1, &targets, &n_targets));
326 ASSERT_EQ(1, n_targets);
327 ASSERT_TRUE(IsSameObject(infobar_accessible.Get(), targets[0]));
328 CoTaskMemFree(targets);
329
330 // If we delete the first view, we should only get the second one now.
331 delete infobar;
332 ASSERT_EQ(S_OK, root_view_accessible->get_relationTargetsOfType(
333 alerts_bstr.Get(), 0, &targets, &n_targets));
334 ASSERT_EQ(1, n_targets);
335 ASSERT_TRUE(IsSameObject(infobar2_accessible.Get(), targets[0]));
336 CoTaskMemFree(targets);
337 }
338
339 // Test trying to retrieve child widgets during window close does not crash.
TEST_F(ViewAXPlatformNodeDelegateWinTest,GetAllOwnedWidgetsCrash)340 TEST_F(ViewAXPlatformNodeDelegateWinTest, GetAllOwnedWidgetsCrash) {
341 Widget widget;
342 Widget::InitParams init_params =
343 CreateParams(Widget::InitParams::TYPE_WINDOW);
344 init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
345 widget.Init(std::move(init_params));
346 widget.CloseNow();
347
348 LONG child_count = 0;
349 ComPtr<IAccessible> content_accessible(
350 widget.GetRootView()->GetNativeViewAccessible());
351 EXPECT_EQ(S_OK, content_accessible->get_accChildCount(&child_count));
352 EXPECT_EQ(1L, child_count);
353 }
354
TEST_F(ViewAXPlatformNodeDelegateWinTest,WindowHasRoleApplication)355 TEST_F(ViewAXPlatformNodeDelegateWinTest, WindowHasRoleApplication) {
356 // We expect that our internal window object does not expose
357 // ROLE_SYSTEM_WINDOW, but ROLE_SYSTEM_PANE instead.
358 Widget widget;
359 Widget::InitParams init_params =
360 CreateParams(Widget::InitParams::TYPE_WINDOW);
361 init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
362 widget.Init(std::move(init_params));
363
364 ComPtr<IAccessible> accessible(
365 widget.GetRootView()->GetNativeViewAccessible());
366 ScopedVariant childid_self(CHILDID_SELF);
367 ScopedVariant role;
368 EXPECT_EQ(S_OK, accessible->get_accRole(childid_self, role.Receive()));
369 EXPECT_EQ(VT_I4, role.type());
370 EXPECT_EQ(ROLE_SYSTEM_PANE, V_I4(role.ptr()));
371 }
372
TEST_F(ViewAXPlatformNodeDelegateWinTest,Overrides)373 TEST_F(ViewAXPlatformNodeDelegateWinTest, Overrides) {
374 // We expect that our internal window object does not expose
375 // ROLE_SYSTEM_WINDOW, but ROLE_SYSTEM_PANE instead.
376 Widget widget;
377 Widget::InitParams init_params = CreateParams(Widget::InitParams::TYPE_POPUP);
378 init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
379 widget.Init(std::move(init_params));
380
381 View* contents_view = widget.SetContentsView(std::make_unique<View>());
382
383 View* alert_view = new ScrollView;
384 alert_view->GetViewAccessibility().OverrideRole(ax::mojom::Role::kAlert);
385 alert_view->GetViewAccessibility().OverrideName(L"Name");
386 alert_view->GetViewAccessibility().OverrideDescription("Description");
387 alert_view->GetViewAccessibility().OverrideIsLeaf(true);
388 contents_view->AddChildView(alert_view);
389
390 // Descendant should be ignored because the parent uses OverrideIsLeaf().
391 View* ignored_descendant = new View;
392 alert_view->AddChildView(ignored_descendant);
393
394 ComPtr<IAccessible> content_accessible(
395 contents_view->GetNativeViewAccessible());
396 ScopedVariant child_index(1);
397
398 // Role.
399 ScopedVariant role;
400 EXPECT_EQ(S_OK, content_accessible->get_accRole(child_index, role.Receive()));
401 EXPECT_EQ(VT_I4, role.type());
402 EXPECT_EQ(ROLE_SYSTEM_ALERT, V_I4(role.ptr()));
403
404 // Name.
405 ScopedBstr name;
406 ASSERT_EQ(S_OK, content_accessible->get_accName(child_index, name.Receive()));
407 ASSERT_STREQ(L"Name", name.Get());
408
409 // Description.
410 ScopedBstr description;
411 ASSERT_EQ(S_OK, content_accessible->get_accDescription(
412 child_index, description.Receive()));
413 ASSERT_STREQ(L"Description", description.Get());
414
415 // Get the child accessible.
416 ComPtr<IDispatch> alert_dispatch;
417 ComPtr<IAccessible> alert_accessible;
418 ASSERT_EQ(S_OK,
419 content_accessible->get_accChild(child_index, &alert_dispatch));
420 ASSERT_EQ(S_OK, alert_dispatch.As(&alert_accessible));
421
422 // Child accessible is a leaf.
423 LONG child_count = 0;
424 ASSERT_EQ(S_OK, alert_accessible->get_accChildCount(&child_count));
425 ASSERT_EQ(0, child_count);
426
427 ComPtr<IDispatch> child_dispatch;
428 ASSERT_EQ(E_INVALIDARG,
429 alert_accessible->get_accChild(child_index, &child_dispatch));
430 ASSERT_EQ(child_dispatch.Get(), nullptr);
431 }
432
TEST_F(ViewAXPlatformNodeDelegateWinTest,GridRowColumnCount)433 TEST_F(ViewAXPlatformNodeDelegateWinTest, GridRowColumnCount) {
434 Widget widget;
435 Widget::InitParams init_params = CreateParams(Widget::InitParams::TYPE_POPUP);
436 init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
437 widget.Init(std::move(init_params));
438
439 View* content = widget.SetContentsView(std::make_unique<View>());
440 TestListGridView* grid = new TestListGridView();
441 content->AddChildView(grid);
442
443 Microsoft::WRL::ComPtr<IGridProvider> grid_provider;
444 EXPECT_HRESULT_SUCCEEDED(
445 grid->GetViewAccessibility().GetNativeObject()->QueryInterface(
446 __uuidof(IGridProvider), &grid_provider));
447
448 // If set, aria row/column count takes precedence over table row/column count.
449 // Expect E_UNEXPECTED if the result is kUnknownAriaColumnOrRowCount (-1) or
450 // if neither is set.
451 int row_count;
452 int column_count;
453
454 // aria row/column count = not set
455 // table row/column count = not set
456 grid->UnsetAriaTableSize();
457 grid->UnsetTableSize();
458 EXPECT_HRESULT_SUCCEEDED(grid_provider->get_RowCount(&row_count));
459 EXPECT_HRESULT_SUCCEEDED(grid_provider->get_ColumnCount(&column_count));
460 EXPECT_EQ(0, row_count);
461 EXPECT_EQ(0, column_count);
462 // To do still: When nothing is set, currently
463 // AXPlatformNodeDelegateBase::GetTable{Row/Col}Count() returns 0 Should it
464 // return base::nullopt if the attribute is not set? Like
465 // GetTableAria{Row/Col}Count()
466 // EXPECT_EQ(E_UNEXPECTED, grid_provider->get_RowCount(&row_count));
467
468 // aria row/column count = 2
469 // table row/column count = not set
470 grid->SetAriaTableSize(2, 2);
471 EXPECT_HRESULT_SUCCEEDED(grid_provider->get_RowCount(&row_count));
472 EXPECT_HRESULT_SUCCEEDED(grid_provider->get_ColumnCount(&column_count));
473 EXPECT_EQ(2, row_count);
474 EXPECT_EQ(2, column_count);
475
476 // aria row/column count = kUnknownAriaColumnOrRowCount
477 // table row/column count = not set
478 grid->SetAriaTableSize(ax::mojom::kUnknownAriaColumnOrRowCount,
479 ax::mojom::kUnknownAriaColumnOrRowCount);
480 EXPECT_EQ(E_UNEXPECTED, grid_provider->get_RowCount(&row_count));
481 EXPECT_EQ(E_UNEXPECTED, grid_provider->get_ColumnCount(&column_count));
482
483 // aria row/column count = 3
484 // table row/column count = 4
485 grid->SetAriaTableSize(3, 3);
486 grid->SetTableSize(4, 4);
487 EXPECT_HRESULT_SUCCEEDED(grid_provider->get_RowCount(&row_count));
488 EXPECT_HRESULT_SUCCEEDED(grid_provider->get_ColumnCount(&column_count));
489 EXPECT_EQ(3, row_count);
490 EXPECT_EQ(3, column_count);
491
492 // aria row/column count = not set
493 // table row/column count = 4
494 grid->UnsetAriaTableSize();
495 grid->SetTableSize(4, 4);
496 EXPECT_HRESULT_SUCCEEDED(grid_provider->get_RowCount(&row_count));
497 EXPECT_HRESULT_SUCCEEDED(grid_provider->get_ColumnCount(&column_count));
498 EXPECT_EQ(4, row_count);
499 EXPECT_EQ(4, column_count);
500
501 // aria row/column count = not set
502 // table row/column count = kUnknownAriaColumnOrRowCount
503 grid->SetTableSize(ax::mojom::kUnknownAriaColumnOrRowCount,
504 ax::mojom::kUnknownAriaColumnOrRowCount);
505 EXPECT_EQ(E_UNEXPECTED, grid_provider->get_RowCount(&row_count));
506 EXPECT_EQ(E_UNEXPECTED, grid_provider->get_ColumnCount(&column_count));
507 }
508
TEST_F(ViewAXPlatformNodeDelegateWinTest,IsUIAControlIsTrueEvenWhenReadonly)509 TEST_F(ViewAXPlatformNodeDelegateWinTest, IsUIAControlIsTrueEvenWhenReadonly) {
510 // This test ensures that the value returned by
511 // AXPlatformNodeWin::IsUIAControl returns true even if the element is
512 // read-only. The previous implementation was incorrect and used to return
513 // false for read-only views, causing all sorts of issues with ATs.
514 //
515 // Since we can't test IsUIAControl directly, we go through the
516 // UIA_IsControlElementPropertyId, which is computed using IsUIAControl.
517
518 Widget widget;
519 Widget::InitParams init_params = CreateParams(Widget::InitParams::TYPE_POPUP);
520 init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
521 widget.Init(std::move(init_params));
522
523 View* content = widget.SetContentsView(std::make_unique<View>());
524
525 Textfield* text_field = new Textfield();
526 text_field->SetReadOnly(true);
527 content->AddChildView(text_field);
528
529 ComPtr<IRawElementProviderSimple> textfield_provider =
530 GetIRawElementProviderSimple(text_field);
531 EXPECT_UIA_BOOL_EQ(textfield_provider, UIA_IsControlElementPropertyId, true);
532 }
533 } // namespace test
534 } // namespace views
535