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