| // 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_textrangeprovider_win.h" |
| |
| #include "base/command_line.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/win/scoped_bstr.h" |
| #include "base/win/scoped_safearray.h" |
| #include "base/win/scoped_variant.h" |
| #include "content/browser/accessibility/accessibility_content_browsertest.h" |
| #include "content/browser/accessibility/browser_accessibility.h" |
| #include "content/browser/accessibility/browser_accessibility_com_win.h" |
| #include "content/browser/accessibility/browser_accessibility_manager.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_utils.h" |
| #include "content/public/test/hit_test_region_observer.h" |
| #include "content/shell/browser/shell.h" |
| #include "content/test/content_browser_test_utils_internal.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "ui/accessibility/accessibility_features.h" |
| #include "ui/accessibility/ax_node_position.h" |
| #include "ui/accessibility/ax_selection.h" |
| #include "ui/accessibility/ax_tree_id.h" |
| |
| using Microsoft::WRL::ComPtr; |
| |
| namespace content { |
| |
| #define ASSERT_UIA_ELEMENTNOTAVAILABLE(expr) \ |
| ASSERT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE), (expr)) |
| |
| #define EXPECT_UIA_DOUBLE_SAFEARRAY_EQ(safearray, expected_property_values) \ |
| { \ |
| EXPECT_EQ(sizeof(V_R8(LPVARIANT(NULL))), \ |
| ::SafeArrayGetElemsize(safearray)); \ |
| ASSERT_EQ(1u, SafeArrayGetDim(safearray)); \ |
| LONG array_lower_bound; \ |
| ASSERT_HRESULT_SUCCEEDED( \ |
| SafeArrayGetLBound(safearray, 1, &array_lower_bound)); \ |
| LONG array_upper_bound; \ |
| ASSERT_HRESULT_SUCCEEDED( \ |
| SafeArrayGetUBound(safearray, 1, &array_upper_bound)); \ |
| double* array_data; \ |
| ASSERT_HRESULT_SUCCEEDED(::SafeArrayAccessData( \ |
| safearray, reinterpret_cast<void**>(&array_data))); \ |
| size_t count = array_upper_bound - array_lower_bound + 1; \ |
| ASSERT_EQ(expected_property_values.size(), count); \ |
| for (size_t i = 0; i < count; ++i) { \ |
| EXPECT_EQ(array_data[i], expected_property_values[i]); \ |
| } \ |
| ASSERT_HRESULT_SUCCEEDED(::SafeArrayUnaccessData(safearray)); \ |
| } |
| |
| #define EXPECT_UIA_SAFEARRAY_EQ(safearray, expected_property_values) \ |
| { \ |
| using T = typename decltype(expected_property_values)::value_type; \ |
| EXPECT_EQ(sizeof(T), ::SafeArrayGetElemsize(safearray)); \ |
| EXPECT_EQ(1u, SafeArrayGetDim(safearray)); \ |
| LONG array_lower_bound; \ |
| EXPECT_HRESULT_SUCCEEDED( \ |
| SafeArrayGetLBound(safearray, 1, &array_lower_bound)); \ |
| LONG array_upper_bound; \ |
| EXPECT_HRESULT_SUCCEEDED( \ |
| SafeArrayGetUBound(safearray, 1, &array_upper_bound)); \ |
| const size_t count = array_upper_bound - array_lower_bound + 1; \ |
| EXPECT_EQ(expected_property_values.size(), count); \ |
| if (sizeof(T) == ::SafeArrayGetElemsize(safearray) && \ |
| count == expected_property_values.size()) { \ |
| T* array_data; \ |
| EXPECT_HRESULT_SUCCEEDED(::SafeArrayAccessData( \ |
| safearray, reinterpret_cast<void**>(&array_data))); \ |
| for (size_t i = 0; i < count; ++i) { \ |
| EXPECT_EQ(array_data[i], expected_property_values[i]); \ |
| } \ |
| EXPECT_HRESULT_SUCCEEDED(::SafeArrayUnaccessData(safearray)); \ |
| } \ |
| } |
| |
| #define EXPECT_UIA_TEXTRANGE_EQ(provider, expected_content) \ |
| { \ |
| base::win::ScopedBstr provider_content; \ |
| ASSERT_HRESULT_SUCCEEDED( \ |
| provider->GetText(-1, provider_content.Receive())); \ |
| EXPECT_STREQ(expected_content, provider_content.Get()); \ |
| } |
| |
| #define EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider, endpoint, unit, \ |
| count, expected_text, expected_count) \ |
| { \ |
| int result_count; \ |
| EXPECT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit( \ |
| endpoint, unit, count, &result_count)); \ |
| EXPECT_EQ(expected_count, result_count); \ |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, expected_text); \ |
| } |
| |
| #define EXPECT_UIA_MOVE(text_range_provider, unit, count, expected_text, \ |
| expected_count) \ |
| { \ |
| int result_count; \ |
| EXPECT_HRESULT_SUCCEEDED( \ |
| text_range_provider->Move(unit, count, &result_count)); \ |
| EXPECT_EQ(expected_count, result_count); \ |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, expected_text); \ |
| } |
| |
| class AXPlatformNodeTextRangeProviderWinBrowserTest |
| : public AccessibilityContentBrowserTest { |
| protected: |
| const std::wstring kEmbeddedCharacterAsString{ |
| base::as_wcstr(&ui::AXPlatformNodeBase::kEmbeddedCharacter), 1}; |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| base::CommandLine* cl = base::CommandLine::ForCurrentProcess(); |
| cl->AppendSwitchASCII(switches::kForceDeviceScaleFactor, "1.0"); |
| } |
| |
| void SetUpOnMainThread() override { |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| SetupCrossSiteRedirector(embedded_test_server()); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| } |
| |
| RenderWidgetHostImpl* GetWidgetHost() { |
| return RenderWidgetHostImpl::From( |
| shell()->web_contents()->GetRenderViewHost()->GetWidget()); |
| } |
| |
| void SynchronizeThreads() { |
| MainThreadFrameObserver observer(GetWidgetHost()); |
| observer.Wait(); |
| } |
| |
| BrowserAccessibilityManager* GetManager() const { |
| WebContentsImpl* web_contents = |
| static_cast<WebContentsImpl*>(shell()->web_contents()); |
| return web_contents->GetRootBrowserAccessibilityManager(); |
| } |
| |
| void GetTextRangeProviderFromTextNode( |
| BrowserAccessibility& target_node, |
| ITextRangeProvider** text_range_provider) { |
| BrowserAccessibilityComWin* target_node_com = |
| ToBrowserAccessibilityWin(&target_node)->GetCOM(); |
| ASSERT_NE(nullptr, target_node_com); |
| |
| ComPtr<ITextProvider> text_provider; |
| ASSERT_HRESULT_SUCCEEDED( |
| target_node_com->GetPatternProvider(UIA_TextPatternId, &text_provider)); |
| ASSERT_NE(nullptr, text_provider.Get()); |
| |
| ASSERT_HRESULT_SUCCEEDED( |
| text_provider->get_DocumentRange(text_range_provider)); |
| } |
| |
| void GetDocumentRangeForMarkup(const std::string& html_markup, |
| ITextRangeProvider** text_range_provider) { |
| LoadInitialAccessibilityTreeFromHtml(html_markup); |
| GetTextRangeProviderFromTextNode( |
| *GetManager()->GetBrowserAccessibilityRoot(), text_range_provider); |
| } |
| |
| // Run through ITextRangeProvider::ScrollIntoView top tests. It's assumed that |
| // the browser has already loaded an HTML document's accessibility tree. |
| // Assert the text range generated for an accessibility node is scrolled to be |
| // flush with the top of the viewport. |
| // expected_start_role: the expected accessibility role of the text range |
| // start node under test |
| // fstart: the function to retrieve the accessibility text |
| // range start node under test from the root |
| // accessibility node |
| // expected_end_role: the expected accessibility role of the text range |
| // end node under test |
| // fend: the function to retrieve the accessibility text |
| // range end node under test from the root |
| // accessibility node |
| // align_to_top: true to test top viewport alignment, otherwise test |
| // bottom viewport alignment |
| void ScrollIntoViewBrowserTestTemplate( |
| const ax::mojom::Role expected_start_role, |
| BrowserAccessibility* (BrowserAccessibility::*fstart)() const, |
| const ax::mojom::Role expected_end_role, |
| BrowserAccessibility* (BrowserAccessibility::*fend)() const, |
| const bool align_to_top) { |
| BrowserAccessibility* root_browser_accessibility = |
| GetRootAndAssertNonNull(); |
| |
| BrowserAccessibility* browser_accessibility_start = |
| (root_browser_accessibility->*fstart)(); |
| ASSERT_NE(nullptr, browser_accessibility_start); |
| ASSERT_EQ(expected_start_role, browser_accessibility_start->GetRole()); |
| |
| BrowserAccessibility* browser_accessibility_end = |
| (root_browser_accessibility->*fend)(); |
| ASSERT_NE(nullptr, browser_accessibility_end); |
| ASSERT_EQ(expected_end_role, browser_accessibility_end->GetRole()); |
| |
| AssertScrollIntoView(root_browser_accessibility, |
| browser_accessibility_start, browser_accessibility_end, |
| align_to_top); |
| } |
| |
| // Run through ITextRangeProvider::ScrollIntoView top tests. It's assumed that |
| // the browser has already loaded an HTML document's accessibility tree. |
| // Assert the text range generated for an accessibility node is scrolled to be |
| // flush with the top of the viewport. |
| // expected_start_role: the expected accessibility role of the text range |
| // start node under test |
| // fstart: the function to retrieve the accessibility text |
| // range start node under test from the root |
| // accessibility node |
| // fstart_arg: an index argument for fstart |
| // expected_end_role: the expected accessibility role of the text range |
| // end node under test |
| // fend: the function to retrieve the accessibility text |
| // range end node under test from the root |
| // accessibility node |
| // fend_arg: an index argument for fend |
| // align_to_top: true to test top viewport alignment, otherwise test |
| // bottom viewport alignment |
| void ScrollIntoViewBrowserTestTemplate( |
| const ax::mojom::Role expected_start_role, |
| BrowserAccessibility* (BrowserAccessibility::*fstart)(size_t) const, |
| const size_t fstart_arg, |
| const ax::mojom::Role expected_end_role, |
| BrowserAccessibility* (BrowserAccessibility::*fend)(size_t) const, |
| const size_t fend_arg, |
| const bool align_to_top) { |
| BrowserAccessibility* root_browser_accessibility = |
| GetRootAndAssertNonNull(); |
| |
| BrowserAccessibility* browser_accessibility_start = |
| (root_browser_accessibility->*fstart)(fstart_arg); |
| ASSERT_NE(nullptr, browser_accessibility_start); |
| ASSERT_EQ(expected_start_role, browser_accessibility_start->GetRole()); |
| |
| BrowserAccessibility* browser_accessibility_end = |
| (root_browser_accessibility->*fend)(fend_arg); |
| ASSERT_NE(nullptr, browser_accessibility_end); |
| ASSERT_EQ(expected_end_role, browser_accessibility_end->GetRole()); |
| |
| AssertScrollIntoView(root_browser_accessibility, |
| browser_accessibility_start, browser_accessibility_end, |
| align_to_top); |
| } |
| |
| void ScrollIntoViewFromIframeBrowserTestTemplate( |
| const ax::mojom::Role expected_start_role, |
| BrowserAccessibility* (BrowserAccessibility::*fstart)() const, |
| const ax::mojom::Role expected_end_role, |
| BrowserAccessibility* (BrowserAccessibility::*fend)() const, |
| const bool align_to_top) { |
| BrowserAccessibility* root_browser_accessibility = |
| GetRootAndAssertNonNull(); |
| BrowserAccessibility* leaf_iframe_browser_accessibility = |
| root_browser_accessibility->InternalDeepestLastChild(); |
| ASSERT_NE(nullptr, leaf_iframe_browser_accessibility); |
| ASSERT_EQ(ax::mojom::Role::kIframe, |
| leaf_iframe_browser_accessibility->GetRole()); |
| |
| ui::AXTreeID iframe_tree_id = ui::AXTreeID::FromString( |
| leaf_iframe_browser_accessibility->GetStringAttribute( |
| ax::mojom::StringAttribute::kChildTreeId)); |
| BrowserAccessibilityManager* iframe_browser_accessibility_manager = |
| BrowserAccessibilityManager::FromID(iframe_tree_id); |
| ASSERT_NE(nullptr, iframe_browser_accessibility_manager); |
| BrowserAccessibility* root_iframe_browser_accessibility = |
| iframe_browser_accessibility_manager->GetBrowserAccessibilityRoot(); |
| ASSERT_NE(nullptr, root_iframe_browser_accessibility); |
| ASSERT_EQ(ax::mojom::Role::kRootWebArea, |
| root_iframe_browser_accessibility->GetRole()); |
| |
| BrowserAccessibility* browser_accessibility_start = |
| (root_iframe_browser_accessibility->*fstart)(); |
| ASSERT_NE(nullptr, browser_accessibility_start); |
| ASSERT_EQ(expected_start_role, browser_accessibility_start->GetRole()); |
| |
| BrowserAccessibility* browser_accessibility_end = |
| (root_iframe_browser_accessibility->*fend)(); |
| ASSERT_NE(nullptr, browser_accessibility_end); |
| ASSERT_EQ(expected_end_role, browser_accessibility_end->GetRole()); |
| |
| AssertScrollIntoView(root_iframe_browser_accessibility, |
| browser_accessibility_start, browser_accessibility_end, |
| align_to_top); |
| } |
| |
| void AssertScrollIntoView(BrowserAccessibility* root_browser_accessibility, |
| BrowserAccessibility* browser_accessibility_start, |
| BrowserAccessibility* browser_accessibility_end, |
| const bool align_to_top) { |
| BrowserAccessibility::AXPosition start = |
| browser_accessibility_start->CreateTextPositionAt(0); |
| BrowserAccessibility::AXPosition end = |
| browser_accessibility_end->CreateTextPositionAt(0) |
| ->CreatePositionAtEndOfAnchor(); |
| |
| BrowserAccessibilityComWin* start_browser_accessibility_com_win = |
| ToBrowserAccessibilityWin(browser_accessibility_start)->GetCOM(); |
| ASSERT_NE(nullptr, start_browser_accessibility_com_win); |
| |
| ComPtr<ITextRangeProvider> text_range_provider; |
| ui::AXPlatformNodeTextRangeProviderWin::CreateTextRangeProvider( |
| std::move(start), std::move(end), &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider); |
| |
| gfx::Rect previous_range_bounds = |
| align_to_top ? browser_accessibility_start->GetBoundsRect( |
| ui::AXCoordinateSystem::kFrame, |
| ui::AXClippingBehavior::kUnclipped) |
| : browser_accessibility_end->GetBoundsRect( |
| ui::AXCoordinateSystem::kFrame, |
| ui::AXClippingBehavior::kUnclipped); |
| |
| AccessibilityNotificationWaiter location_changed_waiter( |
| GetWebContentsAndAssertNonNull(), ui::kAXModeComplete, |
| ax::mojom::Event::kLocationChanged); |
| ASSERT_HRESULT_SUCCEEDED(text_range_provider->ScrollIntoView(align_to_top)); |
| ASSERT_TRUE(location_changed_waiter.WaitForNotification()); |
| |
| gfx::Rect root_page_bounds = root_browser_accessibility->GetBoundsRect( |
| ui::AXCoordinateSystem::kFrame, ui::AXClippingBehavior::kUnclipped); |
| if (align_to_top) { |
| gfx::Rect range_bounds = browser_accessibility_start->GetBoundsRect( |
| ui::AXCoordinateSystem::kFrame, ui::AXClippingBehavior::kUnclipped); |
| ASSERT_NE(previous_range_bounds.y(), range_bounds.y()); |
| ASSERT_NEAR(root_page_bounds.y(), range_bounds.y(), 1); |
| } else { |
| gfx::Rect range_bounds = browser_accessibility_end->GetBoundsRect( |
| ui::AXCoordinateSystem::kFrame, ui::AXClippingBehavior::kUnclipped); |
| gfx::Size viewport_size = |
| gfx::Size(root_page_bounds.width(), root_page_bounds.height()); |
| ASSERT_NE(previous_range_bounds.y(), range_bounds.y()); |
| ASSERT_NEAR(root_page_bounds.y() + viewport_size.height(), |
| range_bounds.y() + range_bounds.height(), 1); |
| } |
| } |
| |
| void ScrollIntoViewTopBrowserTestTemplate( |
| const ax::mojom::Role expected_role, |
| BrowserAccessibility* (BrowserAccessibility::*f)() const) { |
| ScrollIntoViewBrowserTestTemplate(expected_role, f, expected_role, f, true); |
| } |
| |
| void ScrollIntoViewTopBrowserTestTemplate( |
| const ax::mojom::Role expected_role_start, |
| BrowserAccessibility* (BrowserAccessibility::*fstart)() const, |
| const ax::mojom::Role expected_role_end, |
| BrowserAccessibility* (BrowserAccessibility::*fend)() const) { |
| ScrollIntoViewBrowserTestTemplate(expected_role_start, fstart, |
| expected_role_end, fend, true); |
| } |
| |
| void ScrollIntoViewTopBrowserTestTemplate( |
| const ax::mojom::Role expected_role_start, |
| BrowserAccessibility* (BrowserAccessibility::*fstart)(size_t) const, |
| const size_t fstart_arg, |
| const ax::mojom::Role expected_role_end, |
| BrowserAccessibility* (BrowserAccessibility::*fend)(size_t) const, |
| const size_t fend_arg) { |
| ScrollIntoViewBrowserTestTemplate(expected_role_start, fstart, fstart_arg, |
| expected_role_end, fend, fend_arg, true); |
| } |
| |
| void ScrollIntoViewBottomBrowserTestTemplate( |
| const ax::mojom::Role expected_role, |
| BrowserAccessibility* (BrowserAccessibility::*f)() const) { |
| ScrollIntoViewBrowserTestTemplate(expected_role, f, expected_role, f, |
| false); |
| } |
| |
| void ScrollIntoViewBottomBrowserTestTemplate( |
| const ax::mojom::Role expected_role_start, |
| BrowserAccessibility* (BrowserAccessibility::*fstart)() const, |
| const ax::mojom::Role expected_role_end, |
| BrowserAccessibility* (BrowserAccessibility::*fend)() const) { |
| ScrollIntoViewBrowserTestTemplate(expected_role_start, fstart, |
| expected_role_end, fend, false); |
| } |
| |
| void ScrollIntoViewBottomBrowserTestTemplate( |
| const ax::mojom::Role expected_role_start, |
| BrowserAccessibility* (BrowserAccessibility::*fstart)(size_t) const, |
| const size_t fstart_arg, |
| const ax::mojom::Role expected_role_end, |
| BrowserAccessibility* (BrowserAccessibility::*fend)(size_t) const, |
| const size_t fend_arg) { |
| ScrollIntoViewBrowserTestTemplate(expected_role_start, fstart, fstart_arg, |
| expected_role_end, fend, fend_arg, false); |
| } |
| |
| void AssertMoveByUnitForMarkup( |
| const TextUnit& unit, |
| const std::string& html_markup, |
| const std::vector<const wchar_t*>& expected_text) { |
| ComPtr<ITextRangeProvider> text_range; |
| |
| GetDocumentRangeForMarkup(html_markup, &text_range); |
| ASSERT_NE(nullptr, text_range.Get()); |
| text_range->ExpandToEnclosingUnit(unit); |
| |
| size_t index = 0; |
| int count_moved = 1; |
| while (count_moved == 1 && index < expected_text.size()) { |
| EXPECT_UIA_TEXTRANGE_EQ(text_range, expected_text[index++]); |
| ASSERT_HRESULT_SUCCEEDED(text_range->Move(unit, 1, &count_moved)); |
| } |
| EXPECT_EQ(expected_text.size(), index); |
| EXPECT_EQ(0, count_moved); |
| |
| count_moved = -1; |
| index = expected_text.size(); |
| while (count_moved == -1 && index > 0) { |
| EXPECT_UIA_TEXTRANGE_EQ(text_range, expected_text[--index]); |
| ASSERT_HRESULT_SUCCEEDED(text_range->Move(unit, -1, &count_moved)); |
| } |
| EXPECT_EQ(0, count_moved); |
| EXPECT_EQ(0u, index); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_{::features::kUiaProvider}; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| GetChildren) { |
| LoadInitialAccessibilityTreeFromHtml(R"HTML( |
| <!DOCTYPE html> |
| <html> |
| <body> |
| <div> |
| <span>Text1</span> |
| <span>Text2</span> |
| <span>Text3</span> |
| </div> |
| <p>Before link</p> |
| <a href="#"> |
| Link text 1 |
| <span>Link text 2</span> |
| <span>Link text 3</span> |
| Link text 4 |
| </a> |
| <p>After link</p> |
| <p>Before img</p> |
| <img alt="Image description"> |
| <p>After img</p> |
| </body> |
| </html> |
| )HTML"); |
| |
| BrowserAccessibility* text1_node = |
| FindNode(ax::mojom::Role::kStaticText, "Text1"); |
| ASSERT_NE(nullptr, text1_node); |
| ComPtr<IRawElementProviderSimple> text1_raw = |
| QueryInterfaceFromNode<IRawElementProviderSimple>(text1_node); |
| |
| BrowserAccessibility* before_link_text_node = |
| FindNode(ax::mojom::Role::kStaticText, "Before link"); |
| ASSERT_NE(nullptr, before_link_text_node); |
| ComPtr<IRawElementProviderSimple> before_link_text_raw = |
| QueryInterfaceFromNode<IRawElementProviderSimple>(before_link_text_node); |
| |
| BrowserAccessibility* link_node = |
| FindNode(ax::mojom::Role::kLink, |
| "Link text 1 Link text 2 Link text 3 Link text 4"); |
| ASSERT_NE(nullptr, link_node); |
| ComPtr<IRawElementProviderSimple> link_raw = |
| QueryInterfaceFromNode<IRawElementProviderSimple>(link_node); |
| |
| BrowserAccessibility* link_text2_node = |
| FindNode(ax::mojom::Role::kStaticText, "Link text 2"); |
| ASSERT_NE(nullptr, link_text2_node); |
| ComPtr<IRawElementProviderSimple> link_text2_raw = |
| QueryInterfaceFromNode<IRawElementProviderSimple>(link_text2_node); |
| |
| BrowserAccessibility* link_text3_node = |
| FindNode(ax::mojom::Role::kStaticText, "Link text 3"); |
| ASSERT_NE(nullptr, link_text3_node); |
| ComPtr<IRawElementProviderSimple> link_text3_raw = |
| QueryInterfaceFromNode<IRawElementProviderSimple>(link_text3_node); |
| |
| BrowserAccessibility* after_link_text_node = |
| FindNode(ax::mojom::Role::kStaticText, "After link"); |
| ASSERT_NE(nullptr, after_link_text_node); |
| ComPtr<IRawElementProviderSimple> after_link_text_raw = |
| QueryInterfaceFromNode<IRawElementProviderSimple>(after_link_text_node); |
| |
| BrowserAccessibility* image_node = |
| FindNode(ax::mojom::Role::kImage, "Image description"); |
| ASSERT_NE(nullptr, image_node); |
| ComPtr<IRawElementProviderSimple> image_raw = |
| QueryInterfaceFromNode<IRawElementProviderSimple>(image_node); |
| |
| // 1. Validate that no children are returned when the range doesn't include |
| // any UIA embedded objects. |
| ComPtr<ITextRangeProvider> text_range; |
| GetTextRangeProviderFromTextNode(*text1_node, &text_range); |
| ASSERT_NE(nullptr, text_range.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range, L"Text1"); |
| |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range, TextPatternRangeEndpoint_End, |
| TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ L"Text1 Text2 Text3\n", |
| /*expected_count*/ 1); |
| |
| base::win::ScopedSafearray children; |
| std::vector<ComPtr<IRawElementProviderSimple>> expected_values = {}; |
| |
| EXPECT_HRESULT_SUCCEEDED(text_range->GetChildren(children.Receive())); |
| EXPECT_UIA_SAFEARRAY_EQ(children.Get(), expected_values); |
| |
| // 2. Validate that both the link and image objects are returned when the |
| // range spans the document. |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range, TextPatternRangeEndpoint_End, TextUnit_Document, |
| /*count*/ 1, |
| /*expected_text*/ |
| L"Text1 Text2 Text3\nBefore link\nLink text 1 Link text 2 Link text 3 " |
| L"Link text 4\nAfter link\nBefore img\n\xFFFC\nAfter img", |
| /*expected_count*/ 1); |
| |
| EXPECT_HRESULT_SUCCEEDED(text_range->GetChildren(children.Receive())); |
| |
| expected_values = {link_raw, image_raw}; |
| EXPECT_UIA_SAFEARRAY_EQ(children.Get(), expected_values); |
| |
| // 3. Validate that no object is returned when the range is inside the textual |
| // content of an embedded object. |
| GetTextRangeProviderFromTextNode(*link_text2_node, &text_range); |
| ASSERT_NE(nullptr, text_range.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range, L"Link text 2"); |
| |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range, TextPatternRangeEndpoint_End, |
| TextUnit_Character, |
| /*count*/ 12, |
| /*expected_text*/ L"Link text 2 Link text 3", |
| /*expected_count*/ 12); |
| |
| EXPECT_HRESULT_SUCCEEDED(text_range->GetChildren(children.Receive())); |
| |
| expected_values = {}; |
| EXPECT_UIA_SAFEARRAY_EQ(children.Get(), expected_values); |
| |
| // 4. Validate that the link object is returned when the text range contains |
| // a link object. |
| GetTextRangeProviderFromTextNode(*before_link_text_node, &text_range); |
| ASSERT_NE(nullptr, text_range.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range, L"Before link"); |
| |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range, TextPatternRangeEndpoint_End, |
| TextUnit_Paragraph, |
| /*count*/ 2, |
| /*expected_text*/ |
| L"Before link\nLink text 1 Link text 2 Link " |
| L"text 3 Link text 4\nAfter link\n", |
| /*expected_count*/ 2); |
| |
| EXPECT_HRESULT_SUCCEEDED(text_range->GetChildren(children.Receive())); |
| |
| expected_values = {link_raw}; |
| EXPECT_UIA_SAFEARRAY_EQ(children.Get(), expected_values); |
| |
| // 5. Validate that the link object is included even if it is partially |
| // included in the range. |
| GetTextRangeProviderFromTextNode(*link_text2_node, &text_range); |
| ASSERT_NE(nullptr, text_range.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range, L"Link text 2"); |
| |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ 2, |
| /*expected_text*/ L"Link text 2 Link text 3 Link text 4\nAfter link\n", |
| /*expected_count*/ 2); |
| |
| EXPECT_HRESULT_SUCCEEDED(text_range->GetChildren(children.Receive())); |
| |
| expected_values = {link_raw}; |
| EXPECT_UIA_SAFEARRAY_EQ(children.Get(), expected_values); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| GetTextEmptyButtonWithAriaLabelRangeAnchoredInSpans) { |
| LoadInitialAccessibilityTreeFromHtml(R"HTML( |
| <!DOCTYPE html> |
| <span>before</span> |
| <button aria-label="middle"><svg aria-hidden="true"></svg></button> |
| <span>after</span> |
| )HTML"); |
| |
| BrowserAccessibility* root = GetRootAndAssertNonNull(); |
| |
| ComPtr<ITextRangeProvider> text_range_provider; |
| GetTextRangeProviderFromTextNode(*root, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"before\nmiddle\nafter"); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Character, |
| /*count*/ 6, |
| /*expected_text*/ L"\nmiddle\nafter", |
| /*expected_count*/ 6); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Character, |
| /*count*/ 7, |
| /*expected_text*/ L"\nafter", |
| /*expected_count*/ 7); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| GetTextEmptyButtonWithAriaLabel) { |
| LoadInitialAccessibilityTreeFromHtml(R"HTML( |
| <!DOCTYPE html> |
| <button aria-label="middle"><svg aria-hidden="true"></svg></button> |
| )HTML"); |
| |
| BrowserAccessibility* root = GetRootAndAssertNonNull(); |
| |
| ComPtr<ITextRangeProvider> text_range_provider; |
| GetTextRangeProviderFromTextNode(*root, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"middle"); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Character, |
| /*count*/ 3, |
| /*expected_text*/ L"dle", |
| /*expected_count*/ 3); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| GetTextEmptyButtonWithAriaLabelStartAnchoredInSpan) { |
| LoadInitialAccessibilityTreeFromHtml(R"HTML( |
| <!DOCTYPE html> |
| <span>before</span> |
| <button aria-label="middle"><svg aria-hidden="true"></svg></button> |
| )HTML"); |
| |
| BrowserAccessibility* root = GetRootAndAssertNonNull(); |
| |
| ComPtr<ITextRangeProvider> text_range_provider; |
| GetTextRangeProviderFromTextNode(*root, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"before\nmiddle"); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Character, |
| /*count*/ 8, |
| /*expected_text*/ L"iddle", |
| /*expected_count*/ 8); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| GetTextEmptyButtonWithAriaLabelButtonEndAnchoredInSpan) { |
| LoadInitialAccessibilityTreeFromHtml(R"HTML( |
| <!DOCTYPE html> |
| <button aria-label="middle"><svg aria-hidden="true"></svg></button> |
| <span>after</span> |
| )HTML"); |
| |
| BrowserAccessibility* root = GetRootAndAssertNonNull(); |
| |
| ComPtr<ITextRangeProvider> text_range_provider; |
| GetTextRangeProviderFromTextNode(*root, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"middle\nafter"); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Character, |
| /*count*/ 6, |
| /*expected_text*/ L"\nafter", |
| /*expected_count*/ 6); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| GetTextEmptyTextfieldWithAriaLabel) { |
| LoadInitialAccessibilityTreeFromHtml(R"HTML( |
| <!DOCTYPE html> |
| <input type="text" aria-label="before"> |
| <span>after</span> |
| )HTML"); |
| |
| BrowserAccessibility* root = GetRootAndAssertNonNull(); |
| |
| ComPtr<ITextRangeProvider> text_range_provider; |
| GetTextRangeProviderFromTextNode(*root, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"before\nafter"); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Character, |
| /*count*/ 6, |
| /*expected_text*/ L"\nafter", |
| /*expected_count*/ 6); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| GetTextNonEmptyTextfieldWithAriaLabel) { |
| LoadInitialAccessibilityTreeFromHtml(R"HTML( |
| <!DOCTYPE html> |
| <input type="text" aria-label="before" value="go blue"> |
| <span>after</span> |
| )HTML"); |
| |
| BrowserAccessibility* root = GetRootAndAssertNonNull(); |
| |
| ComPtr<ITextRangeProvider> text_range_provider; |
| GetTextRangeProviderFromTextNode(*root, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"go blue\nafter"); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Character, |
| /*count*/ 6, |
| /*expected_text*/ L"e\nafter", |
| /*expected_count*/ 6); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| GetAttributeValue) { |
| LoadInitialAccessibilityTreeFromHtml(R"HTML( |
| <!DOCTYPE html> |
| <html> |
| <body> |
| <div style="font-size: 16pt"> |
| <span style="font-size: 12pt">Text1</span> |
| Text2 |
| </div> |
| </body> |
| </html> |
| )HTML"); |
| |
| ComPtr<IUnknown> mix_attribute_value; |
| EXPECT_HRESULT_SUCCEEDED( |
| UiaGetReservedMixedAttributeValue(&mix_attribute_value)); |
| |
| BrowserAccessibility* node = FindNode(ax::mojom::Role::kStaticText, "Text1"); |
| ASSERT_NE(nullptr, node); |
| EXPECT_TRUE(node->IsLeaf()); |
| EXPECT_EQ(0u, node->PlatformChildCount()); |
| |
| ComPtr<ITextRangeProvider> text_range_provider; |
| GetTextRangeProviderFromTextNode(*node, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"Text1"); |
| |
| base::win::ScopedVariant value; |
| EXPECT_HRESULT_SUCCEEDED(text_range_provider->GetAttributeValue( |
| UIA_FontSizeAttributeId, value.Receive())); |
| EXPECT_EQ(value.type(), VT_R8); |
| EXPECT_EQ(V_R8(value.ptr()), 12.0); |
| |
| EXPECT_HRESULT_SUCCEEDED( |
| text_range_provider->ExpandToEnclosingUnit(TextUnit_Paragraph)); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"Text1 Text2"); |
| |
| EXPECT_HRESULT_SUCCEEDED(text_range_provider->GetAttributeValue( |
| UIA_FontSizeAttributeId, value.Receive())); |
| EXPECT_EQ(value.type(), VT_UNKNOWN); |
| EXPECT_EQ(V_UNKNOWN(value.ptr()), mix_attribute_value.Get()) |
| << "expected 'mixed attribute value' interface pointer"; |
| } |
| |
| // An empty atomic text field, such as an empty <input type="text">, should |
| // expose an embedded object replacement character in its text representation. |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| GetAttributeValueInReadonlyEmptyAtomicTextField) { |
| LoadInitialAccessibilityTreeFromHtml(R"HTML( |
| <!DOCTYPE html> |
| <html> |
| <body> |
| <input readonly type="text"> |
| <input type="search"> |
| </body> |
| </html> |
| )HTML"); |
| |
| BrowserAccessibility* root = GetRootAndAssertNonNull(); |
| |
| BrowserAccessibility* input_text_node = |
| root->InternalGetFirstChild()->InternalGetFirstChild(); |
| ASSERT_NE(nullptr, input_text_node); |
| EXPECT_TRUE(input_text_node->IsLeaf()); |
| EXPECT_EQ(0u, input_text_node->PlatformChildCount()); |
| |
| ComPtr<ITextRangeProvider> text_range_provider; |
| GetTextRangeProviderFromTextNode(*input_text_node, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, |
| kEmbeddedCharacterAsString.c_str()); |
| |
| base::win::ScopedVariant value; |
| EXPECT_HRESULT_SUCCEEDED(text_range_provider->GetAttributeValue( |
| UIA_IsReadOnlyAttributeId, value.Receive())); |
| EXPECT_EQ(value.type(), VT_BOOL); |
| EXPECT_EQ(V_BOOL(value.ptr()), VARIANT_TRUE); |
| text_range_provider.Reset(); |
| value.Reset(); |
| |
| BrowserAccessibility* input_search_node = |
| input_text_node->InternalGetNextSibling(); |
| ASSERT_NE(nullptr, input_search_node); |
| EXPECT_TRUE(input_search_node->IsLeaf()); |
| EXPECT_EQ(0u, input_search_node->PlatformChildCount()); |
| |
| GetTextRangeProviderFromTextNode(*input_search_node, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, |
| kEmbeddedCharacterAsString.c_str()); |
| |
| EXPECT_HRESULT_SUCCEEDED(text_range_provider->GetAttributeValue( |
| UIA_IsReadOnlyAttributeId, value.Receive())); |
| EXPECT_EQ(value.type(), VT_BOOL); |
| EXPECT_EQ(V_BOOL(value.ptr()), VARIANT_FALSE); |
| text_range_provider.Reset(); |
| value.Reset(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| GetAttributeValueInReadonlyAtomicTextField) { |
| LoadInitialAccessibilityTreeFromHtml(R"HTML( |
| <!DOCTYPE html> |
| <html> |
| <body> |
| <input type="text" aria-label="input_text" value="text"> |
| <input readonly type="search" aria-label="input_search" |
| value="search"> |
| </body> |
| </html> |
| )HTML"); |
| |
| BrowserAccessibility* input_text_node = |
| FindNode(ax::mojom::Role::kTextField, "input_text"); |
| ASSERT_NE(nullptr, input_text_node); |
| EXPECT_TRUE(input_text_node->IsLeaf()); |
| EXPECT_EQ(0u, input_text_node->PlatformChildCount()); |
| |
| ComPtr<ITextRangeProvider> text_range_provider; |
| GetTextRangeProviderFromTextNode(*input_text_node, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"text"); |
| |
| base::win::ScopedVariant value; |
| EXPECT_HRESULT_SUCCEEDED(text_range_provider->GetAttributeValue( |
| UIA_IsReadOnlyAttributeId, value.Receive())); |
| EXPECT_EQ(value.type(), VT_BOOL); |
| EXPECT_EQ(V_BOOL(value.ptr()), VARIANT_FALSE); |
| text_range_provider.Reset(); |
| value.Reset(); |
| |
| BrowserAccessibility* input_search_node = |
| FindNode(ax::mojom::Role::kSearchBox, "input_search"); |
| ASSERT_NE(nullptr, input_search_node); |
| EXPECT_TRUE(input_search_node->IsLeaf()); |
| EXPECT_EQ(0u, input_search_node->PlatformChildCount()); |
| |
| GetTextRangeProviderFromTextNode(*input_search_node, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"search"); |
| |
| EXPECT_HRESULT_SUCCEEDED(text_range_provider->GetAttributeValue( |
| UIA_IsReadOnlyAttributeId, value.Receive())); |
| EXPECT_EQ(value.type(), VT_BOOL); |
| EXPECT_EQ(V_BOOL(value.ptr()), VARIANT_TRUE); |
| text_range_provider.Reset(); |
| value.Reset(); |
| } |
| |
| // With a non-atomic text field, the read-only attribute should be determined |
| // based on the content editable root node's editable state. |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| GetAttributeValueInReadonlyNonAtomicTextField) { |
| LoadInitialAccessibilityTreeFromHtml(R"HTML( |
| <!DOCTYPE html> |
| <html> |
| <style> |
| .non-atomic-text-field::before { |
| content: attr(data-placeholder); |
| pointer-events: none; |
| } |
| </style> |
| <body> |
| <div contenteditable="true" data-placeholder="@mention or comment" |
| role="textbox" aria-readonly="false" aria-label="text_field_1" |
| class="non-atomic-text-field"> |
| <p>value1</p> |
| </div> |
| <div contenteditable="true" data-placeholder="@mention or comment" |
| role="textbox" aria-readonly="true" aria-label="text_field_2" |
| class="non-atomic-text-field"> |
| <p>value2</p> |
| </div> |
| <div contenteditable="false" data-placeholder="@mention or comment" |
| role="textbox" aria-readonly="true" aria-label="text_field_3" |
| class="non-atomic-text-field"> |
| <p>value3</p> |
| </div> |
| </body> |
| </html> |
| )HTML"); |
| |
| BrowserAccessibility* text_field_node_1 = |
| FindNode(ax::mojom::Role::kTextField, "text_field_1"); |
| ASSERT_NE(nullptr, text_field_node_1); |
| BrowserAccessibility* text_field_node_2 = |
| FindNode(ax::mojom::Role::kTextField, "text_field_2"); |
| ASSERT_NE(nullptr, text_field_node_2); |
| BrowserAccessibility* text_field_node_3 = |
| FindNode(ax::mojom::Role::kTextField, "text_field_3"); |
| ASSERT_NE(nullptr, text_field_node_3); |
| |
| ComPtr<ITextRangeProvider> text_range_provider; |
| GetTextRangeProviderFromTextNode(*text_field_node_1, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"@mention or comment\nvalue1"); |
| |
| base::win::ScopedVariant value; |
| EXPECT_HRESULT_SUCCEEDED(text_range_provider->GetAttributeValue( |
| UIA_IsReadOnlyAttributeId, value.Receive())); |
| EXPECT_EQ(value.type(), VT_BOOL); |
| EXPECT_EQ(V_BOOL(value.ptr()), VARIANT_FALSE); |
| text_range_provider.Reset(); |
| value.Reset(); |
| |
| GetTextRangeProviderFromTextNode(*text_field_node_2, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"@mention or comment\nvalue2"); |
| |
| EXPECT_HRESULT_SUCCEEDED(text_range_provider->GetAttributeValue( |
| UIA_IsReadOnlyAttributeId, value.Receive())); |
| EXPECT_EQ(value.type(), VT_BOOL); |
| EXPECT_EQ(V_BOOL(value.ptr()), VARIANT_FALSE); |
| text_range_provider.Reset(); |
| value.Reset(); |
| |
| GetTextRangeProviderFromTextNode(*text_field_node_3, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"@mention or comment\nvalue3"); |
| |
| EXPECT_HRESULT_SUCCEEDED(text_range_provider->GetAttributeValue( |
| UIA_IsReadOnlyAttributeId, value.Receive())); |
| EXPECT_EQ(value.type(), VT_BOOL); |
| EXPECT_EQ(V_BOOL(value.ptr()), VARIANT_TRUE); |
| text_range_provider.Reset(); |
| value.Reset(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| DoNotNormalizeRangeWithVisibleCaretOrSelection) { |
| LoadInitialAccessibilityTreeFromHtml(std::string(R"HTML( |
| <!DOCTYPE html> |
| <html> |
| <body> |
| <div aria-value="wrapper"> |
| <input type="text" aria-label="input_text"><span |
| style="font-size: 12pt">Text1</span> |
| </div> |
| <div contenteditable="true"> |
| <ul><li>item</li></ul>3.14 |
| </div> |
| </body> |
| </html> |
| )HTML")); |
| |
| // Case 1: Inside of an atomic text field, NormalizeTextRange shouldn't modify |
| // the text range endpoints. An atomic text field does not expose its internal |
| // implementation to assistive software, appearing as a single leaf node in |
| // the accessibility tree. It includes <input>, <textarea> and Views-based |
| // text fields. |
| // |
| // In order for the test harness to effectively simulate typing in a text |
| // input, first change the value of the text input and then focus it. Only |
| // editing the value won't show the cursor and only focusing will put the |
| // cursor at the beginning of the text input, so both steps are necessary. |
| BrowserAccessibility* input_text_node = |
| FindNode(ax::mojom::Role::kTextField, "input_text"); |
| ASSERT_NE(nullptr, input_text_node); |
| EXPECT_TRUE(input_text_node->IsLeaf()); |
| EXPECT_EQ(0u, input_text_node->PlatformChildCount()); |
| |
| AccessibilityNotificationWaiter edit_waiter(shell()->web_contents(), |
| ui::kAXModeComplete, |
| ax::mojom::Event::kValueChanged); |
| ui::AXActionData edit_data; |
| edit_data.target_node_id = input_text_node->GetId(); |
| edit_data.action = ax::mojom::Action::kSetValue; |
| edit_data.value = "test"; |
| input_text_node->AccessibilityPerformAction(edit_data); |
| ASSERT_TRUE(edit_waiter.WaitForNotification()); |
| |
| AccessibilityNotificationWaiter focus_waiter( |
| shell()->web_contents(), ui::kAXModeComplete, ax::mojom::Event::kFocus); |
| ui::AXActionData focus_data; |
| focus_data.target_node_id = input_text_node->GetId(); |
| focus_data.action = ax::mojom::Action::kFocus; |
| input_text_node->AccessibilityPerformAction(focus_data); |
| ASSERT_TRUE(focus_waiter.WaitForNotification()); |
| |
| ComPtr<ITextRangeProvider> text_range_provider; |
| GetTextRangeProviderFromTextNode(*input_text_node, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"test"); |
| |
| // Move the first position so that both endpoints are at the end of the text |
| // input. This is where calls to NormalizeTextRange can be problematic. |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Character, |
| /*count*/ 4, |
| /*expected_text*/ L"", |
| /*expected_count*/ 4); |
| |
| // Clone the original text range so we can keep track if NormalizeTextRange |
| // causes a change in position. |
| ComPtr<ITextRangeProvider> text_range_provider_clone; |
| text_range_provider->Clone(&text_range_provider_clone); |
| |
| // Since both ranges are identical, the result of CompareEndpoints should be |
| // 0. |
| int result = 0; |
| EXPECT_HRESULT_SUCCEEDED(text_range_provider->CompareEndpoints( |
| TextPatternRangeEndpoint_End, text_range_provider_clone.Get(), |
| TextPatternRangeEndpoint_Start, &result)); |
| ASSERT_EQ(0, result); |
| |
| // Calling GetAttributeValue will call NormalizeTextRange, which shouldn't |
| // change the result of CompareEndpoints below since the range is inside an |
| // atomic text field. |
| base::win::ScopedVariant value; |
| EXPECT_HRESULT_SUCCEEDED(text_range_provider->GetAttributeValue( |
| UIA_IsReadOnlyAttributeId, value.Receive())); |
| EXPECT_EQ(value.type(), VT_BOOL); |
| EXPECT_EQ(V_BOOL(value.ptr()), VARIANT_FALSE); |
| value.Reset(); |
| |
| EXPECT_HRESULT_SUCCEEDED(text_range_provider->CompareEndpoints( |
| TextPatternRangeEndpoint_End, text_range_provider_clone.Get(), |
| TextPatternRangeEndpoint_Start, &result)); |
| ASSERT_EQ(0, result); |
| |
| // Case 2: Inside of a rich text field, NormalizeTextRange should modify the |
| // text range endpoints. |
| BrowserAccessibility* node = FindNode(ax::mojom::Role::kStaticText, "item"); |
| ASSERT_NE(nullptr, node); |
| EXPECT_TRUE(node->IsLeaf()); |
| EXPECT_EQ(0u, node->PlatformChildCount()); |
| |
| GetTextRangeProviderFromTextNode(*node, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"item"); |
| |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Character, |
| /*count*/ 4, |
| /*expected_text*/ L"", |
| /*expected_count*/ 4); |
| // Make the range degenerate. |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character, |
| /*count*/ 2, |
| /*expected_text*/ L"\n3", |
| /*expected_count*/ 2); |
| |
| // The range should now span two nodes: start: "item<>", end: "<3>.14". |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"\n3"); |
| |
| // Clone the original text range so we can keep track if NormalizeTextRange |
| // causes a change in position. |
| text_range_provider->Clone(&text_range_provider_clone); |
| |
| // Calling GetAttributeValue will call NormalizeTextRange, which should |
| // change the result of CompareEndpoints below since we are in a rich text |
| // field. |
| EXPECT_HRESULT_SUCCEEDED(text_range_provider->GetAttributeValue( |
| UIA_IsReadOnlyAttributeId, value.Receive())); |
| EXPECT_EQ(value.type(), VT_BOOL); |
| EXPECT_EQ(V_BOOL(value.ptr()), VARIANT_FALSE); |
| value.Reset(); |
| |
| // Since text_range_provider has been modified by NormalizeTextRange, we |
| // expect a difference here. |
| EXPECT_HRESULT_SUCCEEDED(text_range_provider->CompareEndpoints( |
| TextPatternRangeEndpoint_End, text_range_provider_clone.Get(), |
| TextPatternRangeEndpoint_Start, &result)); |
| ASSERT_EQ(1, result); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| CompareAriaInvalidTextRange) { |
| // This test is needed since there was a bug with this scenario, and it |
| // differs from others since the "aria-invalid" attribute causes the tree to |
| // be different, with an extra generic container that we do not have in the |
| // case without aria-invalid="spelling". |
| LoadInitialAccessibilityTreeFromHtml(std::string(R"HTML( |
| <!DOCTYPE html> |
| <html> |
| <div contentEditable="true">x <span aria-invalid="spelling">He</span></div> |
| </html> |
| )HTML")); |
| |
| BrowserAccessibility* static_text_node_1 = |
| FindNode(ax::mojom::Role::kStaticText, "He"); |
| ASSERT_NE(nullptr, static_text_node_1); |
| BrowserAccessibility* static_text_node_2 = |
| FindNode(ax::mojom::Role::kStaticText, "x "); |
| ASSERT_NE(nullptr, static_text_node_2); |
| |
| ComPtr<ITextRangeProvider> text_range_provider_1; |
| GetTextRangeProviderFromTextNode(*static_text_node_1, &text_range_provider_1); |
| |
| // We are moving the endpoints to replicate a bug where the text ranges looked |
| // like: |
| // 1. H<e> |
| // 2. x <> |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider_1, TextPatternRangeEndpoint_Start, TextUnit_Character, |
| /*count*/ 1, |
| /*expected_text*/ L"e", |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider_1, TextPatternRangeEndpoint_End, TextUnit_Character, |
| /*count*/ -1, |
| /*expected_text*/ L"", |
| /*expected_count*/ -1); |
| ASSERT_NE(nullptr, text_range_provider_1.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider_1, L""); |
| |
| ComPtr<ITextRangeProvider> text_range_provider_2; |
| GetTextRangeProviderFromTextNode(*static_text_node_2, &text_range_provider_2); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider_2, TextPatternRangeEndpoint_Start, TextUnit_Character, |
| /*count*/ 2, |
| /*expected_text*/ L"", |
| /*expected_count*/ 2); |
| ASSERT_NE(nullptr, text_range_provider_2.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider_2, L""); |
| |
| BOOL are_same; |
| text_range_provider_1->Compare(text_range_provider_2.Get(), &are_same); |
| EXPECT_FALSE(are_same); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| TextInputWithNewline) { |
| LoadInitialAccessibilityTreeFromHtml(R"HTML( |
| <!DOCTYPE html> |
| <html> |
| <body> |
| <div aria-value='wrapper'> |
| <input type='text' aria-label='input_text'><br> |
| </div> |
| </body> |
| </html> |
| )HTML"); |
| |
| // This test validates an important scenario for editing. UIA clients such as |
| // Narrator expect newlines to be contained within their adjacent nodes. |
| // This test validates this scenario for GetEnclosingElement and |
| // GetAttributeValue, both of which are essential for text editing scenarios. |
| // |
| // In order for the test harness to effectively simulate typing in a text |
| // input, first change the value of the text input and then focus it. Only |
| // editing the value won't show the cursor and only focusing will put the |
| // cursor at the beginning of the text input, so both steps are necessary. |
| BrowserAccessibility* input_text_node = |
| FindNode(ax::mojom::Role::kTextField, "input_text"); |
| ASSERT_NE(nullptr, input_text_node); |
| EXPECT_TRUE(input_text_node->IsLeaf()); |
| EXPECT_EQ(0u, input_text_node->PlatformChildCount()); |
| |
| AccessibilityNotificationWaiter edit_waiter(shell()->web_contents(), |
| ui::kAXModeComplete, |
| ax::mojom::Event::kValueChanged); |
| ui::AXActionData edit_data; |
| edit_data.target_node_id = input_text_node->GetId(); |
| edit_data.action = ax::mojom::Action::kSetValue; |
| edit_data.value = "test"; |
| input_text_node->AccessibilityPerformAction(edit_data); |
| ASSERT_TRUE(edit_waiter.WaitForNotification()); |
| |
| AccessibilityNotificationWaiter focus_waiter( |
| shell()->web_contents(), ui::kAXModeComplete, ax::mojom::Event::kFocus); |
| ui::AXActionData focus_data; |
| focus_data.target_node_id = input_text_node->GetId(); |
| focus_data.action = ax::mojom::Action::kFocus; |
| input_text_node->AccessibilityPerformAction(focus_data); |
| ASSERT_TRUE(focus_waiter.WaitForNotification()); |
| |
| ComPtr<ITextRangeProvider> text_range_provider; |
| GetTextRangeProviderFromTextNode(*input_text_node, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"test"); |
| |
| // Move the first position so that both endpoints are at the end of the text |
| // input. |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Character, |
| /*count*/ 4, |
| /*expected_text*/ L"", |
| /*expected_count*/ 4); |
| |
| ComPtr<IRawElementProviderSimple> text_input_provider = |
| QueryInterfaceFromNode<IRawElementProviderSimple>(input_text_node); |
| |
| // Validate that the enclosing element is the text input node and not the |
| // parent node that includes the newline. |
| ComPtr<IRawElementProviderSimple> enclosing_element; |
| ASSERT_HRESULT_SUCCEEDED( |
| text_range_provider->GetEnclosingElement(&enclosing_element)); |
| EXPECT_EQ(text_input_provider.Get(), enclosing_element.Get()); |
| |
| // Calling GetAttributeValue on the editable text input should return false, |
| // verifying that the read-only newline is not interfering. |
| base::win::ScopedVariant value; |
| EXPECT_HRESULT_SUCCEEDED(text_range_provider->GetAttributeValue( |
| UIA_IsReadOnlyAttributeId, value.Receive())); |
| EXPECT_EQ(value.type(), VT_BOOL); |
| EXPECT_EQ(V_BOOL(value.ptr()), VARIANT_FALSE); |
| value.Reset(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| GetBoundingRectangles) { |
| LoadInitialAccessibilityTreeFromHtml(std::string(R"HTML( |
| <!DOCTYPE html> |
| <html> |
| <head> |
| <style> |
| .break_word { |
| width: 50px; |
| word-wrap: break-word; |
| } |
| </style> |
| </head> |
| <body> |
| <p class="break_word">AsdfAsdfAsdf</p> |
| </body> |
| </html> |
| )HTML")); |
| |
| auto* node = FindNode(ax::mojom::Role::kStaticText, "AsdfAsdfAsdf"); |
| ASSERT_NE(nullptr, node); |
| EXPECT_TRUE(node->IsLeaf()); |
| EXPECT_EQ(0u, node->PlatformChildCount()); |
| |
| ComPtr<ITextRangeProvider> text_range_provider; |
| GetTextRangeProviderFromTextNode(*node, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"AsdfAsdfAsdf"); |
| |
| base::win::ScopedSafearray rectangles; |
| EXPECT_HRESULT_SUCCEEDED( |
| text_range_provider->GetBoundingRectangles(rectangles.Receive())); |
| |
| // |view_offset| is necessary to account for differences in the shell |
| // between platforms (e.g. title bar height) because the results of |
| // |GetBoundingRectangles| are in screen coordinates. |
| gfx::Vector2dF view_offset( |
| node->manager()->GetViewBoundsInScreenCoordinates().OffsetFromOrigin()); |
| std::vector<double> expected_values = { |
| 8 + view_offset.x(), 16 + view_offset.y(), 49, 17, |
| 8 + view_offset.x(), 34 + view_offset.y(), 44, 17}; |
| EXPECT_UIA_DOUBLE_SAFEARRAY_EQ(rectangles.Get(), expected_values); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| RemoveNode) { |
| LoadInitialAccessibilityTreeFromHtml(R"HTML( |
| <!DOCTYPE html> |
| <div id="wrapper"> |
| <p id="node_1">Node 1</p> |
| <p>Node 2</p> |
| </div> |
| )HTML"); |
| |
| BrowserAccessibility* node = FindNode(ax::mojom::Role::kStaticText, "Node 1"); |
| ASSERT_NE(nullptr, node); |
| EXPECT_EQ(0u, node->PlatformChildCount()); |
| |
| // Create the text range on "Node 1". |
| ComPtr<ITextRangeProvider> text_range_provider; |
| GetTextRangeProviderFromTextNode(*node, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"Node 1"); |
| |
| // Move the text range to "Node 2". |
| EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word, |
| /*count*/ 2, |
| /*expected_text*/ L"Node ", |
| /*expected_count*/ 2); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider, |
| TextPatternRangeEndpoint_End, TextUnit_Word, |
| /*count*/ 1, |
| /*expected_text*/ L"Node 2", |
| /*expected_count*/ 1); |
| |
| // Now remove "Node 1" from the DOM and verify the text range created from |
| // "Node 1" is still functional. |
| { |
| AccessibilityNotificationWaiter waiter( |
| shell()->web_contents(), ui::kAXModeComplete, |
| ui::AXEventGenerator::Event::CHILDREN_CHANGED); |
| EXPECT_TRUE( |
| ExecJs(shell()->web_contents(), |
| "document.getElementById('wrapper').removeChild(document." |
| "getElementById('node_1'));")); |
| |
| ASSERT_TRUE(waiter.WaitForNotification()); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character, |
| /*count*/ -1, |
| /*expected_text*/ L"Node ", |
| /*expected_count*/ -1); |
| } |
| |
| // Now remove all children from the DOM and verify the text range created from |
| // "Node 1" is still valid (it got moved to a non-deleted ancestor node). |
| { |
| AccessibilityNotificationWaiter waiter( |
| shell()->web_contents(), ui::kAXModeComplete, |
| ui::AXEventGenerator::Event::CHILDREN_CHANGED); |
| EXPECT_TRUE(ExecJs(shell()->web_contents(), |
| "while(document.body.childElementCount > 0) {" |
| " document.body.removeChild(document.body.firstChild);" |
| "}")); |
| |
| ASSERT_TRUE(waiter.WaitForNotification()); |
| |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character, |
| /*count*/ 1, |
| /*expected_text*/ L"", |
| /*expected_count*/ 0); |
| |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character, |
| /*count*/ -1, |
| /*expected_text*/ L"", |
| /*expected_count*/ 0); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| ScrollIntoViewTopStaticText) { |
| LoadInitialAccessibilityTreeFromHtmlFilePath( |
| "/accessibility/scrolling/text.html"); |
| ScrollIntoViewTopBrowserTestTemplate( |
| ax::mojom::Role::kStaticText, |
| &BrowserAccessibility::PlatformDeepestFirstChild); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| ScrollIntoViewBottomStaticText) { |
| LoadInitialAccessibilityTreeFromHtmlFilePath( |
| "/accessibility/scrolling/text.html"); |
| ScrollIntoViewBottomBrowserTestTemplate( |
| ax::mojom::Role::kStaticText, |
| &BrowserAccessibility::PlatformDeepestFirstChild); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| ScrollIntoViewTopEmbeddedText) { |
| LoadInitialAccessibilityTreeFromHtmlFilePath( |
| "/accessibility/scrolling/embedded-text.html"); |
| ScrollIntoViewTopBrowserTestTemplate( |
| ax::mojom::Role::kStaticText, |
| &BrowserAccessibility::PlatformDeepestLastChild); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| ScrollIntoViewBottomEmbeddedText) { |
| LoadInitialAccessibilityTreeFromHtmlFilePath( |
| "/accessibility/scrolling/embedded-text.html"); |
| ScrollIntoViewBottomBrowserTestTemplate( |
| ax::mojom::Role::kStaticText, |
| &BrowserAccessibility::PlatformDeepestLastChild); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| ScrollIntoViewTopEmbeddedTextCrossNode) { |
| LoadInitialAccessibilityTreeFromHtmlFilePath( |
| "/accessibility/scrolling/embedded-text.html"); |
| ScrollIntoViewTopBrowserTestTemplate( |
| ax::mojom::Role::kStaticText, |
| &BrowserAccessibility::PlatformDeepestFirstChild, |
| ax::mojom::Role::kStaticText, |
| &BrowserAccessibility::PlatformDeepestLastChild); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| ScrollIntoViewBottomEmbeddedTextCrossNode) { |
| LoadInitialAccessibilityTreeFromHtmlFilePath( |
| "/accessibility/scrolling/embedded-text.html"); |
| ScrollIntoViewBottomBrowserTestTemplate( |
| ax::mojom::Role::kStaticText, |
| &BrowserAccessibility::PlatformDeepestFirstChild, |
| ax::mojom::Role::kStaticText, |
| &BrowserAccessibility::PlatformDeepestLastChild); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| ScrollIntoViewTopTable) { |
| LoadInitialAccessibilityTreeFromHtmlFilePath( |
| "/accessibility/scrolling/table.html"); |
| ScrollIntoViewTopBrowserTestTemplate( |
| ax::mojom::Role::kTable, &BrowserAccessibility::PlatformGetChild, 0, |
| ax::mojom::Role::kTable, &BrowserAccessibility::PlatformGetChild, 0); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| ScrollIntoViewBottomTable) { |
| LoadInitialAccessibilityTreeFromHtmlFilePath( |
| "/accessibility/scrolling/table.html"); |
| ScrollIntoViewBottomBrowserTestTemplate( |
| ax::mojom::Role::kTable, &BrowserAccessibility::PlatformGetChild, 0, |
| ax::mojom::Role::kTable, &BrowserAccessibility::PlatformGetChild, 0); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| ScrollIntoViewTopTableText) { |
| LoadInitialAccessibilityTreeFromHtmlFilePath( |
| "/accessibility/scrolling/table.html"); |
| ScrollIntoViewTopBrowserTestTemplate( |
| ax::mojom::Role::kStaticText, |
| &BrowserAccessibility::PlatformDeepestLastChild); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| ScrollIntoViewBottomTableText) { |
| LoadInitialAccessibilityTreeFromHtmlFilePath( |
| "/accessibility/scrolling/table.html"); |
| ScrollIntoViewBottomBrowserTestTemplate( |
| ax::mojom::Role::kStaticText, |
| &BrowserAccessibility::PlatformDeepestLastChild); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| ScrollIntoViewTopLinkText) { |
| LoadInitialAccessibilityTreeFromHtmlFilePath( |
| "/accessibility/scrolling/link.html"); |
| ScrollIntoViewTopBrowserTestTemplate( |
| ax::mojom::Role::kStaticText, |
| &BrowserAccessibility::PlatformDeepestLastChild); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| ScrollIntoViewBottomLinkText) { |
| LoadInitialAccessibilityTreeFromHtmlFilePath( |
| "/accessibility/scrolling/link.html"); |
| ScrollIntoViewBottomBrowserTestTemplate( |
| ax::mojom::Role::kStaticText, |
| &BrowserAccessibility::PlatformDeepestLastChild); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| ScrollIntoViewTopLinkContainer) { |
| LoadInitialAccessibilityTreeFromHtmlFilePath( |
| "/accessibility/scrolling/link.html"); |
| ScrollIntoViewTopBrowserTestTemplate(ax::mojom::Role::kGenericContainer, |
| &BrowserAccessibility::PlatformGetChild, |
| 0, ax::mojom::Role::kGenericContainer, |
| &BrowserAccessibility::PlatformGetChild, |
| 0); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| ScrollIntoViewBottomLinkContainer) { |
| LoadInitialAccessibilityTreeFromHtmlFilePath( |
| "/accessibility/scrolling/link.html"); |
| ScrollIntoViewBottomBrowserTestTemplate( |
| ax::mojom::Role::kGenericContainer, |
| &BrowserAccessibility::PlatformGetChild, 0, |
| ax::mojom::Role::kGenericContainer, |
| &BrowserAccessibility::PlatformGetChild, 0); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| ScrollIntoViewTopTextFromIFrame) { |
| 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." |
| "\""); |
| ScrollIntoViewFromIframeBrowserTestTemplate( |
| ax::mojom::Role::kStaticText, |
| &BrowserAccessibility::PlatformDeepestFirstChild, |
| ax::mojom::Role::kStaticText, |
| &BrowserAccessibility::PlatformDeepestLastChild, true); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| ScrollIntoViewBottomTextFromIFrame) { |
| 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." |
| "\""); |
| ScrollIntoViewFromIframeBrowserTestTemplate( |
| ax::mojom::Role::kStaticText, |
| &BrowserAccessibility::PlatformDeepestFirstChild, |
| ax::mojom::Role::kStaticText, |
| &BrowserAccessibility::PlatformDeepestLastChild, false); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| MoveEndpointByUnitFormat) { |
| LoadInitialAccessibilityTreeFromHtml( |
| R"HTML(<!DOCTYPE html> |
| <html> |
| <body> |
| <div>plain 1</div><div>plain 2</div> |
| <div role="heading">plain heading</div> |
| <div role="article" style="font-style: italic">italic 1</div> |
| <div style="font-style: italic">italic 2</div> |
| <h1>heading</h1> |
| <h1>heading</h1> |
| <div style="font-weight: bold">bold 1</div> |
| <div style="font-weight: bold">bold 2</div> |
| </body> |
| </html>)HTML"); |
| auto* node = FindNode(ax::mojom::Role::kStaticText, "plain 1"); |
| ASSERT_NE(nullptr, node); |
| EXPECT_TRUE(node->IsLeaf()); |
| EXPECT_EQ(0u, node->PlatformChildCount()); |
| |
| ComPtr<ITextRangeProvider> text_range_provider; |
| GetTextRangeProviderFromTextNode(*node, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"plain 1"); |
| |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format, |
| /*count*/ 1, |
| /*expected_text*/ L"plain 1\nplain 2", |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format, |
| /*count*/ 1, |
| /*expected_text*/ |
| L"plain 1\nplain 2\nplain heading", |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format, |
| /*count*/ 1, |
| /*expected_text*/ |
| L"plain 1\nplain 2\nplain heading\nitalic 1\nitalic 2", |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format, |
| /*count*/ -1, |
| /*expected_text*/ L"plain 1\nplain 2\nplain heading", |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format, |
| /*count*/ 1, |
| /*expected_text*/ |
| L"plain 1\nplain 2\nplain heading\nitalic 1\nitalic 2", |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format, |
| /*count*/ 1, |
| /*expected_text*/ |
| L"plain 1\nplain 2\nplain heading\nitalic 1\nitalic 2\nheading", |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format, |
| /*count*/ 1, |
| /*expected_text*/ |
| L"plain 1\nplain 2\nplain heading\nitalic 1\nitalic 2\nheading\nheading", |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format, |
| /*count*/ 5, |
| /*expected_text*/ |
| L"plain 1\nplain 2\nplain heading\nitalic 1\nitalic 2" |
| L"\nheading\nheading\nbold 1\nbold 2", |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format, |
| /*count*/ -8, |
| /*expected_text*/ L"", |
| /*expected_count*/ -6); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format, |
| /*count*/ 1, |
| /*expected_text*/ L"plain 1\nplain 2", |
| /*expected_count*/ 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| MoveEndpointByLineInlineBlockSpan) { |
| LoadInitialAccessibilityTreeFromHtml( |
| R"HTML(<!DOCTYPE html> |
| <html> |
| <div contenteditable="true" style="outline: 1px solid;" aria-label="canvas"> |
| <div>first line</div> |
| <div><span>this line </span><span style="display: inline-block" id="IB"><span style="display: block;">is</span></span><span> broken.</span></div> |
| <div>last line</div> |
| </div> |
| </html>)HTML"); |
| auto* node = FindNode(ax::mojom::Role::kStaticText, "first line"); |
| ASSERT_NE(nullptr, node); |
| |
| ComPtr<ITextRangeProvider> text_range_provider; |
| GetTextRangeProviderFromTextNode(*node, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"first line"); |
| |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Line, |
| /*count*/ 1, |
| /*expected_text*/ L"first line\nthis line \nis\n broken.", |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Line, |
| /*count*/ 1, |
| /*expected_text*/ |
| L"this line \nis\n broken.", |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider, |
| TextPatternRangeEndpoint_End, TextUnit_Line, |
| /*count*/ 1, |
| /*expected_text*/ |
| L"this line \nis\n broken.\nlast line", |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Line, |
| /*count*/ 1, |
| /*expected_text*/ L"last line", |
| /*expected_count*/ 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| MoveEndpointByLineLinkInTwoLines) { |
| LoadInitialAccessibilityTreeFromHtml(R"HTML(<!DOCTYPE html> |
| <div contenteditable style="width: 70px"> |
| Hello |
| <a href="#">this is a</a> |
| test. |
| </div> |
| )HTML"); |
| auto* node = FindNode(ax::mojom::Role::kStaticText, "Hello "); |
| ASSERT_NE(nullptr, node); |
| |
| ComPtr<ITextRangeProvider> text_range_provider; |
| GetTextRangeProviderFromTextNode(*node, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"Hello "); |
| text_range_provider->ExpandToEnclosingUnit(TextUnit_Line); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"Hello this "); |
| |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider, |
| TextPatternRangeEndpoint_End, TextUnit_Line, |
| /*count*/ 1, |
| /*expected_text*/ L"Hello this is a test.", |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Line, |
| /*count*/ 1, |
| /*expected_text*/ |
| L"is a test.", |
| /*expected_count*/ 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| MoveEndpointByUnitFormatAllFormats) { |
| LoadInitialAccessibilityTreeFromHtml( |
| R"HTML(<!DOCTYPE html> |
| <html> |
| <body> |
| <div>plain 1</div><div>plain 2</div> |
| <div style="background-color: red">background-color 1</div> |
| <div style="background-color: red">background-color 2</div> |
| <div style="color: blue">color 1</div> |
| <div style="color: blue">color 2</div> |
| <div style="text-decoration: overline">overline 1</div> |
| <div style="text-decoration: overline">overline 2</div> |
| <div style="text-decoration: line-through">line-through 1</div> |
| <div style="text-decoration: line-through">line-through 2</div> |
| <div style="vertical-align:super">sup 1</div> |
| <div style="vertical-align:super">sup 2</div> |
| <div style="font-weight: bold">bold 1</div> |
| <div style="font-weight: bold">bold 2</div> |
| <div style="font-family: sans-serif">font-family 1</div> |
| <div style="font-family: sans-serif">font-family 2</div> |
| <div aria-invalid="spelling">spelling 1</div> |
| <div aria-invalid="spelling">spelling two</div> <!-- different length string on purpose --> |
| <div aria-invalid="grammar">grammar 1</div> |
| <div aria-invalid="grammar">grammar two</div> <!-- different length string on purpose --> |
| </body> |
| </html>)HTML"); |
| auto* node = FindNode(ax::mojom::Role::kStaticText, "plain 1"); |
| ASSERT_NE(nullptr, node); |
| EXPECT_TRUE(node->IsLeaf()); |
| EXPECT_EQ(0u, node->PlatformChildCount()); |
| |
| ComPtr<ITextRangeProvider> text_range_provider; |
| GetTextRangeProviderFromTextNode(*node, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"plain 1"); |
| |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format, |
| /*count*/ 1, |
| /*expected_text*/ L"plain 1\nplain 2", |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format, |
| /*count*/ 1, |
| /*expected_text*/ |
| L"plain 1\nplain 2\nbackground-color 1\nbackground-color 2", |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format, |
| /*count*/ -1, |
| /*expected_text*/ L"plain 1\nplain 2", |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format, |
| /*count*/ 2, |
| /*expected_text*/ |
| L"plain 1\nplain 2\nbackground-color 1\nbackground-color 2\ncolor " |
| L"1\ncolor 2", |
| /*expected_count*/ 2); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format, |
| /*count*/ -1, |
| /*expected_text*/ |
| L"plain 1\nplain 2\nbackground-color 1\nbackground-color 2", |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format, |
| /*count*/ 2, |
| /*expected_text*/ |
| L"plain 1\nplain 2\nbackground-color 1\nbackground-color 2\ncolor " |
| L"1\ncolor 2\noverline 1\noverline 2", |
| /*expected_count*/ 2); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format, |
| /*count*/ -1, |
| /*expected_text*/ |
| L"plain 1\nplain 2\nbackground-color 1\nbackground-color 2\ncolor " |
| L"1\ncolor 2", |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format, |
| /*count*/ 2, |
| /*expected_text*/ |
| L"plain 1\nplain 2\nbackground-color 1\nbackground-color 2\ncolor " |
| L"1\ncolor 2\noverline 1\noverline 2\nline-through 1\nline-through 2", |
| /*expected_count*/ 2); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format, |
| /*count*/ -1, |
| /*expected_text*/ |
| L"plain 1\nplain 2\nbackground-color 1\nbackground-color 2\ncolor " |
| L"1\ncolor 2\noverline 1\noverline 2", |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format, |
| /*count*/ 2, |
| /*expected_text*/ |
| L"plain 1\nplain 2\nbackground-color 1\nbackground-color 2\ncolor " |
| L"1\ncolor 2\noverline 1\noverline 2\nline-through 1\nline-through " |
| L"2\nsup 1\nsup 2", |
| /*expected_count*/ 2); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format, |
| /*count*/ -1, |
| /*expected_text*/ |
| L"plain 1\nplain 2\nbackground-color 1\nbackground-color 2\ncolor " |
| L"1\ncolor 2\noverline 1\noverline 2\nline-through 1\nline-through 2", |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format, |
| /*count*/ 2, |
| /*expected_text*/ |
| L"plain 1\nplain 2\nbackground-color 1\nbackground-color 2\ncolor " |
| L"1\ncolor 2\noverline 1\noverline 2\nline-through 1\nline-through " |
| L"2\nsup 1\nsup 2\nbold 1\nbold 2", |
| /*expected_count*/ 2); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format, |
| /*count*/ -1, |
| /*expected_text*/ |
| L"plain 1\nplain 2\nbackground-color 1\nbackground-color 2\ncolor " |
| L"1\ncolor 2\noverline 1\noverline 2\nline-through 1\nline-through " |
| L"2\nsup 1\nsup 2", |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format, |
| /*count*/ 2, |
| /*expected_text*/ |
| L"plain 1\nplain 2\nbackground-color 1\nbackground-color 2\ncolor " |
| L"1\ncolor 2\noverline 1\noverline 2\nline-through 1\nline-through " |
| L"2\nsup 1\nsup 2\nbold 1\nbold 2\nfont-family 1\nfont-family 2", |
| /*expected_count*/ 2); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format, |
| /*count*/ -1, |
| /*expected_text*/ |
| L"plain 1\nplain 2\nbackground-color 1\nbackground-color 2\ncolor " |
| L"1\ncolor 2\noverline 1\noverline 2\nline-through 1\nline-through " |
| L"2\nsup 1\nsup 2\nbold 1\nbold 2", |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format, |
| /*count*/ 2, |
| /*expected_text*/ |
| L"plain 1\nplain 2\nbackground-color 1\nbackground-color 2\ncolor " |
| L"1\ncolor 2\noverline 1\noverline 2\nline-through 1\nline-through " |
| L"2\nsup 1\nsup 2\nbold 1\nbold 2\nfont-family 1\nfont-family " |
| L"2\nspelling 1\nspelling two", |
| /*expected_count*/ 2); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format, |
| /*count*/ -1, |
| /*expected_text*/ |
| L"plain 1\nplain 2\nbackground-color 1\nbackground-color 2\ncolor " |
| L"1\ncolor 2\noverline 1\noverline 2\nline-through 1\nline-through " |
| L"2\nsup 1\nsup 2\nbold 1\nbold 2\nfont-family 1\nfont-family 2", |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format, |
| /*count*/ 2, |
| /*expected_text*/ |
| L"plain 1\nplain 2\nbackground-color 1\nbackground-color 2\ncolor " |
| L"1\ncolor 2\noverline 1\noverline 2\nline-through 1\nline-through " |
| L"2\nsup 1\nsup 2\nbold 1\nbold 2\nfont-family 1\nfont-family " |
| L"2\nspelling 1\nspelling two\ngrammar 1\ngrammar two", |
| /*expected_count*/ 2); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format, |
| /*count*/ -1, |
| /*expected_text*/ |
| L"plain 1\nplain 2\nbackground-color 1\nbackground-color 2\ncolor " |
| L"1\ncolor 2\noverline 1\noverline 2\nline-through 1\nline-through " |
| L"2\nsup 1\nsup 2\nbold 1\nbold 2\nfont-family 1\nfont-family " |
| L"2\nspelling 1\nspelling two", |
| /*expected_count*/ -1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| MoveEndpointByUnitParagraph) { |
| LoadInitialAccessibilityTreeFromHtml( |
| R"HTML(<!DOCTYPE html> |
| <html> |
| <head><style> |
| div { |
| width: 100px; |
| } |
| code::before { |
| content: "["; |
| } |
| code::after { |
| content: "]"; |
| } |
| b::before, i::after { |
| width: 5px; |
| height: 5px; |
| content: ""; |
| display: block; |
| background: black; |
| } |
| </style></head> |
| <body> |
| <div>start</div> |
| <div> |
| text with <code>:before</code> |
| and <code>:after</code> content, |
| then a <b>bold</b> element with a |
| <code>block</code> before content |
| then a <i>italic</i> element with |
| a <code>block</code> after content |
| </div> |
| <div>end</div> |
| </body> |
| </html>)HTML"); |
| BrowserAccessibility* start_node = |
| FindNode(ax::mojom::Role::kStaticText, "start"); |
| ASSERT_NE(nullptr, start_node); |
| BrowserAccessibility* end_node = |
| FindNode(ax::mojom::Role::kStaticText, "end"); |
| ASSERT_NE(nullptr, end_node); |
| |
| std::vector<std::wstring> paragraphs = { |
| L"start", |
| L"text with [:before] and [:after]content, then a\n\xFFFC", |
| L"bold element with a [block]before content then a italic\n\xFFFC", |
| L"element with a [block] after content", |
| L"end", |
| }; |
| |
| // FORWARD NAVIGATION |
| ComPtr<ITextRangeProvider> text_range_provider; |
| GetTextRangeProviderFromTextNode(*start_node, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, paragraphs[0].c_str()); |
| |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ paragraphs[0].c_str(), |
| /*expected_count*/ 0); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ -2, |
| /*expected_text*/ L"", |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ (paragraphs[0] + L'\n').c_str(), |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ (paragraphs[0] + L'\n' + paragraphs[1] + L'\n').c_str(), |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ (paragraphs[1] + L'\n').c_str(), |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ (paragraphs[1] + L'\n' + paragraphs[2] + L'\n').c_str(), |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ (paragraphs[2] + L'\n').c_str(), |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ (paragraphs[2] + L'\n' + paragraphs[3] + L'\n').c_str(), |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ (paragraphs[3] + L'\n').c_str(), |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ (paragraphs[3] + L'\n' + paragraphs[4]).c_str(), |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ paragraphs[4].c_str(), |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ paragraphs[4].c_str(), |
| /*expected_count*/ 0); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ 2, |
| /*expected_text*/ L"", |
| /*expected_count*/ 1); |
| |
| // |
| // REVERSE NAVIGATION |
| // |
| |
| GetTextRangeProviderFromTextNode(*end_node, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, paragraphs[4].c_str()); |
| |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ paragraphs[4].c_str(), |
| /*expected_count*/ 0); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ 2, |
| /*expected_text*/ L"", |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ paragraphs[4].c_str(), |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[3] + L'\n' + paragraphs[4]).c_str(), |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[3] + L'\n').c_str(), |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[2] + L'\n' + paragraphs[3] + L'\n').c_str(), |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[2] + L'\n').c_str(), |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[1] + L'\n' + paragraphs[2] + L'\n').c_str(), |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[1] + L'\n').c_str(), |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[0] + L'\n' + paragraphs[1] + L'\n').c_str(), |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[0] + L'\n').c_str(), |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[0] + L'\n').c_str(), |
| /*expected_count*/ 0); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ -2, |
| /*expected_text*/ L"", |
| /*expected_count*/ -1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| AXPlatformNodeTextRangeProviderWinBrowserTest, |
| MoveEndpointByUnitParagraphCollapseTrailingLineBreakingObjects) { |
| LoadInitialAccessibilityTreeFromHtml( |
| R"HTML(<!DOCTYPE html> |
| <html> |
| <body> |
| <div>start</div> |
| <div> |
| <div>some text</div> |
| <div></div> |
| <br> |
| <span><br><div><br></div></span> |
| <div>more text</div> |
| </div> |
| <div>end</div> |
| </body> |
| </html>)HTML"); |
| BrowserAccessibility* start_node = |
| FindNode(ax::mojom::Role::kStaticText, "start"); |
| ASSERT_NE(nullptr, start_node); |
| BrowserAccessibility* end_node = |
| FindNode(ax::mojom::Role::kStaticText, "end"); |
| ASSERT_NE(nullptr, end_node); |
| |
| // The three <br> elements should be merged with the previous paragraph, |
| // because according to MSDN, in UI Automation, any trailing whitespace should |
| // be part of the previous paragraph. |
| std::vector<std::wstring> paragraphs = { |
| L"start", |
| L"some text\n\n\n", |
| L"more text", |
| L"end", |
| }; |
| |
| // FORWARD NAVIGATION |
| ComPtr<ITextRangeProvider> text_range_provider; |
| GetTextRangeProviderFromTextNode(*start_node, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, paragraphs[0].c_str()); |
| |
| // There is no trailing '\n' because the second paragraph already has merged |
| // trailing whitespace in it, and in such cases we made the design decision |
| // not to add an extra line break. |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ (paragraphs[0] + L'\n' + paragraphs[1]).c_str(), |
| /*expected_count*/ 1); |
| // There is no trailing '\n' because the second paragraph already has merged |
| // trailing whitespace in it, and in such cases we made the design decision |
| // not to add an extra line break. |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ paragraphs[1].c_str(), |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ (paragraphs[1] + paragraphs[2] + L'\n').c_str(), |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ (paragraphs[2] + L'\n').c_str(), |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ (paragraphs[2] + L'\n' + paragraphs[3]).c_str(), |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ paragraphs[3].c_str(), |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ paragraphs[3].c_str(), |
| /*expected_count*/ 0); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ L"", |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ L"", |
| /*expected_count*/ 0); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ L"", |
| /*expected_count*/ 0); |
| |
| // |
| // REVERSE NAVIGATION |
| // |
| |
| GetTextRangeProviderFromTextNode(*end_node, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, paragraphs[3].c_str()); |
| |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[2] + L'\n' + paragraphs[3]).c_str(), |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[2] + L'\n').c_str(), |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[1] + paragraphs[2] + L'\n').c_str(), |
| /*expected_count*/ -1); |
| // There is no trailing '\n' because the second paragraph already has merged |
| // trailing whitespace in it. |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[1]).c_str(), |
| /*expected_count*/ -1); |
| // There is no trailing '\n' because the second paragraph already has merged |
| // trailing whitespace in it. |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[0] + L'\n' + paragraphs[1]).c_str(), |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[0] + L'\n').c_str(), |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[0] + L'\n').c_str(), |
| /*expected_count*/ 0); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ L"", |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ L"", |
| /*expected_count*/ 0); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ L"", |
| /*expected_count*/ 0); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| AXPlatformNodeTextRangeProviderWinBrowserTest, |
| MoveEndpointByUnitParagraphCollapseConsecutiveParentChildLineBreakingObjects) { |
| LoadInitialAccessibilityTreeFromHtml( |
| R"HTML(<!DOCTYPE html> |
| <html> |
| <head> |
| <style> |
| div { |
| width: 100px; |
| } |
| |
| code::before { |
| content: "["; |
| } |
| |
| code::after { |
| content: "]"; |
| } |
| |
| /* This will create an empty anonymous layout block before the <b> |
| element. */ |
| b::before { |
| content: ""; |
| display: block; |
| } |
| </style> |
| </head> |
| |
| <body> |
| <div>start</div> |
| <div> |
| text with <code>:before</code> |
| and <code>:after</code> content, |
| then a |
| <div> |
| <b>bold</b> element |
| </div> |
| </div> |
| </body> |
| </html>)HTML"); |
| BrowserAccessibility* start_node = |
| FindNode(ax::mojom::Role::kStaticText, "start"); |
| ASSERT_NE(nullptr, start_node); |
| |
| std::vector<std::wstring> paragraphs = { |
| L"start", |
| L"text with [:before] and [:after]content, then a\n\xFFFC", |
| L"bold element", |
| }; |
| |
| // Forward navigation. |
| ComPtr<ITextRangeProvider> text_range_provider; |
| GetTextRangeProviderFromTextNode(*start_node, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, paragraphs[0].c_str()); |
| |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ (paragraphs[0] + L'\n' + paragraphs[1] + L'\n').c_str(), |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ |
| (paragraphs[0] + L'\n' + paragraphs[1] + L'\n' + paragraphs[2]).c_str(), |
| /*expected_count*/ 1); |
| |
| // |
| // Reverse navigation. |
| // |
| |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[0] + L'\n' + paragraphs[1] + L'\n').c_str(), |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[0] + L'\n').c_str(), |
| /*expected_count*/ -1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| MoveEndpointByUnitParagraphPreservedWhiteSpace) { |
| LoadInitialAccessibilityTreeFromHtml( |
| R"HTML(<!DOCTYPE html> |
| <html> |
| <body> |
| <div>start</div> |
| <span style='white-space: pre'> |
| First Paragraph |
| Second Paragraph |
| </span> |
| <!-- |
| Intentional nesting to test that ancestor positions can |
| resolve to the newline at the start of preserved whitespace. |
| --> |
| <div> |
| <div style='white-space: pre-line'> |
| Third Paragraph |
| Fourth Paragraph |
| </div> |
| </div> |
| <div style='white-space: pre-wrap; width: 10em;'> |
| Fifth Paragraph |
| Sixth Paragraph |
| </div> |
| <div style='white-space: break-spaces; width: 10em;'> |
| Seventh Paragraph |
| Eighth Paragraph |
| </div> |
| <div>end</div> |
| </body> |
| </html>)HTML"); |
| BrowserAccessibility* start_node = |
| FindNode(ax::mojom::Role::kStaticText, "start"); |
| ASSERT_NE(nullptr, start_node); |
| BrowserAccessibility* end_node = |
| FindNode(ax::mojom::Role::kStaticText, "end"); |
| ASSERT_NE(nullptr, end_node); |
| |
| ComPtr<ITextRangeProvider> text_range_provider; |
| |
| // According to MSDN, empty paragraphs should be merged with the previous |
| // paragraph. However, paragraphs containing only spaces are not considered |
| // empty. Otherwise, deleting all the text except a few spaces from an |
| // existing paragraph while editing a document will confusingly make the |
| // paragraph disappear. |
| // |
| // Note that two out of the three empty paragraphs span two lines, hence the |
| // '\n' suffix. |
| std::vector<std::wstring> paragraphs = { |
| L"start", |
| L" First Paragraph", |
| L" Second Paragraph", |
| L" \n", // Empty paragraph 1. |
| L"Third Paragraph", |
| L"Fourth Paragraph\n", |
| L" Fifth Paragraph", |
| L" Sixth Paragraph", |
| L" \n", // Empty paragraph 2. |
| L" Seventh Paragraph", |
| L" Eighth Paragraph", |
| L" ", // Empty paragraph 3. |
| L"end", |
| }; |
| |
| // FORWARD NAVIGATION |
| GetTextRangeProviderFromTextNode(*start_node, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, paragraphs[0].c_str()); |
| |
| // The first paragraph extends beyond the end of the "start" node, because |
| // the preserved whitespace node begins with a line break, so |
| // move once to capture that. |
| // |
| // Also, some paragraphs end with a line break, '\n', and in those cases |
| // `AXRange::GetText()` does not add an additional line break. Hence you might |
| // see some expectations that at first glance appear to be inconsistent with |
| // one another. |
| |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ (paragraphs[0] + L'\n').c_str(), |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ (paragraphs[0] + L'\n' + paragraphs[1] + L'\n').c_str(), |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ (paragraphs[1] + L'\n').c_str(), |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ (paragraphs[1] + L'\n' + paragraphs[2] + L'\n').c_str(), |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ (paragraphs[2] + L'\n').c_str(), |
| /*expected_count*/ 1); |
| // Since paragraphs[3] ends with a line break, '\n', `AXRange::GetText()` does |
| // not add an additional line break. |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ (paragraphs[2] + L'\n' + paragraphs[3]).c_str(), |
| /*expected_count*/ 1); |
| // Since paragraphs[3] ends with a line break, '\n', `AXRange::GetText()` does |
| // not add an additional line break. |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ paragraphs[3].c_str(), |
| /*expected_count*/ 1); |
| // Since paragraphs[3] ends with a line break, '\n', `AXRange::GetText()` does |
| // not add an additional line break. |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ (paragraphs[3] + paragraphs[4] + L'\n').c_str(), |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ (paragraphs[4] + L'\n').c_str(), |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ (paragraphs[4] + L'\n' + paragraphs[5] + L'\n').c_str(), |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ (paragraphs[5] + L'\n').c_str(), |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ (paragraphs[5] + L'\n' + paragraphs[6] + L'\n').c_str(), |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ (paragraphs[6] + L'\n').c_str(), |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ (paragraphs[6] + L'\n' + paragraphs[7] + L'\n').c_str(), |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ (paragraphs[7] + L'\n').c_str(), |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ (paragraphs[7] + L'\n' + paragraphs[8]).c_str(), |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ paragraphs[8].c_str(), |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ (paragraphs[8] + paragraphs[9] + L'\n').c_str(), |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ (paragraphs[9] + L'\n').c_str(), |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ |
| (paragraphs[9] + L'\n' + paragraphs[10] + L'\n').c_str(), |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ (paragraphs[10] + L'\n').c_str(), |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ (paragraphs[10] + L'\n' + paragraphs[11]).c_str(), |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ (paragraphs[11] + L'\n').c_str(), |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ (paragraphs[11] + L'\n' + paragraphs[12]).c_str(), |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ paragraphs[12].c_str(), |
| /*expected_count*/ 1); |
| |
| // |
| // REVERSE NAVIGATION |
| // |
| |
| GetTextRangeProviderFromTextNode(*end_node, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, paragraphs[12].c_str()); |
| |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[11] + L'\n' + paragraphs[12]).c_str(), |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[11] + L'\n').c_str(), |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[10] + L'\n' + paragraphs[11]).c_str(), |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[10] + L'\n').c_str(), |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ |
| (paragraphs[9] + L'\n' + paragraphs[10] + L'\n').c_str(), |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[9] + L'\n').c_str(), |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[8] + paragraphs[9] + L'\n').c_str(), |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ paragraphs[8].c_str(), |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[7] + L'\n' + paragraphs[8]).c_str(), |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[7] + L'\n').c_str(), |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[6] + L'\n' + paragraphs[7] + L'\n').c_str(), |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[6] + L'\n').c_str(), |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[5] + L'\n' + paragraphs[6] + L'\n').c_str(), |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[5] + L'\n').c_str(), |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[4] + L'\n' + paragraphs[5] + L'\n').c_str(), |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[4] + L'\n').c_str(), |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[3] + paragraphs[4] + L'\n').c_str(), |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ paragraphs[3].c_str(), |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[2] + L'\n' + paragraphs[3]).c_str(), |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[2] + L'\n').c_str(), |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[1] + L'\n' + paragraphs[2] + L'\n').c_str(), |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[1] + L'\n').c_str(), |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[0] + L'\n' + paragraphs[1] + L'\n').c_str(), |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[0] + L'\n').c_str(), |
| /*expected_count*/ -1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| MoveByUnitParagraphWithAriaHiddenNodes) { |
| const std::string html_markup = R"HTML(<!DOCTYPE html> |
| <html> <!-- aria=describedby on #body causes hidden nodes to be included --> |
| <body id="body" aria-describedby="body"> |
| <div>start</div> |
| <div> |
| 1. Paragraph with hidden <span aria-hidden="true"> |
| [IGNORED] |
| </span> inline in between |
| </div> |
| <div> |
| <span>2. Paragraph parts wrapped by</span> <span aria-hidden="true"> |
| [IGNORED] |
| </span> <span>span with hidden inline in between</span> |
| </div> |
| <div> |
| <span>3. Paragraph before hidden block</span><div aria-hidden="true"> |
| [IGNORED] |
| </div><span>4. Paragraph after hidden block</span> |
| </div> |
| <div> |
| <span aria-hidden="true">[IGNORED]</span><span>5. Paragraph with leading |
| and trailing hidden span</span><span aria-hidden="true">[IGNORED]</span> |
| </div> |
| <div> |
| <div aria-hidden="true">[IGNORED]</div><span>6. Paragraph with leading |
| and trailing hidden block</span><div aria-hidden="true">[IGNORED]</div> |
| </div> |
| <div>end</div> |
| </body> |
| </html>)HTML"; |
| |
| const std::vector<const wchar_t*> paragraphs = { |
| L"start\n", |
| L"1. Paragraph with hidden inline in between\n", |
| L"2. Paragraph parts wrapped by span with hidden inline in between\n", |
| L"3. Paragraph before hidden block\n", |
| L"4. Paragraph after hidden block\n", |
| L"5. Paragraph with leading and trailing hidden span\n", |
| L"6. Paragraph with leading and trailing hidden block\n", |
| L"end", |
| }; |
| |
| AssertMoveByUnitForMarkup(TextUnit_Paragraph, html_markup, paragraphs); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| MoveEndpointByUnitParagraphWithEmbeddedObject) { |
| LoadInitialAccessibilityTreeFromHtml( |
| R"HTML(<!DOCTYPE html> |
| <html> |
| <head></head> |
| <body> |
| <span>start</span> |
| <svg aria-label="middle"></svg> |
| <span>end</span> |
| </body> |
| </html>)HTML"); |
| BrowserAccessibility* start_node = |
| FindNode(ax::mojom::Role::kStaticText, "start"); |
| ASSERT_NE(nullptr, start_node); |
| BrowserAccessibility* end_node = |
| FindNode(ax::mojom::Role::kStaticText, "end"); |
| ASSERT_NE(nullptr, end_node); |
| |
| std::vector<std::wstring> paragraphs = { |
| L"start", |
| kEmbeddedCharacterAsString, |
| L"end", |
| }; |
| |
| // FORWARD NAVIGATION |
| ComPtr<ITextRangeProvider> text_range_provider; |
| GetTextRangeProviderFromTextNode(*start_node, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, paragraphs[0].c_str()); |
| |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ paragraphs[0].c_str(), |
| /*expected_count*/ 0); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ -2, |
| /*expected_text*/ L"", |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ (paragraphs[0] + L'\n').c_str(), |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ (paragraphs[0] + L'\n' + paragraphs[1] + L'\n').c_str(), |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ (paragraphs[1] + L'\n').c_str(), |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ (paragraphs[1] + L'\n' + paragraphs[2]).c_str(), |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ paragraphs[2].c_str(), |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ paragraphs[2].c_str(), |
| /*expected_count*/ 0); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ 2, |
| /*expected_text*/ L"", |
| /*expected_count*/ 1); |
| |
| // |
| // REVERSE NAVIGATION |
| // |
| |
| GetTextRangeProviderFromTextNode(*end_node, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, paragraphs[2].c_str()); |
| |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ paragraphs[2].c_str(), |
| /*expected_count*/ 0); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ 2, |
| /*expected_text*/ L"", |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ paragraphs[2].c_str(), |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[1] + L'\n' + paragraphs[2]).c_str(), |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[1] + L'\n').c_str(), |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[0] + L'\n' + paragraphs[1] + L'\n').c_str(), |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[0] + L'\n').c_str(), |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph, |
| /*count*/ -1, |
| /*expected_text*/ (paragraphs[0] + L'\n').c_str(), |
| /*expected_count*/ 0); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ -2, |
| /*expected_text*/ L"", |
| /*expected_count*/ -1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| MoveEndpointByUnitLineInertSpan) { |
| // Spans need to be in the same line: see https://crbug.com/1511390. |
| LoadInitialAccessibilityTreeFromHtml( |
| R"HTML(<!DOCTYPE html> |
| <div> |
| <div>first line</div> |
| <span id="span1">go </span><span inert>inert1</span><span inert>inert2</span><span>blue</span> |
| <div>last line</div> |
| </div>)HTML"); |
| BrowserAccessibility* start_node = |
| FindNode(ax::mojom::Role::kStaticText, "first line"); |
| ASSERT_NE(nullptr, start_node); |
| |
| BrowserAccessibility* end_node = |
| FindNode(ax::mojom::Role::kStaticText, "last line"); |
| ASSERT_NE(nullptr, start_node); |
| |
| // Navigating forward to the next line. |
| ComPtr<ITextRangeProvider> text_range_provider; |
| GetTextRangeProviderFromTextNode(*start_node, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"first line"); |
| |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider, |
| TextPatternRangeEndpoint_End, TextUnit_Line, |
| /*count*/ 1, |
| /*expected_text*/ L"first line\ngo blue", |
| /*expected_count*/ 1); |
| |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Line, |
| /*count*/ 1, |
| /*expected_text*/ L"go blue", |
| /*expected_count*/ 1); |
| |
| // Navigating to the previous line. |
| GetTextRangeProviderFromTextNode(*end_node, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"last line"); |
| |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Line, |
| /*count*/ -1, |
| /*expected_text*/ L"go blue\nlast line", |
| /*expected_count*/ -1); |
| |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider, |
| TextPatternRangeEndpoint_End, TextUnit_Line, |
| /*count*/ -1, |
| /*expected_text*/ L"go blue", |
| /*expected_count*/ -1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| IFrameTraversal) { |
| LoadInitialAccessibilityTreeFromUrl(embedded_test_server()->GetURL( |
| "/accessibility/html/iframe-cross-process.html")); |
| |
| WaitForAccessibilityTreeToContainNodeWithName(shell()->web_contents(), |
| "Text in iframe"); |
| |
| auto* node = FindNode(ax::mojom::Role::kStaticText, "After frame"); |
| ASSERT_NE(nullptr, node); |
| EXPECT_TRUE(node->IsLeaf()); |
| EXPECT_EQ(0u, node->PlatformChildCount()); |
| |
| ComPtr<ITextRangeProvider> text_range_provider; |
| GetTextRangeProviderFromTextNode(*node, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"After frame"); |
| |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Word, |
| /*count*/ -1, |
| /*expected_text*/ L"iframe\nAfter frame", |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Word, |
| /*count*/ -2, |
| /*expected_text*/ L"Text in iframe\nAfter frame", |
| /*expected_count*/ -2); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider, |
| TextPatternRangeEndpoint_End, TextUnit_Word, |
| /*count*/ -3, |
| /*expected_text*/ L"Text in ", |
| /*expected_count*/ -3); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider, |
| TextPatternRangeEndpoint_End, TextUnit_Word, |
| /*count*/ 2, |
| /*expected_text*/ L"Text in iframe\nAfter ", |
| /*expected_count*/ 2); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Line, |
| /*count*/ 1, |
| /*expected_text*/ L"Text in iframe\nAfter frame", |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Document, |
| /*count*/ 1, |
| /*expected_text*/ L"", |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Character, |
| /*count*/ -18, |
| /*expected_text*/ L"iframe\nAfter frame", |
| /*expected_count*/ -18); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider, |
| TextPatternRangeEndpoint_End, TextUnit_Line, |
| /*count*/ -1, |
| /*expected_text*/ L"iframe", |
| /*expected_count*/ -1); |
| |
| text_range_provider->ExpandToEnclosingUnit(TextUnit_Line); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"Text in iframe"); |
| |
| text_range_provider->ExpandToEnclosingUnit(TextUnit_Document); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, |
| L"Before frame\nText in iframe\nAfter frame"); |
| |
| EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word, |
| /*count*/ 2, |
| /*expected_text*/ L"Text ", |
| /*expected_count*/ 2); |
| EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word, |
| /*count*/ -1, |
| /*expected_text*/ L"frame", |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character, |
| /*count*/ 2, |
| /*expected_text*/ L"frame\nT", |
| /*expected_count*/ 2); |
| EXPECT_UIA_MOVE(text_range_provider, TextUnit_Character, |
| /*count*/ 7, |
| /*expected_text*/ L"e", |
| /*expected_count*/ 7); |
| EXPECT_UIA_MOVE(text_range_provider, TextUnit_Character, |
| /*count*/ 20, |
| /*expected_text*/ L"f", |
| /*expected_count*/ 20); |
| EXPECT_UIA_MOVE(text_range_provider, TextUnit_Character, |
| /*count*/ -8, |
| /*expected_text*/ L"e", |
| /*expected_count*/ -8); |
| EXPECT_UIA_MOVE(text_range_provider, TextUnit_Line, |
| /*count*/ 1, |
| /*expected_text*/ L"After frame", |
| /*expected_count*/ 1); |
| |
| text_range_provider->ExpandToEnclosingUnit(TextUnit_Document); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, |
| L"Before frame\nText in iframe\nAfter frame"); |
| } |
| |
| // TODO(crbug.com/40848898): This test is flaky. |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| DISABLED_OutOfProcessIFrameTraversal) { |
| GURL main_url(embedded_test_server()->GetURL( |
| "a.com", "/accessibility/html/iframe-cross-process.html")); |
| LoadInitialAccessibilityTreeFromUrl(main_url); |
| |
| WaitForAccessibilityTreeToContainNodeWithName(shell()->web_contents(), |
| "Text in iframe"); |
| |
| FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetPrimaryFrameTree() |
| .root(); |
| ASSERT_EQ(1U, root->child_count()); |
| |
| // Navigate oopif to URL. |
| FrameTreeNode* iframe_node = root->child_at(0); |
| GURL iframe_url(embedded_test_server()->GetURL( |
| "b.com", "/accessibility/html/frame/static_text.html")); |
| WebContentsImpl* iframe_web_contents = |
| WebContentsImpl::FromFrameTreeNode(iframe_node); |
| DCHECK(iframe_web_contents); |
| { |
| AccessibilityNotificationWaiter waiter(iframe_web_contents, |
| ui::kAXModeComplete, |
| ax::mojom::Event::kLoadComplete); |
| EXPECT_TRUE(NavigateToURLFromRenderer(iframe_node, iframe_url)); |
| ASSERT_TRUE(waiter.WaitForNotification()); |
| } |
| |
| SynchronizeThreads(); |
| WaitForAccessibilityTreeToContainNodeWithName(shell()->web_contents(), |
| "Text in iframe"); |
| |
| WaitForHitTestData(iframe_node->current_frame_host()); |
| ASSERT_EQ( |
| " Site A ------------ proxies for B\n" |
| " +--Site B ------- proxies for A\n" |
| "Where A = http://a.com/\n" |
| " B = http://b.com/", |
| DepictFrameTree(*root)); |
| |
| auto* node = FindNode(ax::mojom::Role::kStaticText, "After frame"); |
| ASSERT_NE(nullptr, node); |
| EXPECT_TRUE(node->IsLeaf()); |
| EXPECT_EQ(0u, node->PlatformChildCount()); |
| |
| ComPtr<ITextRangeProvider> text_range_provider; |
| GetTextRangeProviderFromTextNode(*node, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"After frame"); |
| |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Word, |
| /*count*/ -1, |
| /*expected_text*/ L"iframe\nAfter frame", |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Word, |
| /*count*/ -2, |
| /*expected_text*/ L"Text in iframe\nAfter frame", |
| /*expected_count*/ -2); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider, |
| TextPatternRangeEndpoint_End, TextUnit_Word, |
| /*count*/ -3, |
| /*expected_text*/ L"Text in ", |
| /*expected_count*/ -3); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider, |
| TextPatternRangeEndpoint_End, TextUnit_Word, |
| /*count*/ 2, |
| /*expected_text*/ L"Text in iframe\nAfter ", |
| /*expected_count*/ 2); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Line, |
| /*count*/ 1, |
| /*expected_text*/ L"Text in iframe\nAfter frame", |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Document, |
| /*count*/ 1, |
| /*expected_text*/ L"", |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Character, |
| /*count*/ -17, |
| /*expected_text*/ L"iframe\nAfter frame", |
| /*expected_count*/ -17); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider, |
| TextPatternRangeEndpoint_End, TextUnit_Line, |
| /*count*/ -1, |
| /*expected_text*/ L"iframe", |
| /*expected_count*/ -1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| ExpandToEnclosingFormat) { |
| LoadInitialAccessibilityTreeFromHtml( |
| R"HTML(<!DOCTYPE html> |
| <html> |
| <body> |
| <div>plain</div> |
| <div>text</div> |
| <div style="font-style: italic">italic<div> |
| <div style="font-style: italic">text<div> |
| </body> |
| </html>)HTML"); |
| |
| auto* node = FindNode(ax::mojom::Role::kStaticText, "plain"); |
| ASSERT_NE(nullptr, node); |
| EXPECT_TRUE(node->IsLeaf()); |
| EXPECT_EQ(0u, node->PlatformChildCount()); |
| |
| ComPtr<ITextRangeProvider> text_range_provider; |
| GetTextRangeProviderFromTextNode(*node, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"plain"); |
| |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Character, |
| /*count*/ 3, |
| /*expected_text*/ L"in", |
| /*expected_count*/ 3); |
| |
| ASSERT_HRESULT_SUCCEEDED( |
| text_range_provider->ExpandToEnclosingUnit(TextUnit_Format)); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"plain\ntext"); |
| |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character, |
| /*count*/ 4, |
| /*expected_text*/ L"plain\ntext\nita", |
| /*expected_count*/ 4); |
| |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Character, |
| /*count*/ 12, |
| /*expected_text*/ L"ta", |
| /*expected_count*/ 12); |
| |
| ASSERT_HRESULT_SUCCEEDED( |
| text_range_provider->ExpandToEnclosingUnit(TextUnit_Format)); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"italic\ntext"); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| ExpandToEnclosingWordWhenBeforeFirstWordBoundary) { |
| LoadInitialAccessibilityTreeFromHtml( |
| R"HTML(<!DOCTYPE html> |
| <html> |
| <body> |
| <p tabindex="0" aria-label="space"> </p> |
| <p>3.14</p> |
| </body> |
| </html>)HTML"); |
| |
| // Case 1: test on degenerate range before whitespace. |
| auto* node = FindNode(ax::mojom::Role::kParagraph, "space") |
| ->PlatformDeepestFirstChild(); |
| ASSERT_NE(nullptr, node); |
| EXPECT_TRUE(node->IsLeaf()); |
| EXPECT_EQ(0u, node->PlatformChildCount()); |
| |
| ComPtr<ITextRangeProvider> text_range_provider; |
| GetTextRangeProviderFromTextNode(*node, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"\xA0"); |
| |
| // Make the range degenerate. |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character, |
| /*count*/ -1, |
| /*expected_text*/ L"", |
| /*expected_count*/ -1); |
| ASSERT_HRESULT_SUCCEEDED( |
| text_range_provider->ExpandToEnclosingUnit(TextUnit_Word)); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"\xA0"); |
| |
| // Case 2: test on range that includes the whitespace and the following word. |
| GetTextRangeProviderFromTextNode(*node, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"\xA0"); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider, |
| TextPatternRangeEndpoint_End, TextUnit_Word, |
| /*count*/ 1, |
| /*expected_text*/ L"\xA0\n3.14", |
| /*expected_count*/ 1); |
| ASSERT_HRESULT_SUCCEEDED( |
| text_range_provider->ExpandToEnclosingUnit(TextUnit_Word)); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"\xA0"); |
| |
| // Case 3: test on degenerate range after whitespace. |
| node = FindNode(ax::mojom::Role::kStaticText, "3.14"); |
| ASSERT_NE(nullptr, node); |
| EXPECT_TRUE(node->IsLeaf()); |
| EXPECT_EQ(0u, node->PlatformChildCount()); |
| |
| GetTextRangeProviderFromTextNode(*node, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"3.14"); |
| // Make the range degenerate. |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider, |
| TextPatternRangeEndpoint_End, TextUnit_Word, |
| /*count*/ -1, |
| /*expected_text*/ L"", |
| /*expected_count*/ -1); |
| ASSERT_HRESULT_SUCCEEDED( |
| text_range_provider->ExpandToEnclosingUnit(TextUnit_Word)); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"3.14"); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| EntireMarkupSuccessiveMoveByCharacter) { |
| AssertMoveByUnitForMarkup( |
| TextUnit_Character, "Test ing.", |
| {L"T", L"e", L"s", L"t", L" ", L"i", L"n", L"g", L"."}); |
| |
| // The text consists of an e acute, and two emoticons. |
| const std::string html = R"HTML(<!DOCTYPE html> |
| <html> |
| <body> |
| <input type="text" value=""> |
| <script> |
| document.querySelector('input').value = 'e\u0301' + |
| '\uD83D\uDC69\u200D\u2764\uFE0F\u200D\uD83D\uDC69' + |
| '\uD83D\uDC36'; |
| </script> |
| </body> |
| </html>)HTML"; |
| AssertMoveByUnitForMarkup( |
| TextUnit_Character, html, |
| {L"e\x0301", L"\xD83D\xDC69\x200D\x2764\xFE0F\x200D\xD83D\xDC69", |
| L"\xD83D\xDC36"}); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| EntireMarkupSuccessiveMoveByWord) { |
| AssertMoveByUnitForMarkup(TextUnit_Word, "This is a test.", |
| {L"This ", L"is ", L"a ", L"test", L"."}); |
| |
| AssertMoveByUnitForMarkup(TextUnit_Word, |
| " This is a test. ", |
| {L"This ", L"is ", L"a ", L"test", L"."}); |
| |
| AssertMoveByUnitForMarkup( |
| TextUnit_Word, "It said: to be continued...", |
| {L"It ", L"said", L": ", L"to ", L"be ", L"continued", L"..."}); |
| |
| AssertMoveByUnitForMarkup(TextUnit_Word, |
| "A <a>link with multiple words</a> and text after.", |
| {L"A ", L"link ", L"with ", L"multiple ", L"words", |
| L"and ", L"text ", L"after", L"."}); |
| |
| AssertMoveByUnitForMarkup(TextUnit_Word, |
| "A <span aria-hidden='true'>span with ignored " |
| "text</span> and text after.", |
| {L"A ", L"and ", L"text ", L"after", L"."}); |
| |
| AssertMoveByUnitForMarkup( |
| TextUnit_Word, "<ol><li>item one</li><li>item two</li></ol>", |
| {L"1", L". ", L"item ", L"one", L"2", L". ", L"item ", L"two"}); |
| |
| AssertMoveByUnitForMarkup(TextUnit_Word, |
| "<ul><li>item one</li><li>item two</li></ul>", |
| {L"• ", L"item ", L"one", L"• ", L"item ", L"two"}); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| EntireMarkupSuccessiveMoveByFormat) { |
| AssertMoveByUnitForMarkup( |
| TextUnit_Format, |
| "plain text <b>bold text <i>bold and italic text</i></b><i><b> more bold " |
| "and italic text</b> italic text</i> plain text", |
| {L"plain text ", L"bold text ", |
| L"bold and italic text more bold and italic text", L" italic text", |
| L" plain text"}); |
| |
| AssertMoveByUnitForMarkup(TextUnit_Format, |
| "before <a href='test'>link</a> after", |
| {L"before ", L"link", L" after"}); |
| |
| AssertMoveByUnitForMarkup(TextUnit_Format, |
| "before <a href='test'><b>link </b></a> after", |
| {L"before ", L"link ", L"after"}); |
| |
| AssertMoveByUnitForMarkup(TextUnit_Format, |
| "before <b><a href='test'>link </a></b> after", |
| {L"before ", L"link ", L"after"}); |
| |
| AssertMoveByUnitForMarkup( |
| TextUnit_Format, "before <a href='test'>link </a> after <b>bold</b>", |
| {L"before ", L"link ", L"after ", L"bold"}); |
| |
| AssertMoveByUnitForMarkup( |
| TextUnit_Format, |
| "before <a style='font-weight:bold' href='test'>link </a> after", |
| {L"before ", L"link ", L"after"}); |
| |
| AssertMoveByUnitForMarkup( |
| TextUnit_Format, |
| "before <a style='font-weight:bold' href='test'>link 1</a><a " |
| "style='font-weight:bold' href='test'>link 2</a> after", |
| {L"before ", L"link 1link 2", L" after"}); |
| |
| AssertMoveByUnitForMarkup( |
| TextUnit_Format, |
| "before <span style='font-weight:bold'>text </span> after", |
| {L"before ", L"text ", L"after"}); |
| |
| AssertMoveByUnitForMarkup( |
| TextUnit_Format, |
| "before <span style='font-weight:bold'>text 1</span><span " |
| "style='font-weight:bold'>text 2</span> after", |
| {L"before ", L"text 1text 2", L" after"}); |
| |
| AssertMoveByUnitForMarkup( |
| TextUnit_Format, |
| "before <span style='font-weight:bold'>bold text</span><span " |
| "style='font-style: italic'>italic text</span> after", |
| {L"before ", L"bold text", L"italic text", L" after"}); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| EntireMarkupSuccessiveMoveByLine) { |
| AssertMoveByUnitForMarkup(TextUnit_Line, "<div style='width:0'>one two</div>", |
| {L"one ", L"two"}); |
| |
| AssertMoveByUnitForMarkup(TextUnit_Line, "line one<br>line two", |
| {L"line one", L"line two"}); |
| |
| AssertMoveByUnitForMarkup(TextUnit_Line, |
| "<div>line one</div><div><div>line two</div></div>", |
| {L"line one", L"line two"}); |
| |
| AssertMoveByUnitForMarkup( |
| TextUnit_Line, "<div style='display:inline-block'>a</div>", {L"a"}); |
| |
| // This makes sure that inline block is not adding a line break |
| AssertMoveByUnitForMarkup( |
| TextUnit_Line, "a<div style='display:inline-block'>b</div>c", {L"abc"}); |
| AssertMoveByUnitForMarkup(TextUnit_Line, |
| "a<div style='display:block'>b</div>c", |
| {L"a", L"b", L"c"}); |
| |
| AssertMoveByUnitForMarkup( |
| TextUnit_Line, |
| "<h1>line one</h1><ul><li>line two</li><li>line three</li></ul>", |
| {L"line one", L"• line two", L"• line three"}); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| ExpandToLineCrossingBoundary) { |
| LoadInitialAccessibilityTreeFromHtml( |
| R"HTML(<!DOCTYPE html> |
| <html> |
| <body> |
| plain text <b>on <i>line</i></b><i><b> one<br> |
| <span>next</span> <span>text</span> </b> on </i>line two<br> |
| line three, |
| </body> |
| </html>)HTML"); |
| |
| BrowserAccessibility* start_of_second_line = |
| FindNode(ax::mojom::Role::kStaticText, "next"); |
| ASSERT_NE(nullptr, start_of_second_line); |
| |
| ComPtr<ITextRangeProvider> text_range_provider; |
| GetTextRangeProviderFromTextNode(*start_of_second_line, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| |
| // Ensure ExpandToEnclosingUnit by Line both moves the start and end endpoints |
| // appropriately (doesn't move to previous line for start and spans multiple |
| // elements). |
| text_range_provider->ExpandToEnclosingUnit(TextUnit_Line); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"next text on line two"); |
| |
| BrowserAccessibility* text_on_second_line = |
| FindNode(ax::mojom::Role::kStaticText, "next"); |
| ASSERT_NE(nullptr, text_on_second_line); |
| |
| GetTextRangeProviderFromTextNode(*text_on_second_line, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| |
| // Ensure ExpandToEnclosingUnit by Line moves the start past the anchor |
| // boundary (but not past the start of the line). |
| text_range_provider->ExpandToEnclosingUnit(TextUnit_Line); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"next text on line two"); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| ExpandToParagraphCrossingBoundary) { |
| LoadInitialAccessibilityTreeFromHtml( |
| R"HTML(<!DOCTYPE html> |
| <html> |
| <body> |
| plain text <b>on <i>line</i></b><i><b> one<br> |
| <span>next</span> <span>text</span> </b> on </i>line two<br> |
| line three, |
| </body> |
| </html>)HTML"); |
| |
| BrowserAccessibility* first_bold_text = |
| FindNode(ax::mojom::Role::kStaticText, "line two"); |
| ASSERT_NE(nullptr, first_bold_text); |
| |
| ComPtr<ITextRangeProvider> text_range_provider; |
| GetTextRangeProviderFromTextNode(*first_bold_text, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| |
| // Ensure ExpandToEnclosingUnit by Line both moves the start and end endpoints |
| // appropriately. |
| text_range_provider->ExpandToEnclosingUnit(TextUnit_Paragraph); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"next text on line two\n"); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| ExpandToPageCrossingBoundary) { |
| LoadInitialAccessibilityTreeFromHtml( |
| R"HTML(<!DOCTYPE html> |
| <html> |
| <body> |
| plain text <b>on <i>line</i></b><i><b> one<br> |
| <span>next</span> <span>text</span> </b> on </i>line two<br> |
| line three, |
| </body> |
| </html>)HTML"); |
| |
| BrowserAccessibility* first_bold_text = |
| FindNode(ax::mojom::Role::kStaticText, "next"); |
| ASSERT_NE(nullptr, first_bold_text); |
| |
| ComPtr<ITextRangeProvider> text_range_provider; |
| GetTextRangeProviderFromTextNode(*first_bold_text, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| |
| // Ensure ExpandToEnclosingUnit by Line both moves the start and end endpoints |
| // appropriately. |
| text_range_provider->ExpandToEnclosingUnit(TextUnit_Page); |
| EXPECT_UIA_TEXTRANGE_EQ( |
| text_range_provider, |
| L"plain text on line one\nnext text on line two\nline three,"); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| MoveByCharacterWithEmbeddedObject) { |
| const std::string html_markup = R"HTML(<!DOCTYPE html> |
| <html> |
| <body> |
| <div> |
| <label for="input1">Some text</label> |
| <input id="input1"> |
| <p>after</p> |
| </div> |
| </body> |
| </html>)HTML"; |
| |
| const std::vector<const wchar_t*> characters = { |
| L"S", |
| L"o", |
| L"m", |
| L"e", |
| L" ", |
| L"t", |
| L"e", |
| L"x", |
| L"t", |
| L"\n", |
| kEmbeddedCharacterAsString.c_str(), |
| L"\n", |
| L"a", |
| L"f", |
| L"t", |
| L"e", |
| L"r"}; |
| |
| AssertMoveByUnitForMarkup(TextUnit_Character, html_markup, characters); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| MoveByWordWithEmbeddedObject) { |
| const std::string html_markup = R"HTML(<!DOCTYPE html> |
| <html> |
| <body> |
| <div> |
| <label for="input1">Some text </label> |
| <input id="input1"> |
| <p> after input</p> |
| <button><div></div></button> |
| </div> |
| </body> |
| </html>)HTML"; |
| |
| const std::vector<const wchar_t*> words = { |
| L"Some ", L"text ", kEmbeddedCharacterAsString.c_str(), |
| L"after ", L"input", kEmbeddedCharacterAsString.c_str()}; |
| |
| AssertMoveByUnitForMarkup(TextUnit_Word, html_markup, words); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| MoveByWordWithDialogAndButton) { |
| const std::string html_markup = R"HTML(<!DOCTYPE html> |
| <html> |
| <body> |
| <h1>One</h1> |
| <h1>Two</h1> |
| <div style="visibility:hidden"> |
| <div role="dialog"> |
| <div> |
| <div> |
| <button aria-label="close" type="button"></button> |
| </div> |
| </div> |
| </div> |
| </div> |
| <h1>Three</h1> |
| </body> |
| </html>)HTML"; |
| |
| const std::vector<const wchar_t*> words = {L"One", L"Two", L"Three"}; |
| |
| AssertMoveByUnitForMarkup(TextUnit_Word, html_markup, words); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| BoundingRectangleOfWordBeforeListItemMarker) { |
| LoadInitialAccessibilityTreeFromHtml( |
| R"HTML(<!DOCTYPE html> |
| <html> |
| <body> |
| <p>Text before list</p> |
| <ul> |
| <li>First list item</li> |
| <li>Second list item</li> |
| </ul> |
| </body> |
| </html>)HTML"); |
| |
| BrowserAccessibility* text_before_list = |
| FindNode(ax::mojom::Role::kStaticText, "Text before list"); |
| ASSERT_NE(nullptr, text_before_list); |
| |
| ComPtr<ITextRangeProvider> text_range_provider; |
| GetTextRangeProviderFromTextNode(*text_before_list, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| |
| EXPECT_UIA_MOVE(text_range_provider, TextUnit_Character, |
| /*count*/ 12, |
| /*expected_text*/ L"l", |
| /*expected_count*/ 12); |
| text_range_provider->ExpandToEnclosingUnit(TextUnit_Word); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"list"); |
| |
| base::win::ScopedSafearray rectangles; |
| ASSERT_HRESULT_SUCCEEDED( |
| text_range_provider->GetBoundingRectangles(rectangles.Receive())); |
| |
| gfx::Vector2dF view_offset(text_before_list->manager() |
| ->GetViewBoundsInScreenCoordinates() |
| .OffsetFromOrigin()); |
| std::vector<double> expected_values = {85 + view_offset.x(), |
| 16 + view_offset.y(), 20, 17}; |
| EXPECT_UIA_DOUBLE_SAFEARRAY_EQ(rectangles.Get(), expected_values); |
| |
| EXPECT_UIA_MOVE(text_range_provider, TextUnit_Character, |
| /*count*/ 19, |
| /*expected_text*/ L"t", |
| /*expected_count*/ 19); |
| text_range_provider->ExpandToEnclosingUnit(TextUnit_Word); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"item"); |
| |
| ASSERT_HRESULT_SUCCEEDED( |
| text_range_provider->GetBoundingRectangles(rectangles.Receive())); |
| expected_values = {105 + view_offset.x(), 50 + view_offset.y(), 28, 17}; |
| EXPECT_UIA_DOUBLE_SAFEARRAY_EQ(rectangles.Get(), expected_values); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| MoveByFormatWithGeneratedContentTableAndSpans) { |
| const std::string html_markup = |
| "<!DOCTYPE html>" |
| "<style>" |
| "h2:before, h2:after { content: \" \"; display: table; }" |
| "span {white-space: pre; }" |
| "</style>" |
| "<div><h2>First Heading</h2><span>\nParagraph One</span></div>" |
| "<div><h2>Second Heading</h2><span>\nParagraph Two</span></div>"; |
| |
| const std::vector<const wchar_t*> format_units = { |
| L"First Heading", L"\nParagraph One", L"Second Heading", |
| L"\nParagraph Two"}; |
| |
| AssertMoveByUnitForMarkup(TextUnit_Format, html_markup, format_units); |
| } |
| |
| // https://crbug.com/1036397 |
| IN_PROC_BROWSER_TEST_F( |
| AXPlatformNodeTextRangeProviderWinBrowserTest, |
| DISABLED_MoveByFormatWithGeneratedContentTableAndParagraphs) { |
| const std::string html_markup = R"HTML(<!DOCTYPE html> |
| <html> |
| <style> |
| h2:before, h2:after { content: " "; display: table; } |
| </style> |
| <div><h2>First Heading</h2><p>Paragraph One</p></div> |
| <div><h2>Second Heading</h2><p>Paragraph Two</p></div> |
| </html>)HTML"; |
| |
| const std::vector<const wchar_t*> format_units = { |
| L"First Heading", L"\nParagraph One", L"Second Heading", |
| L"\nParagraph Two"}; |
| |
| AssertMoveByUnitForMarkup(TextUnit_Format, html_markup, format_units); |
| } |
| |
| // Flaky. |
| // TODO(crbug.com/40721846): Re-enable. |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| DISABLED_IframeSelect) { |
| LoadInitialAccessibilityTreeFromHtmlFilePath( |
| "/accessibility/html/iframe-cross-process.html"); |
| |
| WaitForAccessibilityTreeToContainNodeWithName(shell()->web_contents(), |
| "Text in iframe"); |
| |
| auto* node = FindNode(ax::mojom::Role::kStaticText, "Text in iframe"); |
| ASSERT_NE(nullptr, node); |
| |
| ComPtr<ITextRangeProvider> text_range_provider; |
| GetTextRangeProviderFromTextNode(*node, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"Text in iframe"); |
| |
| // First select text entirely in the iframe. To prevent test timeouts, only |
| // validate the next selection, which spans outside of the iframe. |
| EXPECT_HRESULT_SUCCEEDED(text_range_provider->Select()); |
| |
| // Move the endpoint so it spans outside of the text range and ensure |
| // selection still works. |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Document, |
| /*count*/ 1, |
| /*expected_text*/ L"Text in iframe\nAfter frame", |
| /*expected_count*/ 1); |
| |
| // Validate this selection with a waiter. |
| AccessibilityNotificationWaiter waiter( |
| shell()->web_contents(), ui::kAXModeComplete, |
| ax::mojom::Event::kDocumentSelectionChanged); |
| EXPECT_HRESULT_SUCCEEDED(text_range_provider->Select()); |
| |
| ASSERT_TRUE(waiter.WaitForNotification()); |
| ui::AXSelection selection = node->GetUnignoredSelection(); |
| EXPECT_EQ(selection.anchor_object_id, node->GetId()); |
| EXPECT_EQ(selection.anchor_offset, 0); |
| EXPECT_EQ(selection.focus_object_id, node->GetId()); |
| EXPECT_EQ(selection.focus_offset, 14); |
| |
| // Now move the start position to outside of the iframe and select. |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Document, |
| /*count*/ -1, |
| /*expected_text*/ L"Before frame\nText in iframe\nAfter frame", |
| /*expected_count*/ -1); |
| EXPECT_HRESULT_SUCCEEDED(text_range_provider->Select()); |
| |
| // Now move the end position so it's inside of the iframe. |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character, |
| /*count*/ -12, |
| /*expected_text*/ L"Before frame\nText in ifram", |
| /*expected_count*/ -12); |
| EXPECT_HRESULT_SUCCEEDED(text_range_provider->Select()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| ReplaceStartAndEndEndpointNodeInMultipleTrees) { |
| LoadInitialAccessibilityTreeFromHtmlFilePath( |
| "/accessibility/html/replaced-node-across-trees.html"); |
| auto* web_contents = static_cast<WebContentsImpl*>(shell()->web_contents()); |
| WaitForAccessibilityTreeToContainNodeWithName(web_contents, "Text in iframe"); |
| |
| auto* before_frame_node = |
| FindNode(ax::mojom::Role::kStaticText, "Before frame"); |
| ASSERT_NE(nullptr, before_frame_node); |
| |
| // 1. Test when |start_| and |end_| are both not in the iframe. |
| { |
| ComPtr<ITextRangeProvider> text_range_provider; |
| GetTextRangeProviderFromTextNode(*before_frame_node, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"Before frame"); |
| |
| AccessibilityNotificationWaiter waiter(web_contents); |
| |
| // Updating the style on that particular node is going to invalidate the |
| // leaf text node and will replace it with a new one with the updated style. |
| // We don't care about the style - we use it to trigger a node replacement. |
| EXPECT_TRUE(ExecJs( |
| web_contents, |
| "document.getElementById('s1').style.outline = '1px solid black';")); |
| |
| ASSERT_TRUE(waiter.WaitForNotification()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"Before frame"); |
| } |
| |
| // 2. Test when |start_| is not in the iframe but |end_| is. |
| { |
| ComPtr<ITextRangeProvider> text_range_provider; |
| GetTextRangeProviderFromTextNode(*before_frame_node, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"Before frame"); |
| |
| // Move the range from "<B>efore frame<>" to "<B>efore frame/nText< >" |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Word, |
| /*count*/ 1, |
| /*expected_text*/ L"Before frame\nText ", |
| /*expected_count*/ 1); |
| |
| AccessibilityNotificationWaiter waiter(web_contents); |
| |
| // Updating the style on that particular node is going to invalidate the |
| // leaf text node and will replace it with a new one with the updated style. |
| // We don't care about the style - we use it to trigger a node replacement. |
| EXPECT_TRUE(ExecJs( |
| web_contents, |
| "document.getElementsByTagName('iframe')[0].contentWindow.document." |
| "getElementById('s1').style.outline = '1px solid black';")); |
| |
| ASSERT_TRUE(waiter.WaitForNotification()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"Before frame\nText "); |
| } |
| |
| // 3. Test when |start_| is in the iframe but |end_| is not. |
| { |
| ComPtr<ITextRangeProvider> text_range_provider; |
| auto* after_frame_node = |
| FindNode(ax::mojom::Role::kStaticText, "After frame"); |
| ASSERT_NE(nullptr, after_frame_node); |
| GetTextRangeProviderFromTextNode(*after_frame_node, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"After frame"); |
| |
| // Move the range from "<B>efore frame<>" to "<B>efore frame/nText< >" |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Word, |
| /*count*/ -1, |
| /*expected_text*/ L"iframe\nAfter frame", |
| /*expected_count*/ -1); |
| |
| AccessibilityNotificationWaiter waiter(web_contents, ui::kAXModeComplete, |
| ax::mojom::Event::kEndOfTest); |
| |
| // Updating the style on that particular node is going to invalidate the |
| // leaf text node and will replace it with a new one with the updated style. |
| // We don't care about the style - we use it to trigger a node replacement. |
| EXPECT_TRUE(ExecJs( |
| web_contents, |
| "document.getElementById('s2').style.outline = '1px solid black';")); |
| |
| GetManager()->SignalEndOfTest(); |
| ASSERT_TRUE(waiter.WaitForNotification()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"iframe\nAfter frame"); |
| } |
| } |
| |
| // Test that a page reload removes the AXTreeObserver from the AXTree's |
| // observers list. If it doesn't, this test will crash. |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| ReloadTreeShouldRemoveObserverFromTree) { |
| LoadInitialAccessibilityTreeFromHtmlFilePath( |
| "/accessibility/html/simple_spans.html"); |
| auto* web_contents = static_cast<WebContentsImpl*>(shell()->web_contents()); |
| WaitForAccessibilityTreeToContainNodeWithName(web_contents, "Some text"); |
| |
| // 1. Reload the page and trigger a tree update - this should update the tree |
| // id without modifying the observers from the tree. |
| { |
| auto* node = FindNode(ax::mojom::Role::kStaticText, "Some text"); |
| ASSERT_NE(nullptr, node); |
| |
| ComPtr<ITextRangeProvider> text_range_provider; |
| GetTextRangeProviderFromTextNode(*node, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"Some text"); |
| ui::AXTreeID old_tree_id = GetManager()->GetTreeID(); |
| |
| // Reloading changes the tree id, triggering an AXTreeManager replacement. |
| shell()->Reload(); |
| |
| AccessibilityNotificationWaiter waiter( |
| web_contents, ui::kAXModeComplete, |
| ui::AXEventGenerator::Event::FOCUS_CHANGED); |
| |
| // We do a style change here only to trigger an AXTree update - apparently, |
| // a shell reload doesn't update the tree by itself. |
| EXPECT_TRUE(ExecJs( |
| web_contents, |
| "document.getElementById('s1').style.outline = '1px solid black';")); |
| |
| ASSERT_TRUE(waiter.WaitForNotification()); |
| ASSERT_NE(old_tree_id, GetManager()->GetTreeID()); |
| |
| // |text_range_provider| should now be invalid since it is using nodes |
| // pointing to the previous tree id. If the tree id has not been updated |
| // from the page reload, this should fail. |
| base::win::ScopedSafearray children; |
| ASSERT_UIA_ELEMENTNOTAVAILABLE( |
| text_range_provider->GetChildren(children.Receive())); |
| } |
| |
| // 2. Validate that the observer for the previous range has been removed. Also |
| // test that the new observer has been added correctly. |
| { |
| auto* node = FindNode(ax::mojom::Role::kStaticText, "Some text"); |
| ASSERT_NE(nullptr, node); |
| |
| ComPtr<ITextRangeProvider> text_range_provider; |
| GetTextRangeProviderFromTextNode(*node, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"Some text"); |
| |
| // Make the range span the entire document. |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph, |
| /*count*/ 1, |
| /*expected_text*/ L"Some text 3.14159", |
| /*expected_count*/ 1); |
| |
| AccessibilityNotificationWaiter waiter(web_contents, ui::kAXModeComplete, |
| ax::mojom::Event::kEndOfTest); |
| |
| // We do a style change here only to trigger an AXTree update. |
| EXPECT_TRUE(ExecJs( |
| web_contents, |
| "document.getElementById('s2').style.outline = '1px solid black';")); |
| |
| GetManager()->SignalEndOfTest(); |
| ASSERT_TRUE(waiter.WaitForNotification()); |
| |
| // If the previous observer was not removed correctly, this will cause a |
| // crash. If it was removed correctly and this EXPECT fails, it's likely |
| // because the new observer has not been added as expected. |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"Some text 3.14159"); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| GetAttributeValueNormalizesClonedRange) { |
| LoadInitialAccessibilityTreeFromHtml( |
| R"HTML(<!DOCTYPE html> |
| <html> |
| <body> |
| <p>Text before list</p> |
| <div role="listbox"> |
| <div role="option"><i aria-hidden="true">i</i>One</div> |
| </div> |
| </body> |
| </html>)HTML"); |
| |
| BrowserAccessibility* text_before_list = |
| FindNode(ax::mojom::Role::kStaticText, "Text before list"); |
| ASSERT_NE(nullptr, text_before_list); |
| |
| ComPtr<ITextRangeProvider> text_range_provider; |
| GetTextRangeProviderFromTextNode(*text_before_list, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| |
| EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format, |
| /*count*/ 1, |
| /*expected_text*/ L"One", |
| /*expected_count*/ 1); |
| |
| // GetAttributeValue calls NormalizeTextRange but should not modify the |
| // internal endpoints. However, the value returned by GetAttributeValue should |
| // still be computed from a normalized range. |
| base::win::ScopedVariant value; |
| EXPECT_HRESULT_SUCCEEDED(text_range_provider->GetAttributeValue( |
| UIA_IsItalicAttributeId, value.Receive())); |
| EXPECT_EQ(value.type(), VT_BOOL); |
| EXPECT_EQ(V_BOOL(value.ptr()), 0); |
| |
| // The text should be the same as before since the internal endpoints didn't |
| // move. |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"One"); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| DegenerateRangeBoundingRect) { |
| LoadInitialAccessibilityTreeFromHtmlFilePath( |
| "/accessibility/html/fixed-width-text.html"); |
| |
| BrowserAccessibility* text_node = |
| FindNode(ax::mojom::Role::kStaticText, "Hello,"); |
| ASSERT_NE(nullptr, text_node); |
| EXPECT_TRUE(text_node->IsLeaf()); |
| EXPECT_EQ(0u, text_node->PlatformChildCount()); |
| |
| // |view_offset| is necessary to account for differences in the shell |
| // between platforms (e.g. title bar height) because the results of |
| // |GetBoundingRectangles| are in screen coordinates. |
| gfx::Vector2dF view_offset(text_node->manager() |
| ->GetViewBoundsInScreenCoordinates() |
| .OffsetFromOrigin()); |
| |
| // The offset from top based on CSS style absolute position (200px) + viewport |
| // offset. |
| const double total_top_offset = 216 + view_offset.y(); |
| // The offset from left based on CSS style absolute position (100px) + |
| // viewport offset. |
| const double total_left_offset = 100 + view_offset.x(); |
| |
| // The bounding box for character width and height with font-size: 11px. |
| constexpr double bounding_box_char_height = 16; |
| constexpr double bounding_box_char_width = 32; |
| |
| ComPtr<ITextRangeProvider> text_range_provider; |
| GetTextRangeProviderFromTextNode(*text_node, &text_range_provider); |
| ASSERT_NE(nullptr, text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"Hello,"); |
| base::win::ScopedSafearray rectangles; |
| std::vector<double> expected_values = {total_left_offset, total_top_offset, |
| 6 * bounding_box_char_width, |
| bounding_box_char_height}; |
| EXPECT_HRESULT_SUCCEEDED( |
| text_range_provider->GetBoundingRectangles(rectangles.Receive())); |
| EXPECT_UIA_DOUBLE_SAFEARRAY_EQ(rectangles.Get(), expected_values); |
| |
| // Range spans character "H". |
| // |-| |
| // H e l l o , |
| // W o r l d |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character, |
| /*count*/ -5, |
| /*expected_text*/ L"H", |
| /*expected_count*/ -5); |
| |
| expected_values = {total_left_offset, total_top_offset, |
| bounding_box_char_width, bounding_box_char_height}; |
| EXPECT_HRESULT_SUCCEEDED( |
| text_range_provider->GetBoundingRectangles(rectangles.Receive())); |
| EXPECT_UIA_DOUBLE_SAFEARRAY_EQ(rectangles.Get(), expected_values); |
| |
| // Range is degenerate and position is before "H". |
| // || |
| // H e l l o , |
| // W o r l d |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character, |
| /*count*/ -1, |
| /*expected_text*/ L"", |
| /*expected_count*/ -1); |
| EXPECT_HRESULT_SUCCEEDED( |
| text_range_provider->GetBoundingRectangles(rectangles.Receive())); |
| expected_values = {total_left_offset, total_top_offset, 1, |
| bounding_box_char_height}; |
| EXPECT_UIA_DOUBLE_SAFEARRAY_EQ(rectangles.Get(), expected_values); |
| |
| // Range is degenerate and position is after ",". |
| // || |
| // H e l l o , |
| // W o r l d |
| EXPECT_UIA_MOVE(text_range_provider, TextUnit_Character, |
| /*count*/ 6, |
| /*expected_text*/ L"", |
| /*expected_count*/ 6); |
| EXPECT_HRESULT_SUCCEEDED( |
| text_range_provider->GetBoundingRectangles(rectangles.Receive())); |
| expected_values = {total_left_offset + 6 * bounding_box_char_width, |
| total_top_offset, 1, bounding_box_char_height}; |
| EXPECT_UIA_DOUBLE_SAFEARRAY_EQ(rectangles.Get(), expected_values); |
| |
| // Range spans character ",". |
| // |-| |
| // H e l l o , |
| // W o r l d |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Character, |
| /*count*/ -1, |
| /*expected_text*/ L",", |
| /*expected_count*/ -1); |
| expected_values = {total_left_offset + 5 * bounding_box_char_width, |
| total_top_offset, bounding_box_char_width, |
| bounding_box_char_height}; |
| EXPECT_HRESULT_SUCCEEDED( |
| text_range_provider->GetBoundingRectangles(rectangles.Receive())); |
| EXPECT_UIA_DOUBLE_SAFEARRAY_EQ(rectangles.Get(), expected_values); |
| |
| // Range spans character "\n". |
| // |-| |
| // H e l l o , |
| // W o r l d |
| EXPECT_UIA_MOVE(text_range_provider, TextUnit_Character, |
| /*count*/ 1, |
| /*expected_text*/ L"\n", |
| /*expected_count*/ 1); |
| expected_values = {}; |
| EXPECT_HRESULT_SUCCEEDED( |
| text_range_provider->GetBoundingRectangles(rectangles.Receive())); |
| EXPECT_UIA_DOUBLE_SAFEARRAY_EQ(rectangles.Get(), expected_values); |
| |
| // Range spans character "W". |
| // H e l l o , |
| // W o r l d |
| // |-| |
| EXPECT_UIA_MOVE(text_range_provider, TextUnit_Character, |
| /*count*/ 1, |
| /*expected_text*/ L"W", |
| /*expected_count*/ 1); |
| expected_values = {total_left_offset, |
| total_top_offset + bounding_box_char_height, |
| bounding_box_char_width, bounding_box_char_height}; |
| EXPECT_HRESULT_SUCCEEDED( |
| text_range_provider->GetBoundingRectangles(rectangles.Receive())); |
| EXPECT_UIA_DOUBLE_SAFEARRAY_EQ(rectangles.Get(), expected_values); |
| |
| // Range is degenerate and position is before "W". |
| // H e l l o , |
| // W o r l d |
| // || |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character, |
| /*count*/ -1, |
| /*expected_text*/ L"", |
| /*expected_count*/ -1); |
| expected_values = {total_left_offset, |
| total_top_offset + bounding_box_char_height, 1, |
| bounding_box_char_height}; |
| EXPECT_HRESULT_SUCCEEDED( |
| text_range_provider->GetBoundingRectangles(rectangles.Receive())); |
| EXPECT_UIA_DOUBLE_SAFEARRAY_EQ(rectangles.Get(), expected_values); |
| |
| // Range is degenerate and position is after "d". |
| // H e l l o , |
| // W o r l d |
| // || |
| EXPECT_UIA_MOVE(text_range_provider, TextUnit_Character, |
| /*count*/ 5, |
| /*expected_text*/ L"", |
| /*expected_count*/ 5); |
| expected_values = {total_left_offset + 5 * bounding_box_char_width, |
| total_top_offset + bounding_box_char_height, 1, |
| bounding_box_char_height}; |
| EXPECT_HRESULT_SUCCEEDED( |
| text_range_provider->GetBoundingRectangles(rectangles.Receive())); |
| EXPECT_UIA_DOUBLE_SAFEARRAY_EQ(rectangles.Get(), expected_values); |
| |
| // Range spans character "d". |
| // H e l l o , |
| // W o r l d |
| // |-| |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( |
| text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Character, |
| /*count*/ -1, |
| /*expected_text*/ L"d", |
| /*expected_count*/ -1); |
| expected_values = {total_left_offset + 4 * bounding_box_char_width, |
| total_top_offset + bounding_box_char_height, |
| bounding_box_char_width, bounding_box_char_height}; |
| EXPECT_HRESULT_SUCCEEDED( |
| text_range_provider->GetBoundingRectangles(rectangles.Receive())); |
| EXPECT_UIA_DOUBLE_SAFEARRAY_EQ(rectangles.Get(), expected_values); |
| } |
| |
| // TODO(crbug.com/340389557): This test is flaky. |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| DISABLED_TextDeletedInTextFieldAdjustmentNeeded) { |
| // This test, tests a scenario where an AT is used to make a deletion on some |
| // text and then manually moves the caret around: On the input "hello world |
| // red green<> blue" where the caret position is noted by <>: |
| // 1. The AT creates a degenerate text range provider on the caret position |
| // 2. The AT selects "world" and then simulates a backspace to delete the |
| // word. |
| // 3. The AT moves the caret back to the original position (right after |
| // "green") |
| LoadInitialAccessibilityTreeFromHtml( |
| R"HTML(<input id='input' type='text' value='hello world red green blue'/>)HTML"); |
| |
| auto* node = |
| FindNode(ax::mojom::Role::kTextField, "hello world red green blue"); |
| ASSERT_NE(nullptr, node); |
| |
| ComPtr<ITextRangeProvider> original_text_range_provider; |
| GetTextRangeProviderFromTextNode(*node, &original_text_range_provider); |
| ASSERT_NE(nullptr, original_text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(original_text_range_provider, |
| L"hello world red green blue"); |
| |
| ComPtr<ITextRangeProvider> deletion_text_range_provider; |
| GetTextRangeProviderFromTextNode(*node, &deletion_text_range_provider); |
| ASSERT_NE(nullptr, deletion_text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(deletion_text_range_provider, |
| L"hello world red green blue"); |
| |
| base::win::ScopedBstr find_string(L"world"); |
| EXPECT_HRESULT_SUCCEEDED(original_text_range_provider->FindText( |
| find_string.Get(), false, false, &deletion_text_range_provider)); |
| |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(original_text_range_provider, |
| TextPatternRangeEndpoint_Start, |
| TextUnit_Word, |
| /*count*/ 1, |
| /*expected_text*/ L"world red green blue", |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(original_text_range_provider, |
| TextPatternRangeEndpoint_Start, |
| TextUnit_Word, |
| /*count*/ 1, |
| /*expected_text*/ L"red green blue", |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(original_text_range_provider, |
| TextPatternRangeEndpoint_Start, |
| TextUnit_Word, |
| /*count*/ 1, |
| /*expected_text*/ L"green blue", |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(original_text_range_provider, |
| TextPatternRangeEndpoint_End, TextUnit_Word, |
| /*count*/ -1, |
| /*expected_text*/ L"green ", |
| /*expected_count*/ -1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(original_text_range_provider, |
| TextPatternRangeEndpoint_Start, |
| TextUnit_Character, |
| /*count*/ 5, |
| /*expected_text*/ L" ", |
| /*expected_count*/ 5); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(original_text_range_provider, |
| TextPatternRangeEndpoint_End, |
| TextUnit_Character, |
| /*count*/ -1, |
| /*expected_text*/ L"", |
| /*expected_count*/ -1); |
| |
| // At this point, `original_text_range_provider` should be a degenerate range |
| // acting as the caret here: "hello red green<> blue" and |
| // `deletion_text_range_provider` should have "<w>orld<>". |
| |
| EXPECT_HRESULT_SUCCEEDED(deletion_text_range_provider->Select()); |
| |
| // Now we delete "world". |
| AccessibilityNotificationWaiter waiter(shell()->web_contents()); |
| SimulateKeyPress(shell()->web_contents(), ui::DomKey::BACKSPACE, |
| ui::DomCode::BACKSPACE, ui::VKEY_BACK, false, false, false, |
| false); |
| ASSERT_TRUE(waiter.WaitForNotification()); |
| |
| // Since our deletion text range provider is also an observer to the deletion |
| // event, it will be set to nullposition after the deletion, since by design |
| // we return nullpositions when the deletion range encompasses the observing |
| // endpoints. As such, we create this textrange provider that mimmicks where |
| // blink would set the selection after the deletion is done. |
| ComPtr<ITextRangeProvider> blink_selection_text_range_provider; |
| GetTextRangeProviderFromTextNode(*node, &blink_selection_text_range_provider); |
| ASSERT_NE(nullptr, blink_selection_text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(blink_selection_text_range_provider, |
| L"hello red green blue"); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(blink_selection_text_range_provider, |
| TextPatternRangeEndpoint_Start, |
| TextUnit_Character, |
| /*count*/ 6, |
| /*expected_text*/ L" red green blue", |
| /*expected_count*/ 6); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(blink_selection_text_range_provider, |
| TextPatternRangeEndpoint_End, |
| TextUnit_Character, |
| /*count*/ -15, |
| /*expected_text*/ L"", |
| /*expected_count*/ -15); |
| |
| // Now we move the `deletion` text range to the original, which was the |
| // original caret position. |
| EXPECT_HRESULT_SUCCEEDED( |
| blink_selection_text_range_provider->MoveEndpointByRange( |
| TextPatternRangeEndpoint_Start, original_text_range_provider.Get(), |
| TextPatternRangeEndpoint_Start)); |
| EXPECT_HRESULT_SUCCEEDED( |
| blink_selection_text_range_provider->MoveEndpointByRange( |
| TextPatternRangeEndpoint_End, original_text_range_provider.Get(), |
| TextPatternRangeEndpoint_End)); |
| |
| EXPECT_UIA_TEXTRANGE_EQ(blink_selection_text_range_provider, L""); |
| |
| // Since the original caret position was right after "green" if we now expand |
| // the resulting range we would expect to have "green" here as well. |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(blink_selection_text_range_provider, |
| TextPatternRangeEndpoint_Start, |
| TextUnit_Character, |
| /*count*/ -5, |
| /*expected_text*/ L"green", |
| /*expected_count*/ -5); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, |
| TextDeletedInTextFieldAdjustmentNeededNodeDeleted) { |
| // This test, tests a scenario where an AT is used to make a deletion on some |
| // text and then manually moves the caret around: On the input "go hello |
| // <>blue" where the caret position is noted by <>: This covers the scenario |
| // where the deleted text actually deletes a node in the DOM. |
| // 1. The AT creates a degenerate text range provider on the caret position |
| // 2. The AT selects "hello" and then simulates a backspace to delete the |
| // word. |
| // 3. The AT moves the caret back to the original position (right before |
| // "blue") |
| LoadInitialAccessibilityTreeFromHtml( |
| R"HTML(<div id='input' contenteditable='true'>go <span>hello</span> blue<div/>)HTML"); |
| |
| auto* node = FindNode(ax::mojom::Role::kGenericContainer, "go hello blue"); |
| ASSERT_NE(nullptr, node); |
| |
| ComPtr<ITextRangeProvider> original_text_range_provider; |
| GetTextRangeProviderFromTextNode(*node, &original_text_range_provider); |
| ASSERT_NE(nullptr, original_text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(original_text_range_provider, L"go hello blue"); |
| |
| ComPtr<ITextRangeProvider> deletion_text_range_provider; |
| GetTextRangeProviderFromTextNode(*node, &deletion_text_range_provider); |
| ASSERT_NE(nullptr, deletion_text_range_provider.Get()); |
| EXPECT_UIA_TEXTRANGE_EQ(deletion_text_range_provider, L"go hello blue"); |
| |
| base::win::ScopedBstr find_string(L"hello"); |
| EXPECT_HRESULT_SUCCEEDED(original_text_range_provider->FindText( |
| find_string.Get(), false, false, &deletion_text_range_provider)); |
| |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(original_text_range_provider, |
| TextPatternRangeEndpoint_Start, |
| TextUnit_Word, |
| /*count*/ 1, |
| /*expected_text*/ L"hello blue", |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(original_text_range_provider, |
| TextPatternRangeEndpoint_Start, |
| TextUnit_Word, |
| /*count*/ 1, |
| /*expected_text*/ L"blue", |
| /*expected_count*/ 1); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(original_text_range_provider, |
| TextPatternRangeEndpoint_End, |
| TextUnit_Character, |
| /*count*/ -4, |
| /*expected_text*/ L"", |
| /*expected_count*/ -4); |
| |
| // At this point, `original_text_range_provider` should be a degenerate range |
| // acting as the caret here: "go hello <>blue" and |
| // `deletion_text_range_provider` should have "<h>ello<>". |
| |
| AccessibilityNotificationWaiter sel_waiter( |
| shell()->web_contents(), ui::kAXModeComplete, |
| ax::mojom::Event::kDocumentSelectionChanged); |
| |
| EXPECT_HRESULT_SUCCEEDED(deletion_text_range_provider->Select()); |
| |
| ASSERT_TRUE(sel_waiter.WaitForNotification()); |
| |
| // Now we delete "hello". |
| AccessibilityNotificationWaiter waiter(shell()->web_contents()); |
| SimulateKeyPress(shell()->web_contents(), ui::DomKey::BACKSPACE, |
| ui::DomCode::BACKSPACE, ui::VKEY_BACK, /* control */ false, |
| /* shift */ false, /* alt */ false, |
| /* command */ false); |
| ASSERT_TRUE(waiter.WaitForNotification()); |
| |
| // Since our deletion text range provider is also an observer to the deletion |
| // event, it will be set to nullposition after the deletion, since by design |
| // we return nullpositions when the deletion range encompasses the observing |
| // endpoints. As such, we create this textrange provider that mimmicks where |
| // blink would set the selection after the deletion is done. |
| ComPtr<ITextRangeProvider> blink_selection_text_range_provider; |
| GetTextRangeProviderFromTextNode(*node, &blink_selection_text_range_provider); |
| ASSERT_NE(nullptr, blink_selection_text_range_provider.Get()); |
| wchar_t text[11] = L"go "; |
| wchar_t non_breaking_space[2] = L"\xA0"; |
| wchar_t blue[5] = L"blue"; |
| wcscat(text, non_breaking_space); |
| wcscat(text, blue); |
| wchar_t text_2[7] = L"\xA0"; |
| wcscat(text_2, blue); |
| EXPECT_UIA_TEXTRANGE_EQ(blink_selection_text_range_provider, text); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(blink_selection_text_range_provider, |
| TextPatternRangeEndpoint_Start, |
| TextUnit_Character, |
| /*count*/ 3, |
| /*expected_text*/ text_2, |
| /*expected_count*/ 3); |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(blink_selection_text_range_provider, |
| TextPatternRangeEndpoint_End, |
| TextUnit_Character, |
| /*count*/ -5, |
| /*expected_text*/ L"", |
| /*expected_count*/ -5); |
| |
| // Now we move the 'deletion' text range to the original, which was the |
| // original caret position. |
| EXPECT_HRESULT_SUCCEEDED( |
| blink_selection_text_range_provider->MoveEndpointByRange( |
| TextPatternRangeEndpoint_Start, original_text_range_provider.Get(), |
| TextPatternRangeEndpoint_Start)); |
| EXPECT_HRESULT_SUCCEEDED( |
| blink_selection_text_range_provider->MoveEndpointByRange( |
| TextPatternRangeEndpoint_End, original_text_range_provider.Get(), |
| TextPatternRangeEndpoint_End)); |
| |
| EXPECT_UIA_TEXTRANGE_EQ(blink_selection_text_range_provider, L""); |
| |
| // Since the original caret position was right after "blue" if we now expand |
| // the resulting range we would expect to have "blue" here as well, and the |
| // full text would be "go blue". |
| EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(blink_selection_text_range_provider, |
| TextPatternRangeEndpoint_End, |
| TextUnit_Character, |
| /*count*/ 4, |
| /*expected_text*/ L"blue", |
| /*expected_count*/ 4); |
| } |
| |
| } // namespace content |