| // Copyright 2019 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/accessibility/platform/ax_platform_node_win.h" |
| |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/win/scoped_variant.h" |
| #include "content/browser/accessibility/accessibility_content_browsertest.h" |
| #include "content/browser/renderer_host/render_widget_host_view_aura.h" |
| #include "content/browser/web_contents/web_contents_impl.h" |
| #include "content/public/test/accessibility_notification_waiter.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "content/public/test/content_browser_test.h" |
| #include "content/public/test/content_browser_test_utils.h" |
| #include "content/shell/browser/shell.h" |
| #include "testing/gmock/include/gmock/gmock-matchers.h" |
| #include "ui/accessibility/accessibility_features.h" |
| #include "ui/accessibility/platform/browser_accessibility.h" |
| #include "ui/accessibility/platform/browser_accessibility_com_win.h" |
| #include "ui/accessibility/platform/uia_registrar_win.h" |
| |
| using base::win::ScopedVariant; |
| using Microsoft::WRL::ComPtr; |
| |
| namespace content { |
| |
| #define EXPECT_UIA_INT_EQ(node, property_id, expected) \ |
| { \ |
| base::win::ScopedVariant expectedVariant(expected); \ |
| ASSERT_EQ(VT_I4, expectedVariant.type()); \ |
| base::win::ScopedVariant actual; \ |
| ASSERT_HRESULT_SUCCEEDED( \ |
| node->GetPropertyValue(property_id, actual.Receive())); \ |
| EXPECT_EQ(expectedVariant.ptr()->intVal, actual.ptr()->intVal); \ |
| } |
| |
| #define EXPECT_UIA_BSTR_EQ(node, property_id, expected) \ |
| { \ |
| ScopedVariant expectedVariant(expected); \ |
| ASSERT_EQ(VT_BSTR, expectedVariant.type()); \ |
| ASSERT_NE(nullptr, expectedVariant.ptr()->bstrVal); \ |
| ScopedVariant actual; \ |
| ASSERT_HRESULT_SUCCEEDED( \ |
| node->GetPropertyValue(property_id, actual.Receive())); \ |
| ASSERT_EQ(VT_BSTR, actual.type()); \ |
| ASSERT_NE(nullptr, actual.ptr()->bstrVal); \ |
| EXPECT_STREQ(expectedVariant.ptr()->bstrVal, actual.ptr()->bstrVal); \ |
| } |
| |
| class AXPlatformNodeWinBrowserTest : public AccessibilityContentBrowserTest { |
| protected: |
| template <typename T> |
| ComPtr<T> QueryInterfaceFromNode( |
| ui::BrowserAccessibility* browser_accessibility) { |
| ComPtr<T> result; |
| EXPECT_HRESULT_SUCCEEDED( |
| browser_accessibility->GetNativeViewAccessible()->QueryInterface( |
| __uuidof(T), &result)); |
| return result; |
| } |
| |
| ComPtr<IAccessible> IAccessibleFromNode( |
| ui::BrowserAccessibility* browser_accessibility) { |
| return QueryInterfaceFromNode<IAccessible>(browser_accessibility); |
| } |
| |
| ComPtr<IAccessible2> ToIAccessible2(ComPtr<IAccessible> accessible) { |
| CHECK(accessible); |
| ComPtr<IServiceProvider> service_provider; |
| accessible.As(&service_provider); |
| ComPtr<IAccessible2> result; |
| CHECK(SUCCEEDED(service_provider->QueryService(IID_IAccessible2, |
| IID_PPV_ARGS(&result)))); |
| return result; |
| } |
| |
| ui::BrowserAccessibility* FindNodeAfter(ui::BrowserAccessibility* begin, |
| const std::string& name) { |
| WebContentsImpl* web_contents = |
| static_cast<WebContentsImpl*>(shell()->web_contents()); |
| ui::BrowserAccessibilityManager* manager = |
| web_contents->GetRootBrowserAccessibilityManager(); |
| ui::BrowserAccessibility* node = begin; |
| while (node && (node->GetName() != name)) { |
| node = manager->NextInTreeOrder(node); |
| } |
| |
| return node; |
| } |
| |
| void UIAGetPropertyValueFlowsFromBrowserTestTemplate( |
| const ui::BrowserAccessibility* target_browser_accessibility, |
| const std::vector<std::string>& expected_names) { |
| ASSERT_NE(nullptr, target_browser_accessibility); |
| |
| auto* target_browser_accessibility_com_win = |
| ToBrowserAccessibilityWin(target_browser_accessibility)->GetCOM(); |
| ASSERT_NE(nullptr, target_browser_accessibility_com_win); |
| |
| base::win::ScopedVariant flows_from_variant; |
| target_browser_accessibility_com_win->GetPropertyValue( |
| UIA_FlowsFromPropertyId, flows_from_variant.Receive()); |
| ASSERT_EQ(VT_ARRAY | VT_UNKNOWN, flows_from_variant.type()); |
| ASSERT_EQ(1u, SafeArrayGetDim(V_ARRAY(flows_from_variant.ptr()))); |
| |
| LONG lower_bound, upper_bound, size; |
| ASSERT_HRESULT_SUCCEEDED( |
| SafeArrayGetLBound(V_ARRAY(flows_from_variant.ptr()), 1, &lower_bound)); |
| ASSERT_HRESULT_SUCCEEDED( |
| SafeArrayGetUBound(V_ARRAY(flows_from_variant.ptr()), 1, &upper_bound)); |
| size = upper_bound - lower_bound + 1; |
| ASSERT_EQ(static_cast<LONG>(expected_names.size()), size); |
| |
| std::vector<std::string> names; |
| for (LONG i = 0; i < size; ++i) { |
| ComPtr<IUnknown> unknown_element; |
| ASSERT_HRESULT_SUCCEEDED( |
| SafeArrayGetElement(V_ARRAY(flows_from_variant.ptr()), &i, |
| static_cast<void**>(&unknown_element))); |
| ASSERT_NE(nullptr, unknown_element); |
| |
| ComPtr<IRawElementProviderSimple> raw_element_provider_simple = nullptr; |
| ASSERT_HRESULT_SUCCEEDED( |
| unknown_element.As(&raw_element_provider_simple)); |
| ASSERT_NE(nullptr, raw_element_provider_simple); |
| |
| base::win::ScopedVariant name; |
| ASSERT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPropertyValue( |
| UIA_NamePropertyId, name.Receive())); |
| ASSERT_EQ(VT_BSTR, name.type()); |
| names.push_back(base::WideToUTF8( |
| std::wstring(V_BSTR(name.ptr()), SysStringLen(V_BSTR(name.ptr()))))); |
| } |
| |
| ASSERT_THAT(names, ::testing::UnorderedElementsAreArray(expected_names)); |
| } |
| |
| void UIAIWindowProviderGetIsModalBrowserTestTemplate( |
| ax::mojom::Role expected_role, |
| ui::BrowserAccessibility* (ui::BrowserAccessibility::*f)(size_t) const, |
| size_t index_arg, |
| bool expected_is_modal, |
| bool expected_is_window_provider_available) { |
| ui::BrowserAccessibility* root_browser_accessibility = |
| GetRootAndAssertNonNull(); |
| ui::BrowserAccessibilityComWin* root_browser_accessibility_com_win = |
| ToBrowserAccessibilityWin(root_browser_accessibility)->GetCOM(); |
| ASSERT_NE(nullptr, root_browser_accessibility_com_win); |
| |
| ui::BrowserAccessibility* browser_accessibility = |
| (root_browser_accessibility->*f)(index_arg); |
| ASSERT_NE(nullptr, browser_accessibility); |
| ASSERT_EQ(expected_role, browser_accessibility->GetRole()); |
| ui::BrowserAccessibilityComWin* browser_accessibility_com_win = |
| ToBrowserAccessibilityWin(browser_accessibility)->GetCOM(); |
| ASSERT_NE(nullptr, browser_accessibility_com_win); |
| |
| ComPtr<IWindowProvider> window_provider = nullptr; |
| ASSERT_HRESULT_SUCCEEDED(browser_accessibility_com_win->GetPatternProvider( |
| UIA_WindowPatternId, &window_provider)); |
| if (expected_is_window_provider_available) { |
| ASSERT_NE(nullptr, window_provider.Get()); |
| |
| BOOL is_modal = FALSE; |
| ASSERT_HRESULT_SUCCEEDED(window_provider->get_IsModal(&is_modal)); |
| ASSERT_EQ(expected_is_modal, is_modal); |
| } else { |
| ASSERT_EQ(nullptr, window_provider.Get()); |
| } |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_{ |
| features::kEnableAccessibilityAriaVirtualContent}; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeWinBrowserTest, |
| IA2ScrollToPointIframeText) { |
| LoadInitialAccessibilityTreeFromHtmlFilePath( |
| "/accessibility/scrolling/iframe-text.html"); |
| WaitForAccessibilityTreeToContainNodeWithName( |
| shell()->web_contents(), |
| "Game theory is \"the study of Mathematical model mathematical models of " |
| "conflict and cooperation between intelligent rational decision-makers." |
| "\""); |
| |
| ui::BrowserAccessibility* browser_accessibility = |
| GetRootAndAssertNonNull()->PlatformDeepestLastChild(); |
| ASSERT_NE(nullptr, browser_accessibility); |
| ASSERT_EQ(ax::mojom::Role::kStaticText, browser_accessibility->GetRole()); |
| |
| ui::BrowserAccessibility* iframe_browser_accessibility = |
| browser_accessibility->manager()->GetBrowserAccessibilityRoot(); |
| ASSERT_NE(nullptr, iframe_browser_accessibility); |
| ASSERT_EQ(ax::mojom::Role::kRootWebArea, |
| iframe_browser_accessibility->GetRole()); |
| |
| gfx::Rect iframe_screen_bounds = iframe_browser_accessibility->GetBoundsRect( |
| ui::AXCoordinateSystem::kScreenDIPs, ui::AXClippingBehavior::kUnclipped); |
| |
| AccessibilityNotificationWaiter location_changed_waiter( |
| shell()->web_contents(), ax::mojom::Event::kLocationChanged); |
| ComPtr<IAccessible2> root_iaccessible2 = |
| ToIAccessible2(IAccessibleFromNode(browser_accessibility)); |
| ASSERT_EQ(S_OK, root_iaccessible2->scrollToPoint( |
| IA2_COORDTYPE_SCREEN_RELATIVE, iframe_screen_bounds.x(), |
| iframe_screen_bounds.y())); |
| ASSERT_TRUE(location_changed_waiter.WaitForNotification()); |
| |
| gfx::Rect bounds = browser_accessibility->GetBoundsRect( |
| ui::AXCoordinateSystem::kScreenDIPs, ui::AXClippingBehavior::kUnclipped); |
| ASSERT_EQ(iframe_screen_bounds.y(), bounds.y()); |
| } |
| |
| class AXPlatformNodeWinUIABrowserTest : public AXPlatformNodeWinBrowserTest { |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_{::features::kUiaProvider}; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeWinUIABrowserTest, |
| UIAGetPropertyValueFlowsFromNone) { |
| LoadInitialAccessibilityTreeFromHtmlFilePath( |
| "/accessibility/aria/aria-label.html"); |
| |
| base::win::ScopedVariant flows_from_variant; |
| ComPtr<IRawElementProviderSimple> node_provider = |
| QueryInterfaceFromNode<IRawElementProviderSimple>( |
| FindNode(ax::mojom::Role::kCheckBox, "aria label")); |
| node_provider->GetPropertyValue(UIA_FlowsFromPropertyId, |
| flows_from_variant.Receive()); |
| ASSERT_EQ(VT_ARRAY | VT_UNKNOWN, flows_from_variant.type()); |
| ASSERT_EQ(nullptr, V_ARRAY(flows_from_variant.ptr())); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeWinUIABrowserTest, |
| UIAGetPropertyValueWebContentsHistogram) { |
| LoadInitialAccessibilityTreeFromHtml(std::string(R"HTML( |
| <!DOCTYPE html> |
| <html> |
| <p>Hello World</p> |
| </html> |
| )HTML")); |
| |
| base::HistogramTester histogram_tester; |
| base::win::ScopedVariant property_value; |
| ComPtr<IRawElementProviderSimple> node_provider = |
| QueryInterfaceFromNode<IRawElementProviderSimple>( |
| FindNode(ax::mojom::Role::kStaticText, "Hello World")); |
| |
| histogram_tester.ExpectTotalCount( |
| "Accessibility.Performance.WinAPIs2.WebContents.UMA_API_GET_PROPERTY_" |
| "VALUE", |
| 0); |
| |
| node_provider->GetPropertyValue(UIA_NamePropertyId, property_value.Receive()); |
| |
| histogram_tester.ExpectTotalCount( |
| "Accessibility.Performance.WinAPIs2.WebContents.UMA_API_GET_PROPERTY_" |
| "VALUE", |
| 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeWinUIABrowserTest, |
| UIAGetPropertyValueFlowsFromSingle) { |
| LoadInitialAccessibilityTreeFromHtmlFilePath( |
| "/accessibility/aria/aria-flowto.html"); |
| |
| UIAGetPropertyValueFlowsFromBrowserTestTemplate( |
| FindNode(ax::mojom::Role::kFooter, "next"), {"current"}); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeWinUIABrowserTest, |
| UIAGetPropertyValueFlowsFromMultiple) { |
| LoadInitialAccessibilityTreeFromHtmlFilePath( |
| "/accessibility/aria/aria-flowto-multiple.html"); |
| |
| UIAGetPropertyValueFlowsFromBrowserTestTemplate( |
| FindNode(ax::mojom::Role::kGenericContainer, "b3"), {"a3", "c3"}); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeWinUIABrowserTest, UIANamePropertyValue) { |
| LoadInitialAccessibilityTreeFromHtml(std::string(R"HTML( |
| <!DOCTYPE html> |
| <html> |
| <ol> |
| <li>list item 1</li> |
| <li></li> |
| <li>before <div><span>problem</span></div>, after</li> |
| <li>before <a href="https://blah.com">problem</a>, after</li> |
| <li aria-label="from author">from content</li> |
| </ol> |
| </html> |
| )HTML")); |
| ui::BrowserAccessibility* list_node = |
| GetRootAndAssertNonNull()->PlatformGetChild(0); |
| ui::BrowserAccessibility* item_node = list_node->PlatformGetChild(0); |
| ASSERT_NE(nullptr, item_node); |
| EXPECT_UIA_BSTR_EQ(ToBrowserAccessibilityWin(item_node)->GetCOM(), |
| UIA_NamePropertyId, L"list item 1"); |
| |
| // Empty string as name should correspond to empty <li>. |
| item_node = list_node->PlatformGetChild(1); |
| ASSERT_NE(nullptr, item_node); |
| EXPECT_UIA_BSTR_EQ(ToBrowserAccessibilityWin(item_node)->GetCOM(), |
| UIA_NamePropertyId, L""); |
| |
| // <li> with complex structure and text. |
| item_node = list_node->PlatformGetChild(2); |
| ASSERT_NE(nullptr, item_node); |
| EXPECT_UIA_BSTR_EQ(ToBrowserAccessibilityWin(item_node)->GetCOM(), |
| UIA_NamePropertyId, L"beforeproblem, after"); |
| |
| // <li> with a link inside |
| item_node = list_node->PlatformGetChild(3); |
| ASSERT_NE(nullptr, item_node); |
| EXPECT_UIA_BSTR_EQ(ToBrowserAccessibilityWin(item_node)->GetCOM(), |
| UIA_NamePropertyId, L"before problem, after"); |
| |
| // <li> with name specified by the author rather than by the contents. |
| item_node = list_node->PlatformGetChild(4); |
| ASSERT_NE(nullptr, item_node); |
| EXPECT_UIA_BSTR_EQ(ToBrowserAccessibilityWin(item_node)->GetCOM(), |
| UIA_NamePropertyId, L"from author"); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeWinUIABrowserTest, |
| UIAIWindowProviderGetIsModalOnDialog) { |
| LoadInitialAccessibilityTreeFromHtml(std::string(R"HTML( |
| <!DOCTYPE html> |
| <html> |
| <body role="none"> |
| <dialog open>Example Text</dialog> |
| </body> |
| </html> |
| )HTML")); |
| |
| UIAIWindowProviderGetIsModalBrowserTestTemplate( |
| ax::mojom::Role::kDialog, &ui::BrowserAccessibility::PlatformGetChild, 0, |
| false, true); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeWinUIABrowserTest, |
| UIAIWindowProviderGetIsModalOnDialogAriaModalFalse) { |
| LoadInitialAccessibilityTreeFromHtml(std::string(R"HTML( |
| <!DOCTYPE html> |
| <html> |
| <body role="none"> |
| <dialog open aria-modal="false">Example Text</dialog> |
| </body> |
| </html> |
| )HTML")); |
| |
| UIAIWindowProviderGetIsModalBrowserTestTemplate( |
| ax::mojom::Role::kDialog, &ui::BrowserAccessibility::PlatformGetChild, 0, |
| false, true); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeWinUIABrowserTest, |
| UIAIWindowProviderGetIsModalOnDialogAriaModalTrue) { |
| LoadInitialAccessibilityTreeFromHtml(std::string(R"HTML( |
| <!DOCTYPE html> |
| <html> |
| <body role="none"> |
| <dialog open aria-modal="true">Example Text</dialog> |
| </body> |
| </html> |
| )HTML")); |
| |
| UIAIWindowProviderGetIsModalBrowserTestTemplate( |
| ax::mojom::Role::kDialog, &ui::BrowserAccessibility::PlatformGetChild, 0, |
| true, true); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeWinUIABrowserTest, |
| UIAIWindowProviderGetIsModalOnDiv) { |
| LoadInitialAccessibilityTreeFromHtml(std::string(R"HTML( |
| <!DOCTYPE html> |
| <html> |
| <body> |
| <div>Example Text</div> |
| </body> |
| </html> |
| )HTML")); |
| |
| UIAIWindowProviderGetIsModalBrowserTestTemplate( |
| ax::mojom::Role::kGenericContainer, |
| &ui::BrowserAccessibility::PlatformGetChild, 0, false, false); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeWinUIABrowserTest, |
| UIAIWindowProviderGetIsModalOnDivAriaModalFalse) { |
| LoadInitialAccessibilityTreeFromHtml(std::string(R"HTML( |
| <!DOCTYPE html> |
| <html> |
| <body> |
| <div aria-modal="false">Example Text</div> |
| </body> |
| </html> |
| )HTML")); |
| |
| UIAIWindowProviderGetIsModalBrowserTestTemplate( |
| ax::mojom::Role::kGenericContainer, |
| &ui::BrowserAccessibility::PlatformGetChild, 0, false, false); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeWinUIABrowserTest, |
| UIAIWindowProviderGetIsModalOnDivAriaModalTrue) { |
| LoadInitialAccessibilityTreeFromHtml(std::string(R"HTML( |
| <!DOCTYPE html> |
| <html> |
| <body> |
| <div aria-modal="true">Example Text</div> |
| </body> |
| </html> |
| )HTML")); |
| |
| UIAIWindowProviderGetIsModalBrowserTestTemplate( |
| ax::mojom::Role::kGenericContainer, |
| &ui::BrowserAccessibility::PlatformGetChild, 0, false, false); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeWinUIABrowserTest, |
| UIAIWindowProviderGetIsModalOnDivDialog) { |
| LoadInitialAccessibilityTreeFromHtml(std::string(R"HTML( |
| <!DOCTYPE html> |
| <html> |
| <body> |
| <div role="dialog">Example Text</div> |
| </body> |
| </html> |
| )HTML")); |
| |
| UIAIWindowProviderGetIsModalBrowserTestTemplate( |
| ax::mojom::Role::kDialog, &ui::BrowserAccessibility::PlatformGetChild, 0, |
| false, true); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeWinUIABrowserTest, |
| UIAIWindowProviderGetIsModalOnDivDialogAriaModalFalse) { |
| LoadInitialAccessibilityTreeFromHtml(std::string(R"HTML( |
| <!DOCTYPE html> |
| <html> |
| <body> |
| <div role="dialog" aria-modal="false">Example Text</div> |
| </body> |
| </html> |
| )HTML")); |
| |
| UIAIWindowProviderGetIsModalBrowserTestTemplate( |
| ax::mojom::Role::kDialog, &ui::BrowserAccessibility::PlatformGetChild, 0, |
| false, true); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeWinUIABrowserTest, |
| UIAIWindowProviderGetIsModalOnDivDialogAriaModalTrue) { |
| LoadInitialAccessibilityTreeFromHtml(std::string(R"HTML( |
| <!DOCTYPE html> |
| <html> |
| <body> |
| <div role="dialog" aria-modal="true">Example Text</div> |
| </body> |
| </html> |
| )HTML")); |
| |
| UIAIWindowProviderGetIsModalBrowserTestTemplate( |
| ax::mojom::Role::kDialog, &ui::BrowserAccessibility::PlatformGetChild, 0, |
| true, true); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeWinUIABrowserTest, |
| UIAIWindowProviderGetIsModalOnDivAlertDialog) { |
| LoadInitialAccessibilityTreeFromHtml(std::string(R"HTML( |
| <!DOCTYPE html> |
| <html> |
| <body> |
| <div role="alertdialog">Example Text</div> |
| </body> |
| </html> |
| )HTML")); |
| |
| UIAIWindowProviderGetIsModalBrowserTestTemplate( |
| ax::mojom::Role::kAlertDialog, |
| &ui::BrowserAccessibility::PlatformGetChild, 0, false, true); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| AXPlatformNodeWinUIABrowserTest, |
| UIAIWindowProviderGetIsModalOnDivAlertDialogAriaModalFalse) { |
| LoadInitialAccessibilityTreeFromHtml(std::string(R"HTML( |
| <!DOCTYPE html> |
| <html> |
| <body> |
| <div role="alertdialog" aria-modal="false">Example Text</div> |
| </body> |
| </html> |
| )HTML")); |
| |
| UIAIWindowProviderGetIsModalBrowserTestTemplate( |
| ax::mojom::Role::kAlertDialog, |
| &ui::BrowserAccessibility::PlatformGetChild, 0, false, true); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| AXPlatformNodeWinUIABrowserTest, |
| UIAIWindowProviderGetIsModalOnDivAlertDialogAriaModalTrue) { |
| LoadInitialAccessibilityTreeFromHtml(std::string(R"HTML( |
| <!DOCTYPE html> |
| <html> |
| <body> |
| <div role="alertdialog" aria-modal="true">Example Text</div> |
| </body> |
| </html> |
| )HTML")); |
| |
| UIAIWindowProviderGetIsModalBrowserTestTemplate( |
| ax::mojom::Role::kAlertDialog, |
| &ui::BrowserAccessibility::PlatformGetChild, 0, true, true); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeWinUIABrowserTest, |
| UIAGetPropertyValueAutomationId) { |
| LoadInitialAccessibilityTreeFromHtml(std::string(R"HTML( |
| <!DOCTYPE html> |
| <html> |
| <body> |
| <div id="id"></div> |
| </body> |
| </html> |
| )HTML")); |
| |
| ui::BrowserAccessibility* root_browser_accessibility = |
| GetRootAndAssertNonNull(); |
| ui::BrowserAccessibilityComWin* root_browser_accessibility_com_win = |
| ToBrowserAccessibilityWin(root_browser_accessibility)->GetCOM(); |
| ASSERT_NE(nullptr, root_browser_accessibility_com_win); |
| |
| ui::BrowserAccessibility* browser_accessibility = |
| root_browser_accessibility->PlatformDeepestLastChild(); |
| ASSERT_NE(nullptr, browser_accessibility); |
| ASSERT_EQ(ax::mojom::Role::kGenericContainer, |
| browser_accessibility->GetRole()); |
| ui::BrowserAccessibilityComWin* browser_accessibility_com_win = |
| ToBrowserAccessibilityWin(browser_accessibility)->GetCOM(); |
| ASSERT_NE(nullptr, browser_accessibility_com_win); |
| |
| base::win::ScopedVariant expected_scoped_variant; |
| expected_scoped_variant.Set(SysAllocString(L"id")); |
| base::win::ScopedVariant scoped_variant; |
| EXPECT_HRESULT_SUCCEEDED(browser_accessibility_com_win->GetPropertyValue( |
| UIA_AutomationIdPropertyId, scoped_variant.Receive())); |
| EXPECT_EQ(0, expected_scoped_variant.Compare(scoped_variant)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeWinUIABrowserTest, |
| UIAGetPropertyValueNonEmptyAutomationIdOnRootWebArea) { |
| LoadInitialAccessibilityTreeFromHtml(std::string(R"HTML( |
| <!DOCTYPE html> |
| <html> |
| <body> |
| <button></button> |
| </body> |
| </html> |
| )HTML")); |
| |
| ui::BrowserAccessibility* root_browser_accessibility = |
| GetRootAndAssertNonNull(); |
| ASSERT_NE(nullptr, root_browser_accessibility); |
| ASSERT_EQ(ax::mojom::Role::kRootWebArea, |
| root_browser_accessibility->GetRole()); |
| |
| ui::BrowserAccessibilityComWin* root_browser_accessibility_com_win = |
| ToBrowserAccessibilityWin(root_browser_accessibility)->GetCOM(); |
| ASSERT_NE(nullptr, root_browser_accessibility_com_win); |
| |
| // kRootWebArea nodes should not be empty. Some UIA clients appear to rely on |
| // whether it's empty or not. See https://crbug.com/40065516#comment32. |
| base::win::ScopedVariant expected_scoped_variant; |
| expected_scoped_variant.Set(SysAllocString(L"RootWebArea")); |
| base::win::ScopedVariant scoped_variant; |
| EXPECT_HRESULT_SUCCEEDED(root_browser_accessibility_com_win->GetPropertyValue( |
| UIA_AutomationIdPropertyId, scoped_variant.Receive())); |
| EXPECT_EQ(0, expected_scoped_variant.Compare(scoped_variant)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeWinUIABrowserTest, UIAScrollIntoView) { |
| LoadInitialAccessibilityTreeFromHtml(std::string(R"HTML( |
| <!DOCTYPE html> |
| <html> |
| <body> |
| <div id="id" style="height: 20px; overflow: scroll;"> |
| <ul> |
| <li>Item 1</li> |
| <li>Item 2</li> |
| <li>Item 3</li> |
| <li>Item 4</li> |
| <li>Item 5</li> |
| <li>Item 6</li> |
| </ul> |
| </div> |
| </body> |
| </html> |
| )HTML")); |
| |
| ui::BrowserAccessibility* root_browser_accessibility = |
| GetRootAndAssertNonNull(); |
| ui::BrowserAccessibilityComWin* root_browser_accessibility_com_win = |
| ToBrowserAccessibilityWin(root_browser_accessibility)->GetCOM(); |
| ASSERT_NE(nullptr, root_browser_accessibility_com_win); |
| |
| ui::BrowserAccessibility* browser_accessibility = |
| root_browser_accessibility->PlatformDeepestLastChild(); |
| ASSERT_NE(nullptr, browser_accessibility); |
| ASSERT_EQ(ax::mojom::Role::kStaticText, browser_accessibility->GetRole()); |
| ui::BrowserAccessibilityComWin* browser_accessibility_com_win = |
| ToBrowserAccessibilityWin(browser_accessibility)->GetCOM(); |
| ASSERT_NE(nullptr, browser_accessibility_com_win); |
| |
| ui::AXPlatformNodeWin* platform_node = static_cast<ui::AXPlatformNodeWin*>( |
| ui::AXPlatformNode::FromNativeViewAccessible( |
| browser_accessibility->GetNativeViewAccessible())); |
| ASSERT_NE(nullptr, platform_node); |
| |
| AccessibilityNotificationWaiter waiter(shell()->web_contents(), |
| ax::mojom::Event::kLocationChanged); |
| EXPECT_HRESULT_SUCCEEDED(platform_node->ScrollIntoView()); |
| ASSERT_TRUE(waiter.WaitForNotification()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeWinUIABrowserTest, |
| UIAGetPropertyValueCulture) { |
| LoadInitialAccessibilityTreeFromHtml(std::string(R"HTML( |
| <!DOCTYPE html> |
| <html> |
| <body> |
| <div lang='en-us'>en-us</div> |
| <div lang='en-gb'>en-gb</div> |
| <div lang='ru-ru'>ru-ru</div> |
| <div lang='fake'>fake</div> |
| <div>no lang</div> |
| <div lang=''>empty lang</div> |
| </body> |
| </html> |
| )HTML")); |
| |
| ui::BrowserAccessibility* root_node = GetRootAndAssertNonNull(); |
| ui::BrowserAccessibility* body_node = root_node->PlatformGetFirstChild(); |
| ASSERT_NE(nullptr, body_node); |
| |
| ui::BrowserAccessibility* node = FindNodeAfter(body_node, "en-us"); |
| ASSERT_NE(nullptr, node); |
| ui::BrowserAccessibilityComWin* en_us_node_com_win = |
| ToBrowserAccessibilityWin(node)->GetCOM(); |
| ASSERT_NE(nullptr, en_us_node_com_win); |
| constexpr int en_us_lcid = 1033; |
| EXPECT_UIA_INT_EQ(en_us_node_com_win, UIA_CulturePropertyId, en_us_lcid); |
| |
| node = FindNodeAfter(node, "en-gb"); |
| ASSERT_NE(nullptr, node); |
| ui::BrowserAccessibilityComWin* en_gb_node_com_win = |
| ToBrowserAccessibilityWin(node)->GetCOM(); |
| ASSERT_NE(nullptr, en_gb_node_com_win); |
| constexpr int en_gb_lcid = 2057; |
| EXPECT_UIA_INT_EQ(en_gb_node_com_win, UIA_CulturePropertyId, en_gb_lcid); |
| |
| node = FindNodeAfter(node, "ru-ru"); |
| ASSERT_NE(nullptr, node); |
| ui::BrowserAccessibilityComWin* ru_ru_node_com_win = |
| ToBrowserAccessibilityWin(node)->GetCOM(); |
| ASSERT_NE(nullptr, ru_ru_node_com_win); |
| constexpr int ru_ru_lcid = 1049; |
| EXPECT_UIA_INT_EQ(ru_ru_node_com_win, UIA_CulturePropertyId, ru_ru_lcid); |
| |
| // Setting to an invalid language should return a failed HRESULT. |
| node = FindNodeAfter(node, "fake"); |
| ASSERT_NE(nullptr, node); |
| ui::BrowserAccessibilityComWin* fake_lang_node_com_win = |
| ToBrowserAccessibilityWin(node)->GetCOM(); |
| ASSERT_NE(nullptr, fake_lang_node_com_win); |
| base::win::ScopedVariant actual_value; |
| EXPECT_HRESULT_FAILED(fake_lang_node_com_win->GetPropertyValue( |
| UIA_CulturePropertyId, actual_value.Receive())); |
| |
| // No lang should default to the page's default language (en-us). |
| node = FindNodeAfter(node, "no lang"); |
| ASSERT_NE(nullptr, node); |
| ui::BrowserAccessibilityComWin* no_lang_node_com_win = |
| ToBrowserAccessibilityWin(node)->GetCOM(); |
| ASSERT_NE(nullptr, no_lang_node_com_win); |
| EXPECT_UIA_INT_EQ(no_lang_node_com_win, UIA_CulturePropertyId, en_us_lcid); |
| |
| // Empty lang should default to the page's default language (en-us). |
| node = FindNodeAfter(node, "empty lang"); |
| ASSERT_NE(nullptr, node); |
| ui::BrowserAccessibilityComWin* empty_lang_node_com_win = |
| ToBrowserAccessibilityWin(node)->GetCOM(); |
| ASSERT_NE(nullptr, empty_lang_node_com_win); |
| EXPECT_UIA_INT_EQ(empty_lang_node_com_win, UIA_CulturePropertyId, en_us_lcid); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeWinUIABrowserTest, |
| UIAGetPropertyValueVirtualContent) { |
| LoadInitialAccessibilityTreeFromHtml(std::string(R"HTML( |
| <!DOCTYPE html> |
| <html> |
| <body> |
| <div role="group" aria-virtualcontent="block-end" |
| aria-label="vc">Hello World</div> |
| </body> |
| </html> |
| )HTML")); |
| |
| ui::BrowserAccessibility* root_node = GetRootAndAssertNonNull(); |
| ui::BrowserAccessibility* body_node = root_node->PlatformGetFirstChild(); |
| ASSERT_NE(nullptr, body_node); |
| |
| ui::BrowserAccessibility* node = FindNode(ax::mojom::Role::kGroup, "vc"); |
| ASSERT_NE(nullptr, node); |
| ui::BrowserAccessibilityComWin* node_com_win = |
| ToBrowserAccessibilityWin(node)->GetCOM(); |
| ASSERT_NE(nullptr, node_com_win); |
| |
| EXPECT_UIA_BSTR_EQ( |
| node_com_win, |
| ui::UiaRegistrarWin::GetInstance().GetVirtualContentPropertyId(), |
| L"block-end"); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeWinBrowserTest, |
| HitTestOnAncestorOfWebRoot) { |
| EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL))); |
| |
| // Load the page. |
| AccessibilityNotificationWaiter waiter(shell()->web_contents(), |
| ax::mojom::Event::kLoadComplete); |
| const char url_str[] = |
| "data:text/html," |
| "<!doctype html>" |
| "<html><head><title>Accessibility Test</title></head>" |
| "<body>" |
| "<button>This is a button</button>" |
| "</body></html>"; |
| GURL url(url_str); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| ASSERT_TRUE(waiter.WaitForNotification()); |
| |
| WebContentsImpl* web_contents = |
| static_cast<WebContentsImpl*>(shell()->web_contents()); |
| ui::BrowserAccessibilityManager* manager = |
| web_contents->GetRootBrowserAccessibilityManager(); |
| |
| // Find a node to hit test. Note that this is a really simple page, |
| // so synchronous hit testing will work fine. |
| ui::BrowserAccessibility* node = manager->GetBrowserAccessibilityRoot(); |
| while (node && node->GetRole() != ax::mojom::Role::kButton) { |
| node = manager->NextInTreeOrder(node); |
| } |
| DCHECK(node); |
| |
| // Get the screen bounds of the hit target and find the point in the middle. |
| gfx::Rect bounds = node->GetClippedScreenBoundsRect(); |
| gfx::Point point = bounds.CenterPoint(); |
| |
| // Get the root AXPlatformNodeWin. |
| ui::AXPlatformNodeWin* root_platform_node = |
| static_cast<ui::AXPlatformNodeWin*>( |
| ui::AXPlatformNode::FromNativeViewAccessible( |
| manager->GetBrowserAccessibilityRoot() |
| ->GetNativeViewAccessible())); |
| |
| // First test that calling accHitTest on the root node returns the button. |
| { |
| base::win::ScopedVariant hit_child_variant; |
| ASSERT_EQ(S_OK, root_platform_node->accHitTest( |
| point.x(), point.y(), hit_child_variant.Receive())); |
| ASSERT_EQ(VT_DISPATCH, hit_child_variant.type()); |
| ASSERT_NE(nullptr, hit_child_variant.ptr()); |
| ComPtr<IAccessible> accessible; |
| ASSERT_HRESULT_SUCCEEDED(V_DISPATCH(hit_child_variant.ptr()) |
| ->QueryInterface(IID_PPV_ARGS(&accessible))); |
| ui::AXPlatformNode* hit_child = |
| ui::AXPlatformNode::FromNativeViewAccessible(accessible.Get()); |
| ASSERT_NE(nullptr, hit_child); |
| EXPECT_EQ(node->GetId(), hit_child->GetDelegate()->GetData().id); |
| } |
| |
| // Now test it again, but this time caliing accHitTest on the parent |
| // IAccessible of the web root node. |
| { |
| RenderWidgetHostViewAura* rwhva = static_cast<RenderWidgetHostViewAura*>( |
| shell()->web_contents()->GetRenderWidgetHostView()); |
| IAccessible* ancestor = rwhva->GetParentNativeViewAccessible(); |
| |
| base::win::ScopedVariant hit_child_variant; |
| ASSERT_EQ(S_OK, ancestor->accHitTest(point.x(), point.y(), |
| hit_child_variant.Receive())); |
| ASSERT_EQ(VT_DISPATCH, hit_child_variant.type()); |
| ASSERT_NE(nullptr, hit_child_variant.ptr()); |
| ComPtr<IAccessible> accessible; |
| ASSERT_HRESULT_SUCCEEDED(V_DISPATCH(hit_child_variant.ptr()) |
| ->QueryInterface(IID_PPV_ARGS(&accessible))); |
| ui::AXPlatformNode* hit_child = |
| ui::AXPlatformNode::FromNativeViewAccessible(accessible.Get()); |
| ASSERT_NE(nullptr, hit_child); |
| EXPECT_EQ(node->GetId(), hit_child->GetDelegate()->GetData().id); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeWinBrowserTest, IFrameTraversal) { |
| LoadInitialAccessibilityTreeFromHtmlFilePath( |
| "/accessibility/html/iframe-traversal.html"); |
| WaitForAccessibilityTreeToContainNodeWithName(shell()->web_contents(), |
| "Text in iframe"); |
| |
| ui::BrowserAccessibility* root_node = GetRootAndAssertNonNull(); |
| ui::BrowserAccessibility* before_iframe_node = |
| FindNodeAfter(root_node, "Before iframe"); |
| ASSERT_NE(nullptr, before_iframe_node); |
| ASSERT_EQ(ax::mojom::Role::kStaticText, before_iframe_node->GetRole()); |
| |
| ASSERT_EQ(0U, before_iframe_node->PlatformChildCount()); |
| ASSERT_EQ(1U, before_iframe_node->InternalChildCount()); |
| before_iframe_node = before_iframe_node->InternalGetFirstChild(); |
| ASSERT_NE(nullptr, before_iframe_node); |
| ASSERT_EQ(ax::mojom::Role::kInlineTextBox, before_iframe_node->GetRole()); |
| |
| ui::BrowserAccessibility* inside_iframe_node = |
| FindNodeAfter(before_iframe_node, "Text in iframe"); |
| ASSERT_NE(nullptr, inside_iframe_node); |
| ASSERT_EQ(ax::mojom::Role::kStaticText, inside_iframe_node->GetRole()); |
| |
| ASSERT_EQ(0U, inside_iframe_node->PlatformChildCount()); |
| ASSERT_EQ(1U, inside_iframe_node->InternalChildCount()); |
| inside_iframe_node = inside_iframe_node->InternalGetFirstChild(); |
| ASSERT_NE(nullptr, inside_iframe_node); |
| ASSERT_EQ(ax::mojom::Role::kInlineTextBox, inside_iframe_node->GetRole()); |
| |
| ui::BrowserAccessibility* after_iframe_node = |
| FindNodeAfter(inside_iframe_node, "After iframe"); |
| ASSERT_NE(nullptr, after_iframe_node); |
| ASSERT_EQ(ax::mojom::Role::kStaticText, after_iframe_node->GetRole()); |
| |
| ASSERT_EQ(0U, after_iframe_node->PlatformChildCount()); |
| ASSERT_EQ(1U, after_iframe_node->InternalChildCount()); |
| after_iframe_node = after_iframe_node->InternalGetFirstChild(); |
| ASSERT_NE(nullptr, after_iframe_node); |
| ASSERT_EQ(ax::mojom::Role::kInlineTextBox, after_iframe_node->GetRole()); |
| |
| EXPECT_LT(*before_iframe_node->CreateTextPositionAt(0), |
| *inside_iframe_node->CreateTextPositionAt(0)); |
| // The following positions should not be equivalent because they are on two |
| // separate lines in the accessibility tree's text representation, i.e. the |
| // first has an upstream affinity while the second has a downstream affinity. |
| // Note that an iframe boundary is also a line boundary. |
| EXPECT_LT(*before_iframe_node->CreateTextPositionAt(13), |
| *inside_iframe_node->CreateTextPositionAt(0)); |
| EXPECT_LT(*inside_iframe_node->CreateTextPositionAt(0), |
| *after_iframe_node->CreateTextPositionAt(0)); |
| // The following positions should not be equivalent because they are on two |
| // separate lines in the accessibility tree's text representation, i.e. the |
| // first has an upstream affinity while the second has a downstream affinity. |
| // Note that an iframe boundary is also a line boundary. |
| EXPECT_LT(*inside_iframe_node->CreateTextPositionAt(14), |
| *after_iframe_node->CreateTextPositionAt(0)); |
| |
| // Traverse the leaves of the AXTree forwards. |
| ui::BrowserAccessibility::AXPosition tree_position = |
| root_node->CreateTextPositionAt(0)->CreateNextLeafTreePosition(); |
| EXPECT_TRUE(tree_position->IsTreePosition()); |
| EXPECT_EQ(before_iframe_node->node(), tree_position->GetAnchor()); |
| tree_position = tree_position->CreateNextLeafTreePosition(); |
| EXPECT_TRUE(tree_position->IsTreePosition()); |
| EXPECT_EQ(inside_iframe_node->node(), tree_position->GetAnchor()); |
| tree_position = tree_position->CreateNextLeafTreePosition(); |
| EXPECT_TRUE(tree_position->IsTreePosition()); |
| EXPECT_EQ(after_iframe_node->node(), tree_position->GetAnchor()); |
| tree_position = tree_position->CreateNextLeafTreePosition(); |
| EXPECT_TRUE(tree_position->IsNullPosition()); |
| |
| // Traverse the leaves of the AXTree backwards. |
| tree_position = after_iframe_node->CreateTextPositionAt(0) |
| ->CreatePositionAtEndOfAnchor() |
| ->AsLeafTreePosition(); |
| EXPECT_TRUE(tree_position->IsTreePosition()); |
| EXPECT_EQ(after_iframe_node->node(), tree_position->GetAnchor()); |
| tree_position = tree_position->CreatePreviousLeafTreePosition(); |
| EXPECT_TRUE(tree_position->IsTreePosition()); |
| EXPECT_EQ(inside_iframe_node->node(), tree_position->GetAnchor()); |
| tree_position = tree_position->CreatePreviousLeafTreePosition(); |
| EXPECT_TRUE(tree_position->IsTreePosition()); |
| EXPECT_EQ(before_iframe_node->node(), tree_position->GetAnchor()); |
| tree_position = tree_position->CreatePreviousLeafTreePosition(); |
| EXPECT_TRUE(tree_position->IsNullPosition()); |
| } |
| |
| } // namespace content |