chromium/content/browser/accessibility/hit_testing_win_browsertest.cc

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CONTENT_BROWSER_ACCESSIBILITY_HIT_TESTING_WIN_BROWSERTEST_H_
#define CONTENT_BROWSER_ACCESSIBILITY_HIT_TESTING_WIN_BROWSERTEST_H_

#include <objbase.h>

#include <wrl/client.h>

#include "base/test/scoped_feature_list.h"
#include "base/win/scoped_variant.h"
#include "content/browser/accessibility/hit_testing_browsertest.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/shell/browser/shell.h"
#include "ui/accessibility/accessibility_features.h"
#include "ui/accessibility/platform/browser_accessibility.h"
#include "ui/accessibility/platform/browser_accessibility_manager.h"

#include <uiautomation.h>

using Microsoft::WRL::ComPtr;

namespace content {

#define EXPECT_ACCESSIBILITY_WIN_HIT_TEST_RESULT(css_point, expected_unknown, \
                                                 hit_unknown)                 \
  SCOPED_TRACE(GetScopedTrace(css_point));                                    \
  EXPECT_EQ(expected_unknown, hit_unknown);

class AccessibilityHitTestingWinBrowserTest
    : public AccessibilityHitTestingBrowserTest {
 public:
  ComPtr<IAccessible> GetWebContentRootIAccessible() {
    ComPtr<IAccessible> content_root;
    GetRootBrowserAccessibilityManager()
        ->GetBrowserAccessibilityRoot()
        ->GetNativeViewAccessible()
        ->QueryInterface(IID_PPV_ARGS(&content_root));
    return content_root;
  }

  ComPtr<IRawElementProviderFragmentRoot> GetWebContentFragmentRoot() {
    ComPtr<IRawElementProviderFragment> content_root;
    GetWebContentRootIAccessible().As(&content_root);
    ComPtr<IRawElementProviderFragmentRoot> fragment_root;
    content_root->get_FragmentRoot(&fragment_root);
    return fragment_root;
  }

  ComPtr<ITextProvider> GetWebContentRootTextProvider() {
    ComPtr<IRawElementProviderSimple> content_root;
    GetWebContentRootIAccessible().As(&content_root);
    ComPtr<ITextProvider> text_provider;
    content_root->GetPatternProvider(UIA_TextPatternId, &text_provider);
    return text_provider;
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_{::features::kUiaProvider};
};

INSTANTIATE_TEST_SUITE_P(
    All,
    AccessibilityHitTestingWinBrowserTest,
    ::testing::Values(1, 2),
    AccessibilityHitTestingBrowserTest::TestPassToString());

IN_PROC_BROWSER_TEST_P(AccessibilityHitTestingWinBrowserTest, AccHitTest) {
  ASSERT_TRUE(embedded_test_server()->Start());

  EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL)));

  AccessibilityNotificationWaiter waiter(shell()->web_contents(),
                                         ui::kAXModeComplete,
                                         ax::mojom::Event::kLoadComplete);
  GURL url(embedded_test_server()->GetURL(
      "/accessibility/hit_testing/simple_rectangles.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url));
  ASSERT_TRUE(waiter.WaitForNotification());

  WaitForAccessibilityTreeToContainNodeWithName(shell()->web_contents(),
                                                "rectA");

  ComPtr<IAccessible> root_accessible = GetWebContentRootIAccessible();

  // Test a hit on a rect in the main frame.
  {
    gfx::Point rect_2_point(49, 20);
    gfx::Point rect_2_point_physical = CSSToPhysicalPixelPoint(rect_2_point);
    base::win::ScopedVariant hit_variant;
    ASSERT_HRESULT_SUCCEEDED(root_accessible->accHitTest(
        rect_2_point_physical.x(), rect_2_point_physical.y(),
        hit_variant.Receive()));
    ASSERT_EQ(hit_variant.type(), VT_DISPATCH);
    ComPtr<IAccessible> hit_accessible;
    ASSERT_HRESULT_SUCCEEDED(hit_variant.ptr()->pdispVal->QueryInterface(
        IID_PPV_ARGS(&hit_accessible)));
    ui::BrowserAccessibility* expected_node =
        FindNode(ax::mojom::Role::kGenericContainer, "rect2");
    ComPtr<IAccessible> expected_accessible;
    ASSERT_HRESULT_SUCCEEDED(
        expected_node->GetNativeViewAccessible()->QueryInterface(
            IID_PPV_ARGS(&expected_accessible)));
    EXPECT_ACCESSIBILITY_WIN_HIT_TEST_RESULT(
        rect_2_point, expected_accessible.Get(), hit_accessible.Get());
  }

  // Test a hit on a rect in the iframe.
  {
    gfx::Point rect_b_point(79, 79);
    gfx::Point rect_b_point_physical = CSSToPhysicalPixelPoint(rect_b_point);
    base::win::ScopedVariant hit_variant;
    ASSERT_HRESULT_SUCCEEDED(root_accessible->accHitTest(
        rect_b_point_physical.x(), rect_b_point_physical.y(),
        hit_variant.Receive()));
    ASSERT_EQ(hit_variant.type(), VT_DISPATCH);
    ComPtr<IAccessible> hit_accessible;
    ASSERT_HRESULT_SUCCEEDED(hit_variant.ptr()->pdispVal->QueryInterface(
        IID_PPV_ARGS(&hit_accessible)));
    ui::BrowserAccessibility* expected_node =
        FindNode(ax::mojom::Role::kGenericContainer, "rectB");
    ComPtr<IAccessible> expected_accessible;
    ASSERT_HRESULT_SUCCEEDED(
        expected_node->GetNativeViewAccessible()->QueryInterface(
            IID_PPV_ARGS(&expected_accessible)));
    EXPECT_ACCESSIBILITY_WIN_HIT_TEST_RESULT(
        rect_b_point, expected_accessible.Get(), hit_accessible.Get());
  }
}

IN_PROC_BROWSER_TEST_P(AccessibilityHitTestingWinBrowserTest,
                       ElementProviderFromPoint) {
  ASSERT_TRUE(embedded_test_server()->Start());

  EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL)));

  AccessibilityNotificationWaiter waiter(shell()->web_contents(),
                                         ui::kAXModeComplete,
                                         ax::mojom::Event::kLoadComplete);
  GURL url(embedded_test_server()->GetURL(
      "/accessibility/hit_testing/simple_rectangles.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url));
  ASSERT_TRUE(waiter.WaitForNotification());

  WaitForAccessibilityTreeToContainNodeWithName(shell()->web_contents(),
                                                "rectA");

  ComPtr<IRawElementProviderFragmentRoot> fragment_root =
      GetWebContentFragmentRoot();

  // Test a hit on a rect in the main frame.
  {
    gfx::Point rect_2_point(49, 20);
    gfx::Point rect_2_point_physical = CSSToPhysicalPixelPoint(rect_2_point);
    ComPtr<IRawElementProviderFragment> hit_fragment;
    ASSERT_HRESULT_SUCCEEDED(fragment_root->ElementProviderFromPoint(
        rect_2_point_physical.x(), rect_2_point_physical.y(), &hit_fragment));
    ui::BrowserAccessibility* expected_node =
        FindNode(ax::mojom::Role::kGenericContainer, "rect2");
    ComPtr<IRawElementProviderFragment> expected_fragment;
    ASSERT_HRESULT_SUCCEEDED(
        expected_node->GetNativeViewAccessible()->QueryInterface(
            IID_PPV_ARGS(&expected_fragment)));
    EXPECT_ACCESSIBILITY_WIN_HIT_TEST_RESULT(
        rect_2_point, expected_fragment.Get(), hit_fragment.Get());
  }

  // Test a hit on a rect in the iframe.
  {
    gfx::Point rect_b_point(79, 79);
    gfx::Point rect_b_point_physical = CSSToPhysicalPixelPoint(rect_b_point);
    ComPtr<IRawElementProviderFragment> hit_fragment;
    ASSERT_HRESULT_SUCCEEDED(fragment_root->ElementProviderFromPoint(
        rect_b_point_physical.x(), rect_b_point_physical.y(), &hit_fragment));
    ui::BrowserAccessibility* expected_node =
        FindNode(ax::mojom::Role::kGenericContainer, "rectB");
    ComPtr<IRawElementProviderFragment> expected_fragment;
    ASSERT_HRESULT_SUCCEEDED(
        expected_node->GetNativeViewAccessible()->QueryInterface(
            IID_PPV_ARGS(&expected_fragment)));
    EXPECT_EQ(hit_fragment.Get(), expected_fragment.Get());
    EXPECT_ACCESSIBILITY_WIN_HIT_TEST_RESULT(
        rect_b_point, expected_fragment.Get(), hit_fragment.Get());
  }
}

IN_PROC_BROWSER_TEST_P(AccessibilityHitTestingWinBrowserTest,
                       TextProviderRangeFromPoint) {
  ASSERT_TRUE(embedded_test_server()->Start());

  EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL)));

  AccessibilityNotificationWaiter waiter(shell()->web_contents(),
                                         ui::kAXModeComplete,
                                         ax::mojom::Event::kLoadComplete);
  GURL url(embedded_test_server()->GetURL(
      "/accessibility/hit_testing/text_ranges.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url));
  ASSERT_TRUE(waiter.WaitForNotification());

  WaitForAccessibilityTreeToContainNodeWithName(shell()->web_contents(),
                                                "rectA");

  ComPtr<ITextProvider> text_provider = GetWebContentRootTextProvider();

  // Test a hit on a text in the main frame.
  {
    gfx::Point rect_2_point(70, 20);
    gfx::Point rect_2_point_physical = CSSToPhysicalPixelPoint(rect_2_point);
    UiaPoint uia_point;
    uia_point.x = rect_2_point_physical.x();
    uia_point.y = rect_2_point_physical.y();
    ComPtr<ITextRangeProvider> hit_text_range;
    ASSERT_HRESULT_SUCCEEDED(
        text_provider->RangeFromPoint(uia_point, &hit_text_range));
    ASSERT_HRESULT_SUCCEEDED(
        hit_text_range->ExpandToEnclosingUnit(TextUnit_Character));
    ui::BrowserAccessibility* expected_node =
        FindNode(ax::mojom::Role::kGenericContainer, "rect2");
    ComPtr<IRawElementProviderSimple> expected_provider;
    ASSERT_HRESULT_SUCCEEDED(
        expected_node->GetNativeViewAccessible()->QueryInterface(
            IID_PPV_ARGS(&expected_provider)));
    ComPtr<ITextRangeProvider> expected_text_range;
    ASSERT_HRESULT_SUCCEEDED(text_provider->RangeFromChild(
        expected_provider.Get(), &expected_text_range));

    SCOPED_TRACE(GetScopedTrace(rect_2_point));
    BOOL compare_result;
    ASSERT_HRESULT_SUCCEEDED(
        hit_text_range->Compare(expected_text_range.Get(), &compare_result));
    EXPECT_TRUE(compare_result);
  }

  // Test a hit on a text in the iframe.
  {
    gfx::Point rect_b_point(100, 100);
    gfx::Point rect_b_point_physical = CSSToPhysicalPixelPoint(rect_b_point);
    UiaPoint uia_point;
    uia_point.x = rect_b_point_physical.x();
    uia_point.y = rect_b_point_physical.y();
    ComPtr<ITextRangeProvider> hit_text_range;
    ASSERT_HRESULT_SUCCEEDED(
        text_provider->RangeFromPoint(uia_point, &hit_text_range));
    ASSERT_HRESULT_SUCCEEDED(
        hit_text_range->ExpandToEnclosingUnit(TextUnit_Character));
    ui::BrowserAccessibility* expected_node =
        FindNode(ax::mojom::Role::kGenericContainer, "rectB");
    ComPtr<IRawElementProviderSimple> expected_provider;
    ASSERT_HRESULT_SUCCEEDED(
        expected_node->GetNativeViewAccessible()->QueryInterface(
            IID_PPV_ARGS(&expected_provider)));
    ComPtr<ITextRangeProvider> expected_text_range;
    ASSERT_HRESULT_SUCCEEDED(text_provider->RangeFromChild(
        expected_provider.Get(), &expected_text_range));

    SCOPED_TRACE(GetScopedTrace(rect_b_point));
    BOOL compare_result;
    ASSERT_HRESULT_SUCCEEDED(
        hit_text_range->Compare(expected_text_range.Get(), &compare_result));
    EXPECT_TRUE(compare_result);
  }
}

}  // namespace content

#endif  // CONTENT_BROWSER_ACCESSIBILITY_HIT_TESTING_WIN_BROWSERTEST_H_