| // 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/renderer/actor/script_tool.h" |
| |
| #include <memory> |
| #include <optional> |
| |
| #include "base/notimplemented.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/to_string.h" |
| #include "chrome/common/actor/action_result.h" |
| #include "chrome/common/actor/actor_constants.h" |
| #include "chrome/renderer/actor/tool_utils.h" |
| #include "components/actor/core/actor_logging.h" |
| #include "components/actor/public/mojom/actor_types.mojom.h" |
| #include "content/public/renderer/render_frame.h" |
| #include "third_party/abseil-cpp/absl/strings/str_format.h" |
| #include "third_party/blink/public/mojom/content_extraction/script_tools.mojom.h" |
| #include "third_party/blink/public/web/web_document.h" |
| #include "third_party/blink/public/web/web_local_frame.h" |
| #include "third_party/blink/public/web/web_script_tool_types.h" |
| #include "ui/events/base_event_utils.h" |
| |
| namespace actor { |
| namespace { |
| |
| mojom::ActionResultPtr OnToolExecuted( |
| const std::string& name, |
| const std::string& input_arguments, |
| std::unique_ptr<blink::WebScriptToolDeclaration> tool, |
| base::expected<blink::WebString, blink::WebScriptToolError> response) { |
| if (!response.has_value()) { |
| mojom::ActionResultCode code; |
| switch (response.error().code) { |
| case blink::WebScriptToolErrorCode::kInvalidToolName: |
| code = mojom::ActionResultCode::kScriptToolInvalidName; |
| break; |
| case blink::WebScriptToolErrorCode::kInvalidInputArguments: |
| code = mojom::ActionResultCode::kScriptToolInvalidInputArguments; |
| break; |
| case blink::WebScriptToolErrorCode::kMissingRequiredSubmitButton: |
| code = mojom::ActionResultCode::kScriptToolMissingRequiredSubmitButton; |
| break; |
| case blink::WebScriptToolErrorCode::kToolInvocationFailed: |
| code = mojom::ActionResultCode::kScriptToolInvocationFailed; |
| break; |
| case blink::WebScriptToolErrorCode::kToolCancelled: |
| code = mojom::ActionResultCode::kScriptToolCancelled; |
| break; |
| } |
| return MakeResult(code, /*requires_page_stabilization=*/false, |
| response.error().message.Utf8()); |
| } |
| |
| auto result = MakeOkResult(); |
| auto script_tool_response = mojom::ScriptToolResponse::New(); |
| script_tool_response->input_arguments = input_arguments; |
| script_tool_response->tool = blink::mojom::ScriptTool::New(); |
| script_tool_response->tool->name = name; |
| script_tool_response->tool->description = tool->description.Utf8(); |
| script_tool_response->tool->input_schema = tool->input_schema.Utf8(); |
| if (tool->read_only.has_value() || tool->untrusted_content.has_value() || |
| tool->consequential.has_value() || tool->debugging.has_value()) { |
| script_tool_response->tool->annotations = |
| blink::mojom::ScriptToolAnnotations::New(); |
| if (tool->read_only.has_value()) { |
| script_tool_response->tool->annotations->read_only = |
| tool->read_only.value(); |
| } |
| if (tool->untrusted_content.has_value()) { |
| script_tool_response->tool->annotations->untrusted_content = |
| tool->untrusted_content.value(); |
| } |
| if (tool->consequential.has_value()) { |
| script_tool_response->tool->annotations->consequential = |
| tool->consequential.value(); |
| } |
| if (tool->debugging.has_value()) { |
| script_tool_response->tool->annotations->debugging = |
| tool->debugging.value(); |
| } |
| } |
| if (!response->IsEmpty()) { |
| script_tool_response->result = response->Utf8(); |
| } |
| result->script_tool_response = std::move(script_tool_response); |
| |
| return result; |
| } |
| |
| } // namespace |
| |
| ScriptTool::ScriptTool(content::RenderFrame& frame, |
| TaskId task_id, |
| base::UnguessableToken execution_id, |
| Journal& journal, |
| mojom::ToolTargetPtr target, |
| mojom::ObservedToolTargetPtr observed_target, |
| mojom::ScriptToolActionPtr action) |
| : ToolBase(frame, |
| task_id, |
| journal, |
| std::move(target), |
| std::move(observed_target)), |
| action_(std::move(action)), |
| execution_id_(execution_id) {} |
| |
| ScriptTool::~ScriptTool() = default; |
| |
| void ScriptTool::Execute(ToolFinishedCallback callback) { |
| frame_->GetWebFrame()->GetDocument().ExecuteScriptTool( |
| execution_id_.value(), blink::WebString::FromUtf8(action_->name), |
| blink::WebString::FromUtf8(action_->input_arguments), |
| base::BindOnce(&OnToolExecuted, action_->name, action_->input_arguments) |
| .Then(std::move(callback))); |
| } |
| |
| void ScriptTool::Cancel() { |
| if (!execution_id_.has_value()) { |
| return; |
| } |
| // CancelScriptTool() synchronously dispatches DOM events that might destroy |
| // the owning frame and this tool. Use a weak pointer to detect if `this` is |
| // still valid. |
| base::WeakPtr<ScriptTool> weak_this = weak_ptr_factory_.GetWeakPtr(); |
| frame_->GetWebFrame()->GetDocument().CancelScriptTool(execution_id_.value()); |
| if (weak_this) { |
| execution_id_.reset(); |
| } |
| } |
| |
| std::string ScriptTool::DebugString() const { |
| return absl::StrFormat("ScriptTool[tool_name(%s);input_arguments(%s)]", |
| action_->name, action_->input_arguments); |
| } |
| |
| ValidationResult ScriptTool::Validate() { |
| return ValidationResult(MakeOkResult()); |
| } |
| |
| bool ScriptTool::EnsureTargetInView() { |
| return false; |
| } |
| |
| } // namespace actor |