[AXTreeFixing] Add AXTreeFixing service for ScreenAI and create in Router
This CL adds a new internal service router for the AXTree fixing feature. This router connects directly to the ScreenAI service and will be used as a single pipe from the BrowserContextKeyedService tree fixing Router to the ScreenAI service. This is made internal to the tree_fixing target, and only the AXTreeFixingServicesRouter can depend on it. In the future there will be many such internal routers. The top-level Router provides a public API for any client to perform an AXTree fixing operation, such as IdentifyMainNode, which is added in this CL. AX-Relnotes: N/A Bug: 395134535, 401034541, 399384018 Change-Id: Ic8aa07f2884b6ab3bc61a13e1466bdf6ee0765c5 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6325211 Reviewed-by: Nektarios Paisios <nektar@chromium.org> Reviewed-by: Mustafa Emre Acer <meacer@chromium.org> Commit-Queue: Mark Schillaci <mschillaci@google.com> Cr-Commit-Position: refs/heads/main@{#1429730}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
a6bd36497c
commit
79c8281c1f
chrome/browser/accessibility/tree_fixing
content/renderer/accessibility/annotations
services/screen_ai
@@ -17,4 +17,18 @@ if (!is_android) {
|
|||||||
"//content/public/browser:browser",
|
"//content/public/browser:browser",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# All clients that want to use AXTreeFixing need to do so via
|
||||||
|
# the router, and only the router can access the internal
|
||||||
|
# connections to downstream services.
|
||||||
|
source_set("internal") {
|
||||||
|
visibility = [ ":impl" ]
|
||||||
|
|
||||||
|
sources = [
|
||||||
|
"internal/ax_tree_fixing_screen_ai_service.cc",
|
||||||
|
"internal/ax_tree_fixing_screen_ai_service.h",
|
||||||
|
]
|
||||||
|
|
||||||
|
deps = [ "//base" ]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,9 +4,53 @@
|
|||||||
|
|
||||||
#include "chrome/browser/accessibility/tree_fixing/ax_tree_fixing_services_router.h"
|
#include "chrome/browser/accessibility/tree_fixing/ax_tree_fixing_services_router.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "base/check.h"
|
||||||
|
#include "chrome/browser/accessibility/tree_fixing/internal/ax_tree_fixing_screen_ai_service.h"
|
||||||
|
#include "ui/accessibility/ax_tree_update.h"
|
||||||
|
|
||||||
namespace tree_fixing {
|
namespace tree_fixing {
|
||||||
|
|
||||||
AXTreeFixingServicesRouter::AXTreeFixingServicesRouter(Profile* profile) {}
|
AXTreeFixingServicesRouter::AXTreeFixingServicesRouter(Profile* profile)
|
||||||
|
: profile_(profile) {}
|
||||||
AXTreeFixingServicesRouter::~AXTreeFixingServicesRouter() = default;
|
AXTreeFixingServicesRouter::~AXTreeFixingServicesRouter() = default;
|
||||||
|
|
||||||
|
void AXTreeFixingServicesRouter::IdentifyMainNode(
|
||||||
|
const ui::AXTreeUpdate& ax_tree,
|
||||||
|
MainNodeIdentificationCallback callback) {
|
||||||
|
// If this is the first time any client has requested tree fixing in a form
|
||||||
|
// that is handled by the ScreenAI service, then create an instance to connect
|
||||||
|
// to the service now.
|
||||||
|
if (!screen_ai_service_) {
|
||||||
|
screen_ai_service_ =
|
||||||
|
std::make_unique<AXTreeFixingScreenAIService>(this, profile_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the callback for later use, and make a request to ScreenAI.
|
||||||
|
pending_callbacks_.emplace_back(next_request_id_, std::move(callback));
|
||||||
|
screen_ai_service_->IdentifyMainNode(ax_tree, next_request_id_);
|
||||||
|
next_request_id_++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AXTreeFixingServicesRouter::OnMainNodeIdentified(ui::AXTreeID tree_id,
|
||||||
|
ui::AXNodeID node_id,
|
||||||
|
int request_id) {
|
||||||
|
CHECK(!pending_callbacks_.empty());
|
||||||
|
|
||||||
|
// Find the callback associated with the returned request ID, and call it with
|
||||||
|
// the identified tree_id and node_id for the upstream client to use. Remove
|
||||||
|
// the pending callback since we have fulfilled the contract.
|
||||||
|
for (auto it = pending_callbacks_.begin(); it != pending_callbacks_.end();
|
||||||
|
++it) {
|
||||||
|
if (it->first == request_id) {
|
||||||
|
MainNodeIdentificationCallback callback = std::move(it->second);
|
||||||
|
pending_callbacks_.erase(it);
|
||||||
|
std::move(callback).Run(std::make_pair(tree_id, node_id));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NOTREACHED();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace tree_fixing
|
} // namespace tree_fixing
|
||||||
|
@@ -5,16 +5,29 @@
|
|||||||
#ifndef CHROME_BROWSER_ACCESSIBILITY_TREE_FIXING_AX_TREE_FIXING_SERVICES_ROUTER_H_
|
#ifndef CHROME_BROWSER_ACCESSIBILITY_TREE_FIXING_AX_TREE_FIXING_SERVICES_ROUTER_H_
|
||||||
#define CHROME_BROWSER_ACCESSIBILITY_TREE_FIXING_AX_TREE_FIXING_SERVICES_ROUTER_H_
|
#define CHROME_BROWSER_ACCESSIBILITY_TREE_FIXING_AX_TREE_FIXING_SERVICES_ROUTER_H_
|
||||||
|
|
||||||
|
#include <list>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "chrome/browser/accessibility/tree_fixing/internal/ax_tree_fixing_screen_ai_service.h"
|
||||||
#include "components/keyed_service/core/keyed_service.h"
|
#include "components/keyed_service/core/keyed_service.h"
|
||||||
|
|
||||||
class Profile;
|
class Profile;
|
||||||
|
|
||||||
|
namespace ui {
|
||||||
|
struct AXTreeUpdate;
|
||||||
|
} // namespace ui
|
||||||
|
|
||||||
namespace tree_fixing {
|
namespace tree_fixing {
|
||||||
|
|
||||||
|
using MainNodeIdentificationCallback =
|
||||||
|
base::OnceCallback<void(std::pair<ui::AXTreeID, ui::AXNodeID>)>;
|
||||||
|
|
||||||
// This class handles the communication between the browser process and any
|
// This class handles the communication between the browser process and any
|
||||||
// downstream services used to fix the AXTree, such as: the optimization guide,
|
// downstream services used to fix the AXTree, such as: the optimization guide,
|
||||||
// Screen2x, Aratea, etc.
|
// Screen2x, Aratea, etc.
|
||||||
class AXTreeFixingServicesRouter : public KeyedService {
|
class AXTreeFixingServicesRouter
|
||||||
|
: public KeyedService,
|
||||||
|
public AXTreeFixingScreenAIService::MainNodeIdentificationDelegate {
|
||||||
public:
|
public:
|
||||||
explicit AXTreeFixingServicesRouter(Profile* profile);
|
explicit AXTreeFixingServicesRouter(Profile* profile);
|
||||||
AXTreeFixingServicesRouter(const AXTreeFixingServicesRouter&) = delete;
|
AXTreeFixingServicesRouter(const AXTreeFixingServicesRouter&) = delete;
|
||||||
@@ -22,7 +35,29 @@ class AXTreeFixingServicesRouter : public KeyedService {
|
|||||||
delete;
|
delete;
|
||||||
~AXTreeFixingServicesRouter() override;
|
~AXTreeFixingServicesRouter() override;
|
||||||
|
|
||||||
// TODO(mschillaci): Stubbed. Fill in with mojom bindings.
|
// --- Public APIs for any request to fix an AXTree ---
|
||||||
|
|
||||||
|
// Identifies the main node of an AXTree, and asynchronously returns the
|
||||||
|
// identified node_id and its associated tree_id as a std::pair via the
|
||||||
|
// provided callback. The AXTreeUpdate that clients provide to this method
|
||||||
|
// should represent a full AXTree for the page in order to accurately identify
|
||||||
|
// a main node. The AXTree should not have an existing node with Role kMain.
|
||||||
|
void IdentifyMainNode(const ui::AXTreeUpdate& ax_tree,
|
||||||
|
MainNodeIdentificationCallback callback);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// AXTreeFixingScreenAIService::MainNodeIdentificationDelegate overrides:
|
||||||
|
void OnMainNodeIdentified(ui::AXTreeID tree_id,
|
||||||
|
ui::AXNodeID node_id,
|
||||||
|
int request_id) override;
|
||||||
|
|
||||||
|
// ScreenAI related objects: service instance, and a list of callbacks/ids.
|
||||||
|
std::unique_ptr<AXTreeFixingScreenAIService> screen_ai_service_;
|
||||||
|
std::list<std::pair<int, MainNodeIdentificationCallback>> pending_callbacks_;
|
||||||
|
int next_request_id_ = 0;
|
||||||
|
|
||||||
|
// The Profile for the KeyedService for this instance.
|
||||||
|
Profile* profile_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace tree_fixing
|
} // namespace tree_fixing
|
||||||
|
61
chrome/browser/accessibility/tree_fixing/internal/ax_tree_fixing_screen_ai_service.cc
Normal file
61
chrome/browser/accessibility/tree_fixing/internal/ax_tree_fixing_screen_ai_service.cc
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
// Copyright 2025 The Chromium Authors
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
#include "chrome/browser/accessibility/tree_fixing/internal/ax_tree_fixing_screen_ai_service.h"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "base/notreached.h"
|
||||||
|
#include "chrome/browser/screen_ai/screen_ai_service_router.h"
|
||||||
|
#include "chrome/browser/screen_ai/screen_ai_service_router_factory.h"
|
||||||
|
#include "ui/accessibility/ax_enums.mojom.h"
|
||||||
|
#include "ui/accessibility/ax_node_data.h"
|
||||||
|
|
||||||
|
namespace tree_fixing {
|
||||||
|
|
||||||
|
AXTreeFixingScreenAIService::AXTreeFixingScreenAIService(
|
||||||
|
MainNodeIdentificationDelegate& delegate,
|
||||||
|
Profile* profile)
|
||||||
|
: main_node_identification_delegate_(delegate), profile_(profile) {}
|
||||||
|
|
||||||
|
AXTreeFixingScreenAIService::~AXTreeFixingScreenAIService() = default;
|
||||||
|
|
||||||
|
void AXTreeFixingScreenAIService::IdentifyMainNode(
|
||||||
|
const ui::AXTreeUpdate& ax_tree_update,
|
||||||
|
int request_id) {
|
||||||
|
// Clients should not be sending requests for trees that have a kMain node.
|
||||||
|
for (const ui::AXNodeData& node : ax_tree_update.nodes) {
|
||||||
|
if (node.role == ax::mojom::Role::kMain) {
|
||||||
|
NOTREACHED();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the remote to ScreenAI has not yet been bound, do so now.
|
||||||
|
// TODO(crbug.com/401308988): Handle cases like not ready or disconnects.
|
||||||
|
if (!screen_ai_service_.is_bound() || !screen_ai_service_.is_connected()) {
|
||||||
|
mojo::PendingReceiver<screen_ai::mojom::Screen2xMainContentExtractor>
|
||||||
|
receiver = screen_ai_service_.BindNewPipeAndPassReceiver();
|
||||||
|
screen_ai::ScreenAIServiceRouterFactory::GetForBrowserContext(profile_)
|
||||||
|
->BindMainContentExtractor(std::move(receiver));
|
||||||
|
screen_ai_service_.reset_on_disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Identify the main node using ScreenAI.
|
||||||
|
screen_ai_service_->IdentifyMainNode(
|
||||||
|
ax_tree_update,
|
||||||
|
base::BindOnce(&AXTreeFixingScreenAIService::
|
||||||
|
ProcessScreenAIMainNodeIdentificationResult,
|
||||||
|
weak_ptr_factory_.GetWeakPtr(), request_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AXTreeFixingScreenAIService::ProcessScreenAIMainNodeIdentificationResult(
|
||||||
|
const ui::AXTreeID& tree_id,
|
||||||
|
int node_id,
|
||||||
|
int request_id) {
|
||||||
|
// TODO(crbug.com/399383663): Add metrics, internal logic, etc.
|
||||||
|
main_node_identification_delegate_->OnMainNodeIdentified(tree_id, node_id,
|
||||||
|
request_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace tree_fixing
|
@@ -0,0 +1,81 @@
|
|||||||
|
// Copyright 2025 The Chromium Authors
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
#ifndef CHROME_BROWSER_ACCESSIBILITY_TREE_FIXING_INTERNAL_AX_TREE_FIXING_SCREEN_AI_SERVICE_H_
|
||||||
|
#define CHROME_BROWSER_ACCESSIBILITY_TREE_FIXING_INTERNAL_AX_TREE_FIXING_SCREEN_AI_SERVICE_H_
|
||||||
|
|
||||||
|
#include "base/memory/raw_ref.h"
|
||||||
|
#include "base/memory/weak_ptr.h"
|
||||||
|
#include "chrome/browser/profiles/profile.h"
|
||||||
|
#include "mojo/public/cpp/bindings/remote.h"
|
||||||
|
#include "services/screen_ai/public/mojom/screen_ai_service.mojom.h"
|
||||||
|
|
||||||
|
namespace tree_fixing {
|
||||||
|
|
||||||
|
class AXTreeFixingScreenAIService final {
|
||||||
|
public:
|
||||||
|
// Delegate for clients that want to perform main node identification.
|
||||||
|
class MainNodeIdentificationDelegate {
|
||||||
|
protected:
|
||||||
|
MainNodeIdentificationDelegate() = default;
|
||||||
|
|
||||||
|
public:
|
||||||
|
MainNodeIdentificationDelegate(const MainNodeIdentificationDelegate&) =
|
||||||
|
delete;
|
||||||
|
MainNodeIdentificationDelegate& operator=(
|
||||||
|
const MainNodeIdentificationDelegate&) = delete;
|
||||||
|
virtual ~MainNodeIdentificationDelegate() = default;
|
||||||
|
|
||||||
|
// This method is used to communicate to the delegate (owner) of this
|
||||||
|
// instance, the main node that was identified via the IdentifyMainNode
|
||||||
|
// method. When calling IdentifyMainNode, the client must provide a
|
||||||
|
// request_id, and this ID is passed back to the client along with the
|
||||||
|
// tree_id and node_id. The request_id allows clients to make multiple
|
||||||
|
// requests in parallel and uniquely identify each response. It is the
|
||||||
|
// responsibility of the client to handle the logic behind a request_id,
|
||||||
|
// this service simply passes the id through.
|
||||||
|
virtual void OnMainNodeIdentified(ui::AXTreeID tree_id,
|
||||||
|
ui::AXNodeID node_id,
|
||||||
|
int request_id) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
AXTreeFixingScreenAIService(MainNodeIdentificationDelegate& delegate,
|
||||||
|
Profile* profile);
|
||||||
|
AXTreeFixingScreenAIService(const AXTreeFixingScreenAIService&) = delete;
|
||||||
|
AXTreeFixingScreenAIService& operator=(const AXTreeFixingScreenAIService&) =
|
||||||
|
delete;
|
||||||
|
~AXTreeFixingScreenAIService();
|
||||||
|
|
||||||
|
// --- Public APIs for upstream clients (e.g. AXTreeFixingServicesRouter) ---
|
||||||
|
|
||||||
|
// Identifies the main node of an AXTreeUpdate. The client should provide a
|
||||||
|
// request_id, which is returned to the client along with a tree_id and
|
||||||
|
// node_id via a call to OnMainNodeIdentified.
|
||||||
|
void IdentifyMainNode(const ui::AXTreeUpdate& ax_tree_update, int request_id);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Internal method that processes results from the ScreenAI service before
|
||||||
|
// returning the results to the owner of this instance via the provided
|
||||||
|
// delegate.
|
||||||
|
void ProcessScreenAIMainNodeIdentificationResult(const ui::AXTreeID& tree_id,
|
||||||
|
int node_id,
|
||||||
|
int request_id);
|
||||||
|
|
||||||
|
// Delegate provided by client to receive main node identification results.
|
||||||
|
// Use a raw_ref since we do not own the delegate or control its lifecycle.
|
||||||
|
raw_ref<MainNodeIdentificationDelegate> main_node_identification_delegate_;
|
||||||
|
|
||||||
|
// Profile for the KeyedService that owns us.
|
||||||
|
raw_ptr<Profile> profile_;
|
||||||
|
|
||||||
|
// The remote of the ScreenAI service, the receiver is in a utility process.
|
||||||
|
mojo::Remote<screen_ai::mojom::Screen2xMainContentExtractor>
|
||||||
|
screen_ai_service_;
|
||||||
|
|
||||||
|
base::WeakPtrFactory<AXTreeFixingScreenAIService> weak_ptr_factory_{this};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace tree_fixing
|
||||||
|
|
||||||
|
#endif // CHROME_BROWSER_ACCESSIBILITY_TREE_FIXING_INTERNAL_AX_TREE_FIXING_SCREEN_AI_SERVICE_H_
|
@@ -53,6 +53,9 @@ class MockMainNodeAnnotationService
|
|||||||
std::move(callback).Run(main_);
|
std::move(callback).Run(main_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void IdentifyMainNode(const ui::AXTreeUpdate& snapshot,
|
||||||
|
IdentifyMainNodeCallback callback) override {}
|
||||||
|
|
||||||
// Tests should not modify entries in these lists.
|
// Tests should not modify entries in these lists.
|
||||||
std::vector<ui::AXNodeID> content_nodes_;
|
std::vector<ui::AXNodeID> content_nodes_;
|
||||||
ui::AXNodeID main_ = ui::kInvalidAXNodeID;
|
ui::AXNodeID main_ = ui::kInvalidAXNodeID;
|
||||||
|
@@ -137,7 +137,15 @@ interface Screen2xMainContentExtractor {
|
|||||||
|
|
||||||
// Receives the accessibility tree as a snapshot, schedules processing, and
|
// Receives the accessibility tree as a snapshot, schedules processing, and
|
||||||
// returns the main node id of the given tree.
|
// returns the main node id of the given tree.
|
||||||
|
// DEPRECATED, use IdentifyMainNode method below.
|
||||||
|
// TODO(crbug.com/401052200): Clean up existing calls and delete method.
|
||||||
ExtractMainNode(ax.mojom.AXTreeUpdate snapshot) => (int32 main_node_id);
|
ExtractMainNode(ax.mojom.AXTreeUpdate snapshot) => (int32 main_node_id);
|
||||||
|
|
||||||
|
// Receives an AXTreeUpdate and identifies the main node. The AXTreeUpdate
|
||||||
|
// should represent an entire AXTree; not a diff or partial tree, in order
|
||||||
|
// to identify an accurate main node for the entire page.
|
||||||
|
IdentifyMainNode(ax.mojom.AXTreeUpdate ax_tree) =>
|
||||||
|
(ax.mojom.AXTreeID tree_id, int32 node_id);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Provides an interface to the OCR functionality of the Screen AI service.
|
// Provides an interface to the OCR functionality of the Screen AI service.
|
||||||
|
@@ -455,6 +455,20 @@ void ScreenAIService::ExtractMainNode(const ui::AXTreeUpdate& snapshot,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ScreenAIService::IdentifyMainNode(const ui::AXTreeUpdate& snapshot,
|
||||||
|
IdentifyMainNodeCallback callback) {
|
||||||
|
ui::AXTree tree;
|
||||||
|
std::optional<std::vector<int32_t>> content_node_ids;
|
||||||
|
bool success = ExtractMainContentInternal(snapshot, tree, content_node_ids);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
ui::AXNodeID main_node_id = ComputeMainNode(&tree, *content_node_ids);
|
||||||
|
std::move(callback).Run(tree.GetAXTreeID(), main_node_id);
|
||||||
|
} else {
|
||||||
|
std::move(callback).Run(ui::AXTreeIDUnknown(), ui::kInvalidAXNodeID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool ScreenAIService::ExtractMainContentInternal(
|
bool ScreenAIService::ExtractMainContentInternal(
|
||||||
const ui::AXTreeUpdate& snapshot,
|
const ui::AXTreeUpdate& snapshot,
|
||||||
ui::AXTree& tree,
|
ui::AXTree& tree,
|
||||||
|
@@ -76,6 +76,8 @@ class ScreenAIService : public mojom::ScreenAIServiceFactory,
|
|||||||
ExtractMainContentCallback callback) override;
|
ExtractMainContentCallback callback) override;
|
||||||
void ExtractMainNode(const ui::AXTreeUpdate& snapshot,
|
void ExtractMainNode(const ui::AXTreeUpdate& snapshot,
|
||||||
ExtractMainNodeCallback callback) override;
|
ExtractMainNodeCallback callback) override;
|
||||||
|
void IdentifyMainNode(const ui::AXTreeUpdate& snapshot,
|
||||||
|
IdentifyMainNodeCallback callback) override;
|
||||||
|
|
||||||
// mojom::ScreenAIServiceFactory:
|
// mojom::ScreenAIServiceFactory:
|
||||||
void InitializeMainContentExtraction(
|
void InitializeMainContentExtraction(
|
||||||
|
Reference in New Issue
Block a user