blob: 480c0d27cd2b46651575e78274e9981f4d515ae2 [file]
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CONTENT_BROWSER_RENDERER_HOST_MODEL_CONTEXT_USER_DATA_H_
#define CONTENT_BROWSER_RENDERER_HOST_MODEL_CONTEXT_USER_DATA_H_
#include <map>
#include <vector>
#include "base/memory/weak_ptr.h"
#include "content/common/content_export.h"
#include "content/public/browser/document_user_data.h"
#include "content/public/browser/page_user_data.h"
#include "content/public/browser/weak_document_ptr.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/receiver_set.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "third_party/blink/public/common/tokens/tokens.h"
#include "third_party/blink/public/mojom/content_extraction/script_tools.mojom.h"
#include "url/origin.h"
namespace content {
class RenderFrameHost;
// Associated with the Page, managing pending tool executions within a given
// frame tree. This must be `Page`-scoped instead of document-scoped, and
// therefore differs from `ModelContextUserData`, since tool executions are
// fundamentally cross-document, and it's easiest to have a centralized place to
// manage them.
//
// For example, if iframe A invokes a tool in iframe B, and iframe B navigates
// away during tool execution, the destructor needs to traverse the frame tree
// to locate the caller of the tool and run its pending callback, to signal to
// the caller's script that the tool execution has cancelled. The same must
// happen in the inverse, when the caller's script aborts its execution. To
// simplify the "finding" of tool caller and callee, we centralize all pending
// execution data here.
class CONTENT_EXPORT ModelContextPageUserData
: public PageUserData<ModelContextPageUserData> {
public:
struct PendingScriptToolExecution {
PendingScriptToolExecution();
PendingScriptToolExecution(PendingScriptToolExecution&&);
PendingScriptToolExecution& operator=(PendingScriptToolExecution&&);
~PendingScriptToolExecution();
blink::DocumentToken caller_token;
blink::DocumentToken target_token;
WeakDocumentPtr target_document;
std::string tool_name;
blink::mojom::ModelContextHost::ExecuteRemoteScriptToolCallback callback;
};
explicit ModelContextPageUserData(Page& page);
~ModelContextPageUserData() override;
// When the `ExecuteRemoteScriptTool()` IPC is called on
// `blink.mojom.ModelContextHost` (i.e., `ModelContextUserData`), this method
// is called, and the callback to resolve/reject the Promise in the renderer
// is added to `pending_script_tool_executions_`.
void AddPendingScriptToolExecution(
const base::UnguessableToken& invocation_id,
PendingScriptToolExecution execution);
// When the browser process calls the `ExecuteScriptTool()` IPC on
// `blink.mojom.ModelContext` (i.e., `blink::ModelContext`), the callback
// signifying tool completion in the renderer calls this method. This method
// in turn takes the tool response, and forwards it back to the invoking
// renderer process, by running the response callback that was added in
// `AddPendingScriptToolExecution()` above.
void CompletePendingScriptToolExecution(
const base::UnguessableToken& invocation_id,
const std::optional<std::string>& result,
bool success);
// The below "cancellation" methods ensure that a failure to execute a tool to
// completion is propagated back to the calling renderer under certain
// circumstances.
//
// `CancelPendingScriptToolExecutionsForDocument()` removes all outstanding
// `PendingScriptToolExecution` structs associated with `rfh`,
// because the document is being destroyed. This means it serves two purposes:
// 1. It signals to the callers of all still-running tools in
// `rfh` that tool execution was cancelled because the tool
// owner is going away (by invoking the tool execution response
// callback); and
// 2. It simply cleans up all pending execution data for tools *called by*
// `rfh`, when tool callers gets destroyed.
//
void CancelPendingScriptToolExecution(
const base::UnguessableToken& invocation_id);
void CancelPendingScriptToolExecutionsForDocument(
const blink::DocumentToken& document_token);
base::WeakPtr<ModelContextPageUserData> GetWeakPtr();
private:
friend class PageUserData<ModelContextPageUserData>;
PAGE_USER_DATA_KEY_DECL();
static void SendCancelScriptToolToTarget(
WeakDocumentPtr target_document,
const base::UnguessableToken& invocation_id);
std::map<base::UnguessableToken, PendingScriptToolExecution>
pending_script_tool_executions_;
base::WeakPtrFactory<ModelContextPageUserData> weak_factory_{this};
};
// `ModelContextUserData` tracks registered tools and handles communication with
// `blink::ModelContext` It is created on-demand when the renderer first
// accesses `document.modelContext`. If it does not exist for a frame, it means
// that frame has not used the API yet and does not need to receive broadcasts
// like `toolchange`.
class CONTENT_EXPORT ModelContextUserData
: public blink::mojom::ModelContextHost,
public DocumentUserData<ModelContextUserData> {
public:
explicit ModelContextUserData(RenderFrameHost* rfh);
~ModelContextUserData() override;
static void Bind(
RenderFrameHost* rfh,
mojo::PendingReceiver<blink::mojom::ModelContextHost> receiver);
// blink::mojom::ModelContextHost implementation:
void BindModelContext(
mojo::PendingRemote<blink::mojom::ModelContext> model_context) override;
void RegisterScriptTool(blink::mojom::ScriptToolPtr tool,
RegisterScriptToolCallback callback) override;
void UnregisterScriptTool(const std::string& name) override;
void GetScriptTools(const std::vector<url::Origin>& from_origins,
GetScriptToolsCallback callback) override;
void ExecuteRemoteScriptTool(
const base::UnguessableToken& invocation_id,
const blink::FrameToken& tool_owner_frame_token,
const url::Origin& expected_target_origin,
const std::string& name,
const std::string& input_arguments,
ExecuteRemoteScriptToolCallback callback) override;
void CancelRemoteScriptTool(
const base::UnguessableToken& invocation_id) override;
std::vector<blink::mojom::ScriptToolPtr>& script_tools() {
return script_tools_;
}
private:
friend class DocumentUserData<ModelContextUserData>;
friend class ModelContextPageUserData;
DOCUMENT_USER_DATA_KEY_DECL();
// Iterates over all documents in the frame tree and notifies them that the
// list of available tools has changed, provided their origin matches one in
// `exposed_origins` for the tool.
void NotifyToolChange(const std::vector<url::Origin>& exposed_origins);
mojo::Receiver<blink::mojom::ModelContextHost> receiver_{this};
mojo::Remote<blink::mojom::ModelContext> model_context_remote_;
std::vector<blink::mojom::ScriptToolPtr> script_tools_;
base::WeakPtrFactory<ModelContextUserData> weak_factory_{this};
};
} // namespace content
#endif // CONTENT_BROWSER_RENDERER_HOST_MODEL_CONTEXT_USER_DATA_H_