blob: 87949de690f876a426a141b59f8ecf4ecbe3d56a [file] [log] [blame]
// 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