chromium/chrome/browser/pdf/pdf_extension_accessibility_test.cc

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

#include <stddef.h>

#include <map>
#include <set>
#include <string>
#include <string_view>
#include <tuple>
#include <variant>
#include <vector>

#include "base/containers/contains.h"
#include "base/containers/flat_set.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "base/test/with_feature_override.h"
#include "base/threading/thread_restrictions.h"
#include "build/branding_buildflags.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/accessibility/accessibility_state_utils.h"
#include "chrome/browser/pdf/pdf_extension_test_base.h"
#include "chrome/browser/pdf/pdf_extension_test_util.h"
#include "chrome/browser/renderer_context_menu/render_view_context_menu_browsertest_util.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/metrics/content/subprocess_metrics_provider.h"
#include "components/prefs/pref_service.h"
#include "components/ukm/test_ukm_recorder.h"
#include "components/zoom/zoom_controller.h"
#include "content/public/browser/ax_inspect_factory.h"
#include "content/public/browser/browser_accessibility_state.h"
#include "content/public/browser/web_contents.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/context_menu_interceptor.h"
#include "content/public/test/scoped_accessibility_mode_override.h"
#include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "pdf/pdf_features.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/screen_ai/buildflags/buildflags.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/context_menu_data/untrustworthy_context_menu_params.h"
#include "ui/accessibility/accessibility_features.h"
#include "ui/accessibility/accessibility_switches.h"
#include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/ax_enum_util.h"
#include "ui/accessibility/ax_features.mojom-features.h"
#include "ui/accessibility/ax_mode.h"
#include "ui/accessibility/ax_node.h"
#include "ui/accessibility/ax_tree.h"
#include "ui/accessibility/ax_tree_id.h"
#include "ui/accessibility/platform/ax_platform_node_delegate.h"
#include "ui/accessibility/platform/inspect/ax_api_type.h"
#include "ui/accessibility/platform/inspect/ax_inspect_scenario.h"
#include "ui/accessibility/platform/inspect/ax_inspect_test_helper.h"
#include "url/gurl.h"

#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "chrome/browser/ash/accessibility/accessibility_manager.h"
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

// Fake ScreenAI library returns empty results for all queries, so testing with
// it is not helpful.
#if BUILDFLAG(ENABLE_SCREEN_AI_BROWSERTESTS) && !BUILDFLAG(USE_FAKE_SCREEN_AI)
#define PDF_OCR_INTEGRATION_TEST_ENABLED
#endif

#if defined(PDF_OCR_INTEGRATION_TEST_ENABLED)
#include "base/scoped_observation.h"
#include "chrome/browser/screen_ai/screen_ai_install_state.h"
#include "chrome/browser/screen_ai/screen_ai_service_router.h"
#include "chrome/browser/screen_ai/screen_ai_service_router_factory.h"
#include "components/strings/grit/components_strings.h"
#include "services/screen_ai/public/cpp/utilities.h"
#include "ui/base/l10n/l10n_util.h"
#endif  // defined(PDF_OCR_INTEGRATION_TEST_ENABLED)

namespace {

WebContents;
MimeHandlerViewGuest;

std::string DumpPdfAccessibilityTree(const ui::AXTreeUpdate& ax_tree,
                                     bool skip_status_subtree) {}

constexpr char kExpectedPDFAXTree[] =;

}  // namespace

// Using ASSERT_TRUE deliberately instead of ASSERT_EQ or ASSERT_STREQ
// in order to print a more readable message if the strings differ.
#define ASSERT_MULTILINE_STREQ(expected, actual)

class PDFExtensionAccessibilityTest : public PDFExtensionTestBase {};

class PDFExtensionAccessibilityTestWithOopifOverride
    : public base::test::WithFeatureOverride,
      public PDFExtensionAccessibilityTest {};

// The test is flaky on Mac: https://crbug.com/334099836.
#if BUILDFLAG(IS_MAC)
#define MAYBE_PdfAccessibility
#else
#define MAYBE_PdfAccessibility
#endif
IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTestWithOopifOverride,
                       MAYBE_PdfAccessibility) {}

// The test is flaky on Mac: https://crbug.com/334099836.
#if BUILDFLAG(IS_MAC)
#define MAYBE_PdfAccessibilityEnableLater
#else
#define MAYBE_PdfAccessibilityEnableLater
#endif
IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTestWithOopifOverride,
                       MAYBE_PdfAccessibilityEnableLater) {}

IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTestWithOopifOverride,
                       PdfAccessibilityInIframe) {}

IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTestWithOopifOverride,
                       PdfAccessibilityInOOPIF) {}

// Flaky on ChromiumOS MSan. See https://crbug.com/1484869.
// Flaky on Mac: https://crbug.com/334099836.
#if (BUILDFLAG(IS_CHROMEOS) && defined(MEMORY_SANITIZER)) || BUILDFLAG(IS_MAC)
#define MAYBE_PdfAccessibilityWordBoundaries
#else
#define MAYBE_PdfAccessibilityWordBoundaries
#endif
IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTestWithOopifOverride,
                       MAYBE_PdfAccessibilityWordBoundaries) {}

IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTestWithOopifOverride,
                       PdfAccessibilitySelection) {}

// TODO(crbug.com/330202391): Fix the flakiness on Windows.
#if BUILDFLAG(IS_WIN)
#define MAYBE_PdfAccessibilityContextMenuAction
#else
#define MAYBE_PdfAccessibilityContextMenuAction
#endif  // BUILDFLAG(IS_WIN)
IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTestWithOopifOverride,
                       MAYBE_PdfAccessibilityContextMenuAction) {}

IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTestWithOopifOverride,
                       RecordHasAccessibleTextToUmaWithAccessiblePdf) {}

IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTestWithOopifOverride,
                       RecordInaccessiblePdfUKM) {}

IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTestWithOopifOverride,
                       RecordHasAccessibleTextToUmaWithInaccessible) {}

#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
// Test a particular PDF encountered in the wild that triggered a crash
// when accessibility is enabled.  (http://crbug.com/668724)
IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTestWithOopifOverride,
                       PdfAccessibilityTextRunCrash) {
  content::ScopedAccessibilityModeOverride mode_override(ui::kAXModeComplete);
  ASSERT_TRUE(LoadPdf(embedded_test_server()->GetURL(
      "/pdf_private/accessibility_crash_2.pdf")));

  WaitForAccessibilityTreeToContainNodeWithName(GetActiveWebContents(),
                                                "Page 1");
}
#endif

// This test suite does a simple text-extraction based on the accessibility
// internals, breaking lines & paragraphs where appropriate.  Unlike
// TreeDumpTests, this allows us to verify the kNextOnLine and kPreviousOnLine
// relationships.
class PDFExtensionAccessibilityTextExtractionTest
    : public PDFExtensionAccessibilityTestWithOopifOverride {};

// Test that Previous/NextOnLineId attributes are present and properly linked on
// InlineTextBoxes within a line.
IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTextExtractionTest,
                       NextOnLine) {}

// Test that a drop-cap is grouped with the correct line.
IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTextExtractionTest, DropCap) {}

// Test that simulated superscripts and subscripts don't cause a line break.
IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTextExtractionTest,
                       SuperscriptSubscript) {}

// Test that simple font and font-size changes in the middle of a line don't
// cause line breaks.
IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTextExtractionTest,
                       FontChange) {}

// Test one property of pdf_private/accessibility_crash_2.pdf, where a page has
// only whitespace characters.
IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTextExtractionTest,
                       OnlyWhitespaceText) {}

// Test data of inline text boxes for PDF with weblinks.
IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTextExtractionTest, WebLinks) {}

// Test data of inline text boxes for PDF with highlights.
IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTextExtractionTest,
                       Highlights) {}

// Test data of inline text boxes for PDF with text fields.
IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTextExtractionTest,
                       TextFields) {}

// Test data of inline text boxes for PDF with multi-line and various font-sized
// text.
IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTextExtractionTest,
                       ParagraphsAndHeadingUntagged) {}

// Test data of inline text boxes for PDF with text, weblinks, images and
// annotation links.
IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTextExtractionTest,
                       LinksImagesAndText) {}

// Test data of inline text boxes for PDF with overlapping annotations.
IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTextExtractionTest,
                       OverlappingAnnots) {}

class PDFExtensionAccessibilityTreeDumpTest
    : public PDFExtensionAccessibilityTest,
      public ::testing::WithParamInterface<
          std::tuple<ui::AXApiType::Type, bool>> {};

// Constructs a list of accessibility tests, one for each accessibility tree
// formatter testpasses.
const std::vector<ui::AXApiType::Type> GetAXTestValues() {}

struct PDFExtensionAccessibilityTreeDumpTestPassToString {};

INSTANTIATE_TEST_SUITE_P();

IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTreeDumpTest, HelloWorld) {}

IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTreeDumpTest,
                       ParagraphsAndHeadingUntagged) {}

IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTreeDumpTest, MultiPage) {}

IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTreeDumpTest,
                       DirectionalTextRuns) {}

IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTreeDumpTest, TextDirection) {}

IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTreeDumpTest, WebLinks) {}

IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTreeDumpTest,
                       OverlappingLinks) {}

IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTreeDumpTest, Highlights) {}

IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTreeDumpTest, TextFields) {}

IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTreeDumpTest, Images) {}

IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTreeDumpTest,
                       LinksImagesAndText) {}

IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTreeDumpTest,
                       TextRunStyleHeuristic) {}

IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTreeDumpTest, TextStyle) {}

// TODO(crbug.com/40745411)
IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTreeDumpTest, XfaFields) {}

// This test suite validates the navigation done using the accessibility client.
PDFExtensionAccessibilityNavigationTest;

// TODO(crbug.com/40934115): Fix the flakiness on ChromeOS.
#if BUILDFLAG(IS_CHROMEOS)
#define MAYBE_LinkNavigation
#else
#define MAYBE_LinkNavigation
#endif  // BUILDFLAG(IS_CHROMEOS)
IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityNavigationTest,
                       MAYBE_LinkNavigation) {}

// TODO(crbug.com/289010799): Revisit using `crosapi` in `PdfOcrUmaTest` for
// Lacros.
#if !BUILDFLAG(IS_CHROMEOS_LACROS)
// This test suite contains simple tests for the PDF OCR feature.
class PdfOcrUmaTest : public PDFExtensionAccessibilityTest,
                      public ::testing::WithParamInterface<bool> {};

IN_PROC_BROWSER_TEST_P(PdfOcrUmaTest, CheckOpenedWithScreenReader) {}

#if BUILDFLAG(IS_CHROMEOS_ASH)
IN_PROC_BROWSER_TEST_P(PdfOcrUmaTest, CheckOpenedWithSelectToSpeak) {
  // TODO(crbug.com/289010799): Remove this once the metrics are added for OOPIF
  // PDF.
  if (UseOopif()) {
    GTEST_SKIP();
  }

  ::ash::AccessibilityManager::Get()->SetSelectToSpeakEnabled(true);

  base::HistogramTester histograms;
  histograms.ExpectUniqueSample(
      "Accessibility.PDF.OpenedWithSelectToSpeak.PdfOcr", true,
      /*expected_bucket_count=*/0);

  ASSERT_TRUE(LoadPdf(embedded_test_server()->GetURL("/pdf/test.pdf")));

  WebContents* contents = GetActiveWebContents();
  content::RenderFrameHost* extension_host =
      pdf_extension_test_util::GetOnlyPdfExtensionHost(contents);
  ASSERT_TRUE(extension_host);

  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
  histograms.ExpectUniqueSample(
      "Accessibility.PDF.OpenedWithSelectToSpeak.PdfOcr", true,
      /*expected_bucket_count=*/1);
}

IN_PROC_BROWSER_TEST_P(PdfOcrUmaTest,
                       CheckSelectToSpeakPagesOcredWithAccessiblePdf) {
  // TODO(crbug.com/289010799): Remove this once the metrics are added for OOPIF
  // PDF.
  if (UseOopif()) {
    GTEST_SKIP();
  }

  ::ash::AccessibilityManager::Get()->SetSelectToSpeakEnabled(true);

  base::HistogramTester histograms;
  histograms.ExpectTotalCount(
      "Accessibility.PdfOcr.CrosSelectToSpeak.PagesOcred",
      /*expected_count=*/0);

  ASSERT_TRUE(LoadPdf(embedded_test_server()->GetURL("/pdf/test.pdf")));

  WebContents* contents = GetActiveWebContents();
  content::RenderFrameHost* extension_host =
      pdf_extension_test_util::GetOnlyPdfExtensionHost(contents);
  ASSERT_TRUE(extension_host);

  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
  // The metric should record nothing for accessible PDFs.
  histograms.ExpectTotalCount(
      "Accessibility.PdfOcr.CrosSelectToSpeak.PagesOcred",
      /*expected_count=*/0);
}
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

INSTANTIATE_TEST_SUITE_P();
#endif  // !BUILDFLAG(IS_CHROMEOS_LACROS)

// TODO(crbug.com/40268279): Stop testing both modes after OOPIF PDF viewer
// launches.
INSTANTIATE_FEATURE_OVERRIDE_TEST_SUITE();
INSTANTIATE_FEATURE_OVERRIDE_TEST_SUITE();
INSTANTIATE_FEATURE_OVERRIDE_TEST_SUITE();

#if defined(PDF_OCR_INTEGRATION_TEST_ENABLED)

class PdfOcrIntegrationTest
    : public PDFExtensionAccessibilityTest,
      public screen_ai::ScreenAIInstallState::Observer,
      public ::testing::WithParamInterface<std::tuple<bool, bool, bool>> {};

IN_PROC_BROWSER_TEST_P(PdfOcrIntegrationTest, EnsureScreenAIInitializes) {}

IN_PROC_BROWSER_TEST_P(PdfOcrIntegrationTest, HelloWorld) {}

IN_PROC_BROWSER_TEST_P(PdfOcrIntegrationTest, ThreePagePDF) {}

IN_PROC_BROWSER_TEST_P(PdfOcrIntegrationTest, TestBatchingWithTwentyPagePDF) {}

IN_PROC_BROWSER_TEST_P(PdfOcrIntegrationTest, NoOcrResultOnBlankImagePdf) {}

INSTANTIATE_TEST_SUITE_P();

#endif  // defined(PDF_OCR_INTEGRATION_TEST_ENABLED)