910e62b5创建于 1月15日历史提交
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/test/run_until.h"
#include "base/test/scoped_run_loop_timeout.h"
#include "build/build_config.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_view_base.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/browser_accessibility_state.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/url_constants.h"
#include "content/public/test/accessibility_notification_waiter.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/scoped_accessibility_mode_override.h"
#include "content/shell/browser/shell.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/accessibility/ax_mode.h"
#include "ui/accessibility/platform/browser_accessibility.h"
#include "ui/accessibility/platform/browser_accessibility_manager.h"

namespace content {

const char kMinimalPageDataURL[] =
    "data:text/html,<html><head></head><body>Hello, world</body></html>";

class AccessibilityModeTest : public ContentBrowserTest {
 protected:
  WebContentsImpl* web_contents() {
    return static_cast<WebContentsImpl*>(shell()->web_contents());
  }

  const ui::BrowserAccessibility* FindNode(ax::mojom::Role role,
                                           const std::string& name) {
    const ui::BrowserAccessibility* root =
        GetManager()->GetBrowserAccessibilityRoot();
    CHECK(root);
    return FindNodeInSubtree(*root, role, name);
  }

  ui::BrowserAccessibilityManager* GetManager() {
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
    return web_contents->GetRootBrowserAccessibilityManager();
  }

 private:
  const ui::BrowserAccessibility* FindNodeInSubtree(
      const ui::BrowserAccessibility& node,
      ax::mojom::Role role,
      const std::string& name) {
    if (node.GetRole() == role &&
        node.GetStringAttribute(ax::mojom::StringAttribute::kName) == name) {
      return &node;
    }
    for (unsigned int i = 0; i < node.PlatformChildCount(); ++i) {
      const ui::BrowserAccessibility* result =
          FindNodeInSubtree(*node.PlatformGetChild(i), role, name);
      if (result) {
        return result;
      }
    }
    return nullptr;
  }
};

IN_PROC_BROWSER_TEST_F(AccessibilityModeTest, AccessibilityModeOff) {
  EXPECT_TRUE(NavigateToURL(shell(), GURL(kMinimalPageDataURL)));
  EXPECT_TRUE(web_contents()->GetAccessibilityMode().is_mode_off());
  EXPECT_EQ(nullptr, GetManager());
}

IN_PROC_BROWSER_TEST_F(AccessibilityModeTest, AccessibilityModeComplete) {
  EXPECT_TRUE(NavigateToURL(shell(), GURL(kMinimalPageDataURL)));
  ASSERT_TRUE(web_contents()->GetAccessibilityMode().is_mode_off());

  AccessibilityNotificationWaiter waiter(shell()->web_contents());
  ScopedAccessibilityModeOverride scoped_accessibility_mode(
      web_contents(), ui::kAXModeComplete);
  EXPECT_EQ(web_contents()->GetAccessibilityMode(), ui::kAXModeComplete);
  ASSERT_TRUE(waiter.WaitForNotification());
  EXPECT_NE(nullptr, GetManager());
}

// Tests that adding kAXModeComplete via BrowserAccessibilityState gives the
// flags to an active WebContents.
IN_PROC_BROWSER_TEST_F(AccessibilityModeTest,
                       AccessibilityModeCompleteViaContent) {
  EXPECT_TRUE(NavigateToURL(shell(), GURL(kMinimalPageDataURL)));
  ASSERT_TRUE(web_contents()->GetAccessibilityMode().is_mode_off());

  AccessibilityNotificationWaiter waiter(shell()->web_contents());
  ScopedAccessibilityModeOverride complete(ui::kAXModeComplete);
  ASSERT_TRUE(waiter.WaitForNotification());
  EXPECT_EQ(web_contents()->GetAccessibilityMode(), ui::kAXModeComplete);
  EXPECT_NE(nullptr, GetManager());
}

IN_PROC_BROWSER_TEST_F(AccessibilityModeTest,
                       AccessibilityModeWebContentsOnly) {
  EXPECT_TRUE(NavigateToURL(shell(), GURL(kMinimalPageDataURL)));
  ASSERT_TRUE(web_contents()->GetAccessibilityMode().is_mode_off());

  AccessibilityNotificationWaiter waiter(shell()->web_contents());
  ScopedAccessibilityModeOverride scoped_accessibility_mode(
      web_contents(), ui::kAXModeWebContentsOnly);

  EXPECT_EQ(web_contents()->GetAccessibilityMode(), ui::kAXModeWebContentsOnly);
  ASSERT_TRUE(waiter.WaitForNotification());
  EXPECT_EQ(nullptr, GetManager());
}

IN_PROC_BROWSER_TEST_F(AccessibilityModeTest, AddingModes) {
  EXPECT_TRUE(NavigateToURL(shell(), GURL(kMinimalPageDataURL)));

  AccessibilityNotificationWaiter waiter(shell()->web_contents());
  ScopedAccessibilityModeOverride scoped_accessibility_mode(
      web_contents(), ui::kAXModeWebContentsOnly);
  EXPECT_EQ(web_contents()->GetAccessibilityMode(), ui::kAXModeWebContentsOnly);
  ASSERT_TRUE(waiter.WaitForNotification());
  EXPECT_EQ(nullptr, GetManager());

  AccessibilityNotificationWaiter waiter2(shell()->web_contents());
  ScopedAccessibilityModeOverride scoped_accessibility_mode2(
      web_contents(), ui::kAXModeComplete);
  EXPECT_EQ(web_contents()->GetAccessibilityMode(), ui::kAXModeComplete);
  ASSERT_TRUE(waiter2.WaitForNotification());
  EXPECT_NE(nullptr, GetManager());
}

IN_PROC_BROWSER_TEST_F(AccessibilityModeTest,
                       FullAccessibilityHasInlineTextBoxes) {
  // TODO(dmazzoni): On Android we use an ifdef to disable inline text boxes,
  // we should do it with accessibility flags instead. http://crbug.com/672205
#if !BUILDFLAG(IS_ANDROID)
  EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL)));

  AccessibilityNotificationWaiter waiter(shell()->web_contents(),
                                         ax::mojom::Event::kLoadComplete);
  ScopedAccessibilityModeOverride scoped_accessibility_mode(
      web_contents(), ui::kAXModeComplete);
  GURL url("data:text/html,<p>Para</p>");
  EXPECT_TRUE(NavigateToURL(shell(), url));
  ASSERT_TRUE(waiter.WaitForNotification());

  const ui::BrowserAccessibility* text =
      FindNode(ax::mojom::Role::kStaticText, "Para");
  ASSERT_NE(nullptr, text);
  ASSERT_EQ(1U, text->InternalChildCount());
  ui::BrowserAccessibility* inline_text = text->InternalGetChild(0);
  ASSERT_NE(nullptr, inline_text);
  EXPECT_EQ(ax::mojom::Role::kInlineTextBox, inline_text->GetRole());
#endif  // !BUILDFLAG(IS_ANDROID)
}

IN_PROC_BROWSER_TEST_F(AccessibilityModeTest,
                       MinimalAccessibilityModeHasNoInlineTextBoxes) {
  // TODO(dmazzoni): On Android we use an ifdef to disable inline text boxes,
  // we should do it with accessibility flags instead. http://crbug.com/672205
#if !BUILDFLAG(IS_ANDROID)
  EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL)));

  AccessibilityNotificationWaiter waiter(
      shell()->web_contents(),
      ax::mojom::Event::kLoadComplete);
  ScopedAccessibilityModeOverride basic(ui::kAXModeBasic);
  GURL url("data:text/html,<p>Para</p>");
  EXPECT_TRUE(NavigateToURL(shell(), url));
  ASSERT_TRUE(waiter.WaitForNotification());

  const ui::BrowserAccessibility* text =
      FindNode(ax::mojom::Role::kStaticText, "Para");
  ASSERT_NE(nullptr, text);
  EXPECT_EQ(0U, text->InternalChildCount());
#endif  // !BUILDFLAG(IS_ANDROID)
}

IN_PROC_BROWSER_TEST_F(AccessibilityModeTest, AddScreenReaderModeFlag) {
  EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL)));

  AccessibilityNotificationWaiter waiter(
      shell()->web_contents(),
      ax::mojom::Event::kLoadComplete);
  ScopedAccessibilityModeOverride basic(ui::kAXModeBasic);
  GURL url("data:text/html,<input aria-label=Foo placeholder=Bar>");
  EXPECT_TRUE(NavigateToURL(shell(), url));
  ASSERT_TRUE(waiter.WaitForNotification());

  const ui::BrowserAccessibility* textbox =
      FindNode(ax::mojom::Role::kTextField, "Foo");
  ASSERT_NE(nullptr, textbox);
  EXPECT_FALSE(
      textbox->HasStringAttribute(ax::mojom::StringAttribute::kPlaceholder));
  int original_id = textbox->GetId();

  AccessibilityNotificationWaiter waiter2(shell()->web_contents(),
                                          ax::mojom::Event::kLoadComplete);
  ScopedAccessibilityModeOverride ax_mode_override(
      ui::AXMode::kExtendedProperties);
  ASSERT_TRUE(waiter2.WaitForNotification());

  const ui::BrowserAccessibility* textbox2 =
      FindNode(ax::mojom::Role::kTextField, "Foo");
  ASSERT_NE(nullptr, textbox2);
  EXPECT_TRUE(
      textbox2->HasStringAttribute(ax::mojom::StringAttribute::kPlaceholder));
  EXPECT_EQ(original_id, textbox2->GetId());
}

IN_PROC_BROWSER_TEST_F(AccessibilityModeTest, TestEngineUseHistograms) {
  // Check that we are starting with AXMode, so that we don't fail if a11y was
  // already on when the test starts, e.g. in Android Automotive.
  if (!BrowserAccessibilityState::GetInstance()
           ->GetAccessibilityMode()
           .is_mode_off()) {
    return;
  }

  base::HistogramTester histograms;
  histograms.ExpectTotalCount("Accessibility.EngineUse.PageNavsUntilStart", 0);
  histograms.ExpectTotalCount("Accessibility.EngineUse.TimeUntilStart", 0);

  EXPECT_TRUE(NavigateToURL(shell(), GURL(kMinimalPageDataURL)));

  // We only consider it a start when AXMode::kWebContents is set.
  ScopedAccessibilityModeOverride native_apis(ui::AXMode::kNativeAPIs);
  histograms.ExpectTotalCount("Accessibility.EngineUse.PageNavsUntilStart", 0);
  histograms.ExpectTotalCount("Accessibility.EngineUse.TimeUntilStart", 0);

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

  // This is considered a start of the engine (the Blink a11y pipeline).
  ScopedAccessibilityModeOverride web_contents(ui::AXMode::kWebContents);
  ASSERT_TRUE(base::test::RunUntil([&]() {
    return histograms
               .GetAllSamples("Accessibility.EngineUse.PageNavsUntilStart")
               .empty() == false;
  }));

  histograms.ExpectTotalCount("Accessibility.EngineUse.PageNavsUntilStart", 1);
  histograms.ExpectUniqueSample("Accessibility.EngineUse.PageNavsUntilStart", 2,
                                1);
  histograms.ExpectTotalCount("Accessibility.EngineUse.TimeUntilStart", 1);
}

IN_PROC_BROWSER_TEST_F(AccessibilityModeTest,
                       ReEnablingAccessibilityDoesNotTimeout) {
  EXPECT_TRUE(NavigateToURL(shell(), GURL(kMinimalPageDataURL)));
  ASSERT_TRUE(web_contents()->GetAccessibilityMode().is_mode_off());

  AccessibilityNotificationWaiter waiter(shell()->web_contents());
  ScopedAccessibilityModeOverride scoped_accessibility_mode(
      web_contents(), ui::kAXModeWebContentsOnly);
  EXPECT_EQ(web_contents()->GetAccessibilityMode(), ui::kAXModeWebContentsOnly);
  ASSERT_TRUE(waiter.WaitForNotification());
  EXPECT_EQ(nullptr, GetManager());

  AccessibilityNotificationWaiter waiter2(shell()->web_contents());
  ScopedAccessibilityModeOverride scoped_accessibility_mode2(
      web_contents(), ui::kAXModeComplete);
  EXPECT_EQ(web_contents()->GetAccessibilityMode(), ui::kAXModeComplete);
  ASSERT_TRUE(waiter2.WaitForNotification());
  EXPECT_NE(nullptr, GetManager());
}

// Test platform node ids on OS's that have platform nodes.
#if !BUILDFLAG(IS_CHROMEOS) && !BUILDFLAG(IS_ANDROID)
IN_PROC_BROWSER_TEST_F(AccessibilityModeTest, ReEnablingDoesNotAlterUniqueIds) {
  ASSERT_TRUE(NavigateToURL(shell(), GURL(R"HTML(
      data:text/html,<!DOCTYPE html>
      <html>
      <body>
        <button>Button 1</button>
        <iframe srcdoc="
          <!DOCTYPE html>
          <html>
          <body>
            <button>Button 2</button>
          </body>
          </html>
        "></iframe>
        <button>Button 3</button>
      </body>
      </html>)HTML")));

  // Turn accessibility on.
  AccessibilityNotificationWaiter waiter(shell()->web_contents());
  std::optional<ScopedAccessibilityModeOverride> ax_mode(ui::kAXModeComplete);
  ASSERT_TRUE(waiter.WaitForNotification());
  WaitForAccessibilityTreeToContainNodeWithName(shell()->web_contents(),
                                                "Button 2");

  // Save unique ids.
  auto accessibility_mode = web_contents()->GetAccessibilityMode();
  ASSERT_TRUE(accessibility_mode.has_mode(ui::AXMode::kNativeAPIs));
  ASSERT_TRUE(accessibility_mode.has_mode(ui::AXMode::kWebContents));
  EXPECT_NE(nullptr, GetManager());
  const ui::BrowserAccessibility* button_1 =
      FindNode(ax::mojom::Role::kButton, "Button 1");
  ASSERT_NE(nullptr, button_1);
  const ui::BrowserAccessibility* button_2 =
      FindNode(ax::mojom::Role::kButton, "Button 2");
  ASSERT_NE(nullptr, button_2);

  int32_t unique_id_1 = button_1->GetAXPlatformNode()->GetUniqueId();
  int32_t unique_id_2 = button_2->GetAXPlatformNode()->GetUniqueId();

  // Turn accessibility off again.
  ax_mode.reset();
  accessibility_mode = web_contents()->GetAccessibilityMode();
  ASSERT_TRUE(accessibility_mode.is_mode_off());
  EXPECT_EQ(nullptr, GetManager());

  // Turn accessibility on again.
  AccessibilityNotificationWaiter waiter_3(shell()->web_contents());
  ax_mode.emplace(ui::kAXModeBasic);
  ASSERT_TRUE(waiter_3.WaitForNotification());
  WaitForAccessibilityTreeToContainNodeWithName(shell()->web_contents(),
                                                "Button 2");

  // Compare unique id for newly created a11y nodes with previous unique ids.
  accessibility_mode = web_contents()->GetAccessibilityMode();
  ASSERT_TRUE(accessibility_mode.has_mode(ui::AXMode::kNativeAPIs));
  ASSERT_TRUE(accessibility_mode.has_mode(ui::AXMode::kWebContents));
  EXPECT_NE(nullptr, GetManager());
  const ui::BrowserAccessibility* button_1_refresh =
      FindNode(ax::mojom::Role::kButton, "Button 1");
  ASSERT_NE(nullptr, button_1_refresh);
  // button_1 is now a dangling pointer for the old button.
  // The pointers are not the same, proving that button_1_refresh is new.
  ASSERT_NE(button_1, button_1_refresh);
  const ui::BrowserAccessibility* button_2_refresh =
      FindNode(ax::mojom::Role::kButton, "Button 2");
  ASSERT_NE(nullptr, button_2_refresh);
  // button_2 is now a dangling pointer for the old button.
  // The pointers are not the same, proving that button_2_refresh is new.
  ASSERT_NE(button_2, button_2_refresh);

  EXPECT_EQ(unique_id_1, button_1_refresh->GetAXPlatformNode()->GetUniqueId());
  EXPECT_EQ(unique_id_2, button_2_refresh->GetAXPlatformNode()->GetUniqueId());
}
#endif  // #if !BUILDFLAG(IS_CHROMEOS) && !BUILDFLAG(IS_ANDROID)

}  // namespace content