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

#include "content/renderer/accessibility/annotations/ax_main_node_annotator.h"

#include <utility>

#include "base/metrics/histogram_functions.h"
#include "base/time/time.h"
#include "third_party/blink/public/platform/browser_interface_broker_proxy.h"

namespace content {

using blink::WebAXObject;
using blink::WebDocument;

namespace {

const char kHistogramsName[] =
    "Accessibility.MainNodeAnnotations.AnnotationResult";

// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
// LINT.IfChange(MainNodeAnnotationResult)
enum class MainNodeAnnotationResult {
  kSuccess = 0,
  kInvalid = 1,
  kDuplicate = 2,
  kMaxValue = kDuplicate,
};
// LINT.ThenChange(/tools/metrics/histograms/metadata/accessibility/enums.xml:MainNodeAnnotationResult)

}  // namespace

AXMainNodeAnnotator::AXMainNodeAnnotator(
    RenderAccessibilityImpl* const render_accessibility)
    : render_accessibility_(render_accessibility) {
  CHECK(render_accessibility_);
}

AXMainNodeAnnotator::~AXMainNodeAnnotator() = default;

void AXMainNodeAnnotator::EnableAnnotations() {
  annotator_enabled_ = true;
}

void AXMainNodeAnnotator::CancelAnnotations() {
  if (render_accessibility_->GetAccessibilityMode().has_mode(
          GetAXModeToEnableAnnotations())) {
    return;
  }
  annotator_enabled_ = false;
  annotator_remote_.reset();
}

uint32_t AXMainNodeAnnotator::GetAXModeToEnableAnnotations() {
  return ui::AXMode::kAnnotateMainNode;
}

bool AXMainNodeAnnotator::HasAXActionToEnableAnnotations() {
  return false;
}

ax::mojom::Action AXMainNodeAnnotator::GetAXActionToEnableAnnotations() {
  NOTREACHED();
}

void AXMainNodeAnnotator::Annotate(const WebDocument& document,
                                   ui::AXTreeUpdate* update,
                                   bool load_complete) {
  // Annotate is called every time RenderAccessibilityImpl sends a serialized
  // tree, in the form of an AXTreeUpdate, to the browser. Before sending to
  // the browser, we annotate the main node of the AXTreeUpdate here.
  if (main_node_id_ != ui::kInvalidAXNodeID) {
    // TODO: Replace with binary search as nodes should be in order by id.
    for (ui::AXNodeData& node : update->nodes) {
      if (node.id != main_node_id_) {
        continue;
      }
      // TODO: Replace this with a role specifically for annotations.
      node.role = ax::mojom::Role::kMain;
      return;
    }
    // If the main node was set, even if if is not found in the tree anymore,
    // do nothing.
    // TODO: Consider whether we should call Screen2x again.
    return;
  }

  // Do nothing if this is not a load complete event.
  if (!load_complete) {
    return;
  }

  // Check whether the author has already labeled a main node in this tree.
  ComputeAuthorStatus(update);
  if (author_status_ == AXMainNodeAnnotatorAuthorStatus::kAuthorProvidedMain) {
    return;
  }
  CHECK_EQ(author_status_,
           AXMainNodeAnnotatorAuthorStatus::kAuthorDidNotProvideMain);

  // TODO(crbug.com/327248295): Promote the feature if the user has not enabled
  // it and is on a page without a main node annotation.
  if (!annotator_enabled_) {
    return;
  }

  if (!annotator_remote_.is_bound() || !annotator_remote_.is_connected()) {
    if (!render_accessibility_->render_frame()) {
      return;
    }

    mojo::PendingRemote<screen_ai::mojom::Screen2xMainContentExtractor>
        annotator;
    render_accessibility_->render_frame()
        ->GetBrowserInterfaceBroker()
        .GetInterface(annotator.InitWithNewPipeAndPassReceiver());
    annotator_remote_.Bind(std::move(annotator));
    annotator_remote_.reset_on_disconnect();
    annotator_remote_->SetClientType(
        screen_ai::mojom::MceClientType::kMainNode);
  }

  // Identify the main node using Screen2x.
  annotator_remote_->ExtractMainNode(
      *update, base::BindOnce(&AXMainNodeAnnotator::ProcessScreen2xResult,
                              weak_ptr_factory_.GetWeakPtr(), document));
}

void AXMainNodeAnnotator::ProcessScreen2xResult(const WebDocument& document,
                                                ui::AXNodeID main_node_id) {
  // If Screen2x returned an invalid main node id, do nothing.
  if (main_node_id == ui::kInvalidAXNodeID) {
    base::UmaHistogramEnumeration(kHistogramsName,
                                  MainNodeAnnotationResult::kInvalid);
    return;
  }
  // If the main node id was already set, do nothing.
  if (main_node_id_ != ui::kInvalidAXNodeID) {
    base::UmaHistogramEnumeration(kHistogramsName,
                                  MainNodeAnnotationResult::kDuplicate);
    return;
  }
  WebAXObject object = WebAXObject::FromWebDocumentByID(document, main_node_id);
  // If the tree has changed, do nothing.
  if (!object.IsIncludedInTree()) {
    base::UmaHistogramEnumeration(kHistogramsName,
                                  MainNodeAnnotationResult::kInvalid);
    return;
  }
  main_node_id_ = main_node_id;
  render_accessibility_->MarkWebAXObjectDirty(object);
  base::UmaHistogramEnumeration(kHistogramsName,
                                MainNodeAnnotationResult::kSuccess);
}

void AXMainNodeAnnotator::ComputeAuthorStatus(ui::AXTreeUpdate* update) {
  if (author_status_ != AXMainNodeAnnotatorAuthorStatus::kUnconfirmed) {
    return;
  }
  for (ui::AXNodeData& node : update->nodes) {
    if (node.role == ax::mojom::Role::kMain) {
      author_status_ = AXMainNodeAnnotatorAuthorStatus::kAuthorProvidedMain;
      return;
    }
  }
  author_status_ = AXMainNodeAnnotatorAuthorStatus::kAuthorDidNotProvideMain;
}

void AXMainNodeAnnotator::BindAnnotatorForTesting(
    mojo::PendingRemote<screen_ai::mojom::Screen2xMainContentExtractor>
        annotator) {
  annotator_remote_.Bind(std::move(annotator));
  annotator_enabled_ = true;
}

}  // namespace content