910e62b5创建于 1月15日历史提交
// 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 <map>
#include <optional>

#include "base/check.h"
#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/functional/callback_helpers.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "chrome/browser/accessibility/accessibility_labels_service.h"
#include "chrome/browser/accessibility/accessibility_labels_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/language/core/browser/pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/user_prefs/user_prefs.h"
#include "content/public/browser/browser_accessibility_state.h"
#include "content/public/browser/browser_context.h"
#include "content/public/common/content_features.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/scoped_accessibility_mode_override.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver_set.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/request_handler_util.h"
#include "services/image_annotation/public/cpp/image_processor.h"
#include "services/image_annotation/public/mojom/image_annotation.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/accessibility/accessibility_features.h"
#include "ui/accessibility/ax_enum_util.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node.h"
#include "ui/accessibility/ax_tree.h"
#include "url/gurl.h"

constexpr base::FilePath::CharType kDocRoot[] =
    FILE_PATH_LITERAL("chrome/test/data");

namespace {

void DescribeNodesWithAnnotations(const ui::AXNode& node,
                                  std::vector<std::string>* descriptions) {
  std::string annotation =
      node.GetStringAttribute(ax::mojom::StringAttribute::kImageAnnotation);
  if (!annotation.empty()) {
    std::string role_str = ui::ToString(node.GetRole());
    std::string name =
        node.GetStringAttribute(ax::mojom::StringAttribute::kName);
    if (!name.empty() && node.GetRole() != ax::mojom::Role::kRootWebArea)
      descriptions->push_back(role_str + " " + name + " " + annotation);
    else
      descriptions->push_back(role_str + " " + annotation);
  }
  for (const ui::AXNode* child : node.children()) {
    DescribeNodesWithAnnotations(*child, descriptions);
  }
}

std::vector<std::string> DescribeNodesWithAnnotations(
    const ui::AXTreeUpdate& tree_update) {
  std::vector<std::string> descriptions;
  if (tree_update.root_id) {
    ui::AXTree tree(tree_update);
    DescribeNodesWithAnnotations(*tree.root(), &descriptions);
  }
  return descriptions;
}

bool HasNodeWithAnnotationStatus(const ui::AXTreeUpdate& tree_update,
                                 ax::mojom::ImageAnnotationStatus status) {
  for (const auto& node_data : tree_update.nodes) {
    if (node_data.GetImageAnnotationStatus() == status)
      return true;
  }
  return false;
}

// A fake implementation of the Annotator mojo interface that
// returns predictable results based on the filename of the image
// it's asked to annotate. Enables us to test the rest of the
// system without using the real annotator that queries a back-end
// API.
class FakeAnnotator : public image_annotation::mojom::Annotator {
 public:
  static void SetReturnOcrResults(bool ocr) { return_ocr_results_ = ocr; }

  static void SetReturnLabelResults(bool label) {
    return_label_results_ = label;
  }

  static void AddCustomLabelResultMapping(const std::string& filename,
                                          const std::string& label) {
    custom_label_result_mapping_[filename] = label;
  }

  static void SetReturnErrorCode(
      image_annotation::mojom::AnnotateImageError error_code) {
    return_error_code_ = error_code;
  }

  FakeAnnotator() = default;

  FakeAnnotator(const FakeAnnotator&) = delete;
  FakeAnnotator& operator=(const FakeAnnotator&) = delete;

  ~FakeAnnotator() override = default;

  void BindReceiver(
      mojo::PendingReceiver<image_annotation::mojom::Annotator> receiver) {
    receivers_.Add(this, std::move(receiver));
  }

  void AnnotateImage(
      const std::string& image_id,
      const std::string& description_language_tag,
      mojo::PendingRemote<image_annotation::mojom::ImageProcessor>
          image_processor,
      AnnotateImageCallback callback) override {
    if (return_error_code_) {
      image_annotation::mojom::AnnotateImageResultPtr result =
          image_annotation::mojom::AnnotateImageResult::NewErrorCode(
              *return_error_code_);
      std::move(callback).Run(std::move(result));
      return;
    }

    processors_.emplace_back(std::move(image_processor));
    processors_.back()->GetJpgImageData(base::BindOnce(
        &FakeAnnotator::OnJpgImageDataReceived, base::Unretained(this),
        std::move(image_id), std::move(description_language_tag),
        std::move(callback)));
  }

  void OnJpgImageDataReceived(const std::string& image_id,
                              const std::string& description_language_tag,
                              AnnotateImageCallback callback,
                              const std::vector<uint8_t>& image_bytes,
                              const int32_t width,
                              const int32_t height) {
    if (image_bytes.empty()) {
      std::move(callback).Run(
          image_annotation::mojom::AnnotateImageResult::NewErrorCode(
              image_annotation::mojom::AnnotateImageError::kFailure));
      return;
    }

    // Use the filename to create annotation strings. Check a map from filename
    // to desired label, otherwise just construct a string based on the
    // filename. Adds some trailing whitespace and punctuation to check that
    // clean-up happens correctly when combining annotation strings.
    std::string image_filename = GURL(image_id).ExtractFileName();
    std::string label_text;
    if (base::Contains(custom_label_result_mapping_, image_filename)) {
      label_text = custom_label_result_mapping_[image_filename];
    } else {
      label_text = image_filename + " '" + description_language_tag + "' Label";
    }
    std::string ocr_text = image_filename + " Annotation . ";

    image_annotation::mojom::AnnotationPtr ocr_annotation =
        image_annotation::mojom::Annotation::New(
            image_annotation::mojom::AnnotationType::kOcr, 1.0, ocr_text);
    image_annotation::mojom::AnnotationPtr label_annotation =
        image_annotation::mojom::Annotation::New(
            image_annotation::mojom::AnnotationType::kLabel, 1.0, label_text);

    // Return enabled results as an annotation.
    std::vector<image_annotation::mojom::AnnotationPtr> annotations;
    if (return_ocr_results_)
      annotations.push_back(std::move(ocr_annotation));
    if (return_label_results_)
      annotations.push_back(std::move(label_annotation));

    image_annotation::mojom::AnnotateImageResultPtr result =
        image_annotation::mojom::AnnotateImageResult::NewAnnotations(
            std::move(annotations));
    std::move(callback).Run(std::move(result));
  }

 private:
  mojo::ReceiverSet<image_annotation::mojom::Annotator> receivers_;
  std::vector<mojo::Remote<image_annotation::mojom::ImageProcessor>>
      processors_;
  static bool return_ocr_results_;
  static bool return_label_results_;
  static std::map<std::string, std::string> custom_label_result_mapping_;
  static std::optional<image_annotation::mojom::AnnotateImageError>
      return_error_code_;
};

// static
bool FakeAnnotator::return_ocr_results_ = false;
// static
bool FakeAnnotator::return_label_results_ = false;
// static
std::map<std::string, std::string> FakeAnnotator::custom_label_result_mapping_;
// static
std::optional<image_annotation::mojom::AnnotateImageError>
    FakeAnnotator::return_error_code_;

// The fake ImageAnnotationService, which handles mojo calls from the renderer
// process and passes them to FakeAnnotator.
class FakeImageAnnotationService
    : public image_annotation::mojom::ImageAnnotationService {
 public:
  FakeImageAnnotationService() = default;

  FakeImageAnnotationService(const FakeImageAnnotationService&) = delete;
  FakeImageAnnotationService& operator=(const FakeImageAnnotationService&) =
      delete;

  ~FakeImageAnnotationService() override = default;

 private:
  // image_annotation::mojom::ImageAnnotationService:
  void BindAnnotator(mojo::PendingReceiver<image_annotation::mojom::Annotator>
                         receiver) override {
    annotator_.BindReceiver(std::move(receiver));
  }

  FakeAnnotator annotator_;
};

void BindImageAnnotatorService(
    mojo::PendingReceiver<image_annotation::mojom::ImageAnnotationService>
        receiver) {
  mojo::MakeSelfOwnedReceiver(std::make_unique<FakeImageAnnotationService>(),
                              std::move(receiver));
}

}  // namespace

class ImageAnnotationBrowserTest : public InProcessBrowserTest {
 public:
  ImageAnnotationBrowserTest()
      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {
    https_server_.AddDefaultHandlers(base::FilePath(kDocRoot));
  }

  ImageAnnotationBrowserTest(const ImageAnnotationBrowserTest&) = delete;
  ImageAnnotationBrowserTest& operator=(const ImageAnnotationBrowserTest&) =
      delete;

 protected:
  void SetUpOnMainThread() override {
    InProcessBrowserTest::SetUpOnMainThread();

    ASSERT_TRUE(https_server_.Start());

    content::WebContents* web_contents =
        browser()->tab_strip_model()->GetActiveWebContents();

    AccessibilityLabelsServiceFactory::GetForProfile(browser()->profile())
        ->OverrideImageAnnotatorBinderForTesting(
            base::BindRepeating(&BindImageAnnotatorService));

    scoped_accessibility_mode_.emplace(
        web_contents, ui::kAXModeComplete | ui::AXMode::kLabelImages);

    SetAcceptLanguages("en,fr");
  }

  void TearDownOnMainThread() override {
    scoped_accessibility_mode_.reset();
    AccessibilityLabelsServiceFactory::GetForProfile(browser()->profile())
        ->OverrideImageAnnotatorBinderForTesting(base::NullCallback());
    InProcessBrowserTest::TearDownOnMainThread();
  }

  void SetAcceptLanguages(const std::string& accept_languages) {
    content::BrowserContext* context =
        static_cast<content::BrowserContext*>(browser()->profile());
    DCHECK(context);

    PrefService* prefs = user_prefs::UserPrefs::Get(context);
    DCHECK(prefs);

    prefs->Set(language::prefs::kSelectedLanguages,
               base::Value(accept_languages));
  }

 protected:
  net::EmbeddedTestServer https_server_;
  std::optional<content::ScopedAccessibilityModeOverride>
      scoped_accessibility_mode_;

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

IN_PROC_BROWSER_TEST_F(ImageAnnotationBrowserTest,
                       AnnotateImageInAccessibilityTree) {
  FakeAnnotator::SetReturnOcrResults(true);
  FakeAnnotator::SetReturnLabelResults(true);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL("/accessibility/image_annotation.html")));

  content::WebContents* web_contents =
      browser()->tab_strip_model()->GetActiveWebContents();

  content::WaitForAccessibilityTreeToContainNodeWithName(
      web_contents,
      "Appears to say: red.png Annotation. Appears to be: red.png 'en' Label");
}

IN_PROC_BROWSER_TEST_F(ImageAnnotationBrowserTest, ImagesInLinks) {
  FakeAnnotator::SetReturnOcrResults(true);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      https_server_.GetURL("/accessibility/image_annotation_link.html")));

  // Block until the accessibility tree has at least 8 annotations. If
  // that never happens, the test will time out.
  content::WebContents* web_contents =
      browser()->tab_strip_model()->GetActiveWebContents();
  while (10 > DescribeNodesWithAnnotations(
                  content::GetAccessibilityTreeSnapshot(web_contents))
                  .size()) {
    content::WaitForAccessibilityTreeToChange(web_contents);
  }

  // All images should be annotated. Only links that contain exactly one image
  // should be annotated.
  ui::AXTreeUpdate ax_tree_update =
      content::GetAccessibilityTreeSnapshot(web_contents);
  EXPECT_THAT(
      DescribeNodesWithAnnotations(ax_tree_update),
      testing::ElementsAre("image Appears to say: red.png Annotation",
                           "link Appears to say: green.png Annotation",
                           "image Appears to say: green.png Annotation",
                           "image Appears to say: red.png Annotation",
                           "image Appears to say: printer.png Annotation",
                           "image Appears to say: red.png Annotation",
                           "link Appears to say: printer.png Annotation",
                           "image Appears to say: printer.png Annotation",
                           "link Appears to say: green.png Annotation",
                           "image Appears to say: green.png Annotation"));
}

IN_PROC_BROWSER_TEST_F(ImageAnnotationBrowserTest, ImagesInIframe) {
  FakeAnnotator::SetReturnOcrResults(true);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      https_server_.GetURL("/accessibility/image_annotation_iframe.html")));

  // Block until the accessibility tree has the annotated image from the
  // iframe in it. The test times out if it never appears.
  content::WebContents* web_contents =
      browser()->tab_strip_model()->GetActiveWebContents();
  content::WaitForAccessibilityTreeToContainNodeWithName(
      web_contents, "Appears to say: green.png Annotation");
}

IN_PROC_BROWSER_TEST_F(ImageAnnotationBrowserTest, AugmentImageNames) {
  FakeAnnotator::SetReturnLabelResults(true);
  FakeAnnotator::AddCustomLabelResultMapping("frog.jpg", "Tadpole");
  FakeAnnotator::AddCustomLabelResultMapping("train.png", "Locomotive");
  FakeAnnotator::AddCustomLabelResultMapping("cloud.png", "Cumulonimbus");
  FakeAnnotator::AddCustomLabelResultMapping("goat.jpg", "Billy goat");
  FakeAnnotator::AddCustomLabelResultMapping("dog.jpg", "Puppy");

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      https_server_.GetURL("/accessibility/image_annotation_augment.html")));

  // Block until the accessibility tree has at least 5 annotations. If
  // that never happens, the test will time out.
  content::WebContents* web_contents =
      browser()->tab_strip_model()->GetActiveWebContents();
  while (5 > DescribeNodesWithAnnotations(
                 content::GetAccessibilityTreeSnapshot(web_contents))
                 .size()) {
    content::WaitForAccessibilityTreeToChange(web_contents);
  }

  ui::AXTreeUpdate ax_tree_update =
      content::GetAccessibilityTreeSnapshot(web_contents);
  EXPECT_THAT(DescribeNodesWithAnnotations(ax_tree_update),
              testing::ElementsAre(
                  "image the Appears to be: Tadpole",
                  "image photo background Appears to be: Locomotive",
                  "image 12345678.jpg Appears to be: Cumulonimbus",
                  "image Sunday, Feb 6, 1966 Appears to be: Billy goat",
                  "image fotografia bianca e nero Appears to be: Puppy"));
}

IN_PROC_BROWSER_TEST_F(ImageAnnotationBrowserTest, AugmentImageNamesInLinks) {
  FakeAnnotator::SetReturnLabelResults(true);
  FakeAnnotator::AddCustomLabelResultMapping("frog.jpg", "Tadpole");
  FakeAnnotator::AddCustomLabelResultMapping("train.png", "Locomotive");

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL(
                     "/accessibility/image_annotation_augment_links.html")));

  // Block until the accessibility tree has at least 3 annotations. If
  // that never happens, the test will time out.
  content::WebContents* web_contents =
      browser()->tab_strip_model()->GetActiveWebContents();
  ui::AXTreeUpdate ax_tree_update =
      content::GetAccessibilityTreeSnapshot(web_contents);
  while (3 > DescribeNodesWithAnnotations(ax_tree_update).size()) {
    content::WaitForAccessibilityTreeToChange(web_contents);
    ax_tree_update = content::GetAccessibilityTreeSnapshot(web_contents);
  }

  EXPECT_THAT(
      DescribeNodesWithAnnotations(ax_tree_update),
      testing::ElementsAre("link photo background Appears to be: Locomotive",
                           "image photo background Appears to be: Locomotive",
                           "image the Appears to be: Tadpole"));
}

IN_PROC_BROWSER_TEST_F(ImageAnnotationBrowserTest, ImageDoc) {
  FakeAnnotator::SetReturnOcrResults(true);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      https_server_.GetURL("/accessibility/image_annotation_doc.html")));

  // Block until the accessibility tree has at least 2 annotations. If
  // that never happens, the test will time out.
  content::WebContents* web_contents =
      browser()->tab_strip_model()->GetActiveWebContents();
  while (2 > DescribeNodesWithAnnotations(
                 content::GetAccessibilityTreeSnapshot(web_contents))
                 .size()) {
    content::WaitForAccessibilityTreeToChange(web_contents);
  }

  // When a document contains exactly one image, the document should be
  // annotated with the image's annotation, too.
  ui::AXTreeUpdate ax_tree_update =
      content::GetAccessibilityTreeSnapshot(web_contents);
  EXPECT_THAT(
      DescribeNodesWithAnnotations(ax_tree_update),
      testing::ElementsAre("rootWebArea Appears to say: red.png Annotation",
                           "image Appears to say: red.png Annotation"));
}

IN_PROC_BROWSER_TEST_F(ImageAnnotationBrowserTest, ImageUrl) {
  FakeAnnotator::SetReturnOcrResults(true);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL("/accessibility/red.png")));

  // Block until the accessibility tree has at least 2 annotations. If
  // that never happens, the test will time out.
  content::WebContents* web_contents =
      browser()->tab_strip_model()->GetActiveWebContents();
  while (2 > DescribeNodesWithAnnotations(
                 content::GetAccessibilityTreeSnapshot(web_contents))
                 .size()) {
    content::WaitForAccessibilityTreeToChange(web_contents);
  }

  // When a document contains exactly one image, the document should be
  // annotated with the image's annotation, too.
  ui::AXTreeUpdate ax_tree_update =
      content::GetAccessibilityTreeSnapshot(web_contents);
  EXPECT_THAT(
      DescribeNodesWithAnnotations(ax_tree_update),
      testing::ElementsAre("rootWebArea Appears to say: red.png Annotation",
                           "image Appears to say: red.png Annotation"));
}

IN_PROC_BROWSER_TEST_F(ImageAnnotationBrowserTest, NoAnnotationsAvailable) {
  // Don't return any results.
  FakeAnnotator::SetReturnOcrResults(false);
  FakeAnnotator::SetReturnLabelResults(false);

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      https_server_.GetURL("/accessibility/image_annotation_doc.html")));

  // Block until the annotation status for the root is empty. If that
  // never occurs then the test will time out.
  content::WebContents* web_contents =
      browser()->tab_strip_model()->GetActiveWebContents();
  ui::AXTreeUpdate snapshot =
      content::GetAccessibilityTreeSnapshot(web_contents);
  while (snapshot.nodes.empty() ||
         snapshot.nodes[0].GetImageAnnotationStatus() !=
             ax::mojom::ImageAnnotationStatus::kAnnotationEmpty) {
    content::WaitForAccessibilityTreeToChange(web_contents);
    snapshot = content::GetAccessibilityTreeSnapshot(web_contents);
  }
}

IN_PROC_BROWSER_TEST_F(ImageAnnotationBrowserTest, AnnotationError) {
  // Return an error code.
  FakeAnnotator::SetReturnErrorCode(
      image_annotation::mojom::AnnotateImageError::kFailure);

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      https_server_.GetURL("/accessibility/image_annotation_doc.html")));

  // Block until the annotation status for the root contains an error code. If
  // that never occurs then the test will time out.
  content::WebContents* web_contents =
      browser()->tab_strip_model()->GetActiveWebContents();
  ui::AXTreeUpdate snapshot =
      content::GetAccessibilityTreeSnapshot(web_contents);
  while (snapshot.nodes.empty() ||
         snapshot.nodes[0].GetImageAnnotationStatus() !=
             ax::mojom::ImageAnnotationStatus::kAnnotationProcessFailed) {
    content::WaitForAccessibilityTreeToChange(web_contents);
    snapshot = content::GetAccessibilityTreeSnapshot(web_contents);
  }
}

IN_PROC_BROWSER_TEST_F(ImageAnnotationBrowserTest, ImageWithSrcSet) {
  FakeAnnotator::SetReturnOcrResults(true);
  FakeAnnotator::SetReturnLabelResults(true);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL("/accessibility/image_srcset.html")));

  content::WebContents* web_contents =
      browser()->tab_strip_model()->GetActiveWebContents();
  content::WaitForAccessibilityTreeToContainNodeWithName(
      web_contents,
      "Appears to say: red.png Annotation. Appears to be: red.png 'en' Label");
}

// Disabled due to flakiness. http://crbug.com/983404
IN_PROC_BROWSER_TEST_F(ImageAnnotationBrowserTest,
                       DISABLED_AnnotationLanguages) {
  FakeAnnotator::SetReturnOcrResults(true);
  FakeAnnotator::SetReturnLabelResults(true);

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL("/accessibility/image_annotation.html")));
  content::WebContents* web_contents =
      browser()->tab_strip_model()->GetActiveWebContents();
  content::WaitForAccessibilityTreeToContainNodeWithName(
      web_contents,
      "Appears to say: red.png Annotation. Appears to be: red.png 'en' Label");

  SetAcceptLanguages("fr,en");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL("/accessibility/image_annotation.html")));
  web_contents = browser()->tab_strip_model()->GetActiveWebContents();
  content::WaitForAccessibilityTreeToContainNodeWithName(
      web_contents,
      "Appears to say: red.png Annotation. Appears to be: red.png 'fr' Label");
}

// TODO(crbug.com/40928269): Fix flakiness on ChromeOS
#if BUILDFLAG(IS_CHROMEOS)
#define MAYBE_DoesntAnnotateInternalPages DISABLED_DoesntAnnotateInternalPages
#else
#define MAYBE_DoesntAnnotateInternalPages DoesntAnnotateInternalPages
#endif
IN_PROC_BROWSER_TEST_F(ImageAnnotationBrowserTest,
                       MAYBE_DoesntAnnotateInternalPages) {
  FakeAnnotator::SetReturnLabelResults(true);
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), GURL("chrome://version")));

  content::WebContents* web_contents =
      browser()->tab_strip_model()->GetActiveWebContents();

  std::string svg_image =
      "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'><circle "
      "cx='50' cy='50' r='40' fill='yellow' /></svg>";
  const std::string javascript =
      "var image = document.createElement('img');"
      "image.src = \"" +
      svg_image +
      "\";"
      "var outer = document.getElementById('outer');"
      "outer.insertBefore(image, outer.childNodes[0]);";
  EXPECT_TRUE(content::ExecJs(web_contents, javascript));

  ui::AXTreeUpdate snapshot =
      content::GetAccessibilityTreeSnapshot(web_contents);
  // Wait for the accessibility tree to contain an error that the image cannot
  // be annotated due to the page url's scheme.
  while (!HasNodeWithAnnotationStatus(
      snapshot,
      ax::mojom::ImageAnnotationStatus::kWillNotAnnotateDueToScheme)) {
    content::WaitForAccessibilityTreeToChange(web_contents);
    snapshot = content::GetAccessibilityTreeSnapshot(web_contents);
  }
}

IN_PROC_BROWSER_TEST_F(ImageAnnotationBrowserTest,
                       TutorMessageOnlyOnFirstImage) {
  // We should not promote the image annotation service on more than one image
  // in the same renderer.

  FakeAnnotator::SetReturnOcrResults(false);
  FakeAnnotator::SetReturnLabelResults(false);

  // The following test page should have at least two images on it.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL("/accessibility/image_annotation.html")));

  content::WebContents* web_contents =
      browser()->tab_strip_model()->GetActiveWebContents();

  scoped_accessibility_mode_ = content::ScopedAccessibilityModeOverride(
      web_contents, ui::kAXModeComplete);

  // Block until there are at least two images that have been processed. One of
  // them should get the tutor message and the other shouldn't. The annotation
  // status for the image that didn't get the tutor message should be
  // kSilentlyEligibleForAnnotation whilst the status for the image that did
  // should be kEligibleForAnnotation. If that never occurs then the test will
  // time out.
  ui::AXTreeUpdate snapshot =
      content::GetAccessibilityTreeSnapshot(web_contents);
  while (
      !HasNodeWithAnnotationStatus(
          snapshot,
          ax::mojom::ImageAnnotationStatus::kSilentlyEligibleForAnnotation) ||
      !HasNodeWithAnnotationStatus(
          snapshot, ax::mojom::ImageAnnotationStatus::kEligibleForAnnotation)) {
    content::WaitForAccessibilityTreeToChange(web_contents);
    snapshot = content::GetAccessibilityTreeSnapshot(web_contents);
  }
}

IN_PROC_BROWSER_TEST_F(ImageAnnotationBrowserTest,
                       TutorMessageOnlyOnFirstImageInLinks) {
  // We should not promote the image annotation service on more than one image
  // in the same renderer.

  FakeAnnotator::SetReturnOcrResults(false);
  FakeAnnotator::SetReturnLabelResults(false);

  // The following test page should have at least two images on it.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      https_server_.GetURL("/accessibility/image_annotation_link.html")));

  content::WebContents* web_contents =
      browser()->tab_strip_model()->GetActiveWebContents();

  scoped_accessibility_mode_ = content::ScopedAccessibilityModeOverride(
      web_contents, ui::kAXModeComplete);

  // Block until there are at least two images that have been processed. One of
  // them should get the tutor message and the other shouldn't. The annotation
  // status for the image that didn't get the tutor message should be
  // kSilentlyEligibleForAnnotation whilst the status for the image that did
  // should be kEligibleForAnnotation. If that never occurs then the test will
  // time out.
  ui::AXTreeUpdate snapshot =
      content::GetAccessibilityTreeSnapshot(web_contents);
  while (
      !HasNodeWithAnnotationStatus(
          snapshot,
          ax::mojom::ImageAnnotationStatus::kSilentlyEligibleForAnnotation) ||
      !HasNodeWithAnnotationStatus(
          snapshot, ax::mojom::ImageAnnotationStatus::kEligibleForAnnotation)) {
    content::WaitForAccessibilityTreeToChange(web_contents);
    snapshot = content::GetAccessibilityTreeSnapshot(web_contents);
  }
}

IN_PROC_BROWSER_TEST_F(ImageAnnotationBrowserTest, LazyLoadingImages) {
  FakeAnnotator::SetReturnOcrResults(true);
  FakeAnnotator::SetReturnLabelResults(true);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      https_server_.GetURL("/accessibility/page_with_lazy_image.html")));
  content::WebContents* web_contents =
      browser()->tab_strip_model()->GetActiveWebContents();

  content::WaitForAccessibilityTreeToContainNodeWithName(
      web_contents,
      "Appears to say: green.png Annotation. Appears to be: green.png 'en' "
      "Label");
  EXPECT_EQ(1u, DescribeNodesWithAnnotations(
                    content::GetAccessibilityTreeSnapshot(web_contents))
                    .size());

  base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE, run_loop.QuitWhenIdleClosure(), base::Milliseconds(500));
  run_loop.Run();
  web_contents->ScrollToBottomOfDocument();

  content::WaitForAccessibilityTreeToContainNodeWithName(
      web_contents,
      "Appears to say: red.png Annotation. Appears to be: red.png 'en' Label");
  EXPECT_EQ(2u, DescribeNodesWithAnnotations(
                    content::GetAccessibilityTreeSnapshot(web_contents))
                    .size());
}