blob: ba5d1303d27bb48d94c6d3b232423705a3832d93 [file]
// 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_ACTOR_EXECUTION_ENGINE_H_
#define CHROME_BROWSER_ACTOR_EXECUTION_ENGINE_H_
#include <memory>
#include <optional>
#include <vector>
#include "base/callback_list.h"
#include "base/functional/callback.h"
#include "base/functional/callback_forward.h"
#include "base/memory/raw_ref.h"
#include "base/memory/safe_ref.h"
#include "base/memory/weak_ptr.h"
#include "base/sequence_checker.h"
#include "base/types/id_type.h"
#include "base/types/optional_ref.h"
#include "base/types/pass_key.h"
#include "chrome/browser/actor/aggregated_journal.h"
#include "chrome/browser/actor/origin_checker.h"
#include "chrome/browser/actor/site_policy.h"
#include "chrome/browser/actor/tools/tool_controller.h"
#include "chrome/browser/actor/tools/tool_delegate.h"
#include "chrome/browser/password_manager/actor_login/actor_login_service.h"
#include "chrome/common/actor.mojom-forward.h"
#include "chrome/common/actor/task_id.h"
#include "chrome/common/buildflags.h"
#include "components/autofill/core/browser/integrators/actor/actor_form_filling_types.h"
#include "components/password_manager/core/browser/actor_login/actor_login_types.h"
#include "components/tabs/public/tab_interface.h"
#include "content/public/browser/navigation_throttle.h"
#include "content/public/browser/web_contents_observer.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
#include "third_party/abseil-cpp/absl/container/flat_hash_set.h"
class Profile;
namespace affiliations {
struct Facet;
} // namespace affiliations
namespace base {
class ScopedUmaHistogramTimer;
}
namespace content {
class NavigationHandle;
}
namespace url {
class Origin;
} // namespace url
namespace actor {
struct ActionResultWithLatencyInfo;
class ActorTask;
class AutofillSelectionDialogEventHandler;
class ToolRequest;
namespace ui {
class UiEventDispatcher;
}
// Coordinates the execution of a multi-step task.
class ExecutionEngine : public ToolDelegate,
public actor_login::ActionSequenceDelegate {
public:
// State machine (success case)
//
// Init
// |
// v
// StartAction -> ToolCreateAndVerify ->
// ^ UiPreInvoke -> ToolInvoke -> UiPostInvoke -> Complete
// | | |
// |___________________________________________|______________|
//
// Complete may also be reached directly from other states in case of error.
// LINT.IfChange(State)
enum class State {
kInit = 0,
kStartAction,
kToolCreateAndVerify,
kUiPreInvoke,
kToolInvoke,
kUiPostInvoke,
kComplete,
};
// LINT.ThenChange(//tools/metrics/histograms/metadata/actor/enums.xml:ActorEngineState)
// This enum represents the possible outcomes of the synchronous part of the
// navigation gating logic.
// LINT.IfChange(GatingDecision)
// These enum values are persisted to logs. Do not renumber or reuse numeric
// values.
enum class GatingDecision {
// The source origin and navigation origin are the same and should not be
// gated.
kAllowSameOrigin = 0,
// The navigation is allowed by the static allowlist or enterprise policy
// allowlist.
kAllowByStaticList = 1,
// The navigation is blocked by the static blocklist or enterprise policy
// blocklist. The user will not be prompted for confirmation.
kBlockByStaticList = 2,
// The navigation is not on any allowlist or blocklist and requires an
// asynchronous check to determine the final outcome.
kNeedsAsyncCheck = 3,
kMaxValue = kNeedsAsyncCheck,
};
// LINT.ThenChange(//tools/metrics/histograms/metadata/actor/enums.xml:GatingDecision)
class StateObserver : public base::CheckedObserver {
public:
~StateObserver() override = default;
virtual void OnStateChanged(State old_state, State new_state) = 0;
};
// This enum is used for a UKM metric for recording how often actor
// navigations require server confirmation.
// LINT.IfChange(ActorServerConfirmationResult)
enum class ActorServerConfirmationResult {
// Server confirmation is not required. This is emitted when the actor
// navigates to an origin not on a allow-/block-/confirm-list that does not
// require server confirmation because it was already evaluated.
kNotRequired = 0,
// Server confirmation accepted the origin for navigation.
kAccepted = 1,
// Server confirmation rejected the origin for navigation.
kRejected = 2,
kMaxValue = kRejected
};
// LINT.ThenChange(//tools/metrics/histograms/actor/enums.xml:ActorServerConfirmationResult)
// Tests can provide a factory function which will be used to create
// test-instrumented ExecutionEngine instances. See the
// ScopedExecutionEngineFactory helper.
using FactoryFunction =
base::RepeatingCallback<std::unique_ptr<ExecutionEngine>(ActorTask&)>;
static FactoryFunction& GetFactoryFunctionForTesting();
static std::unique_ptr<ExecutionEngine> Create(ActorTask& owner_task);
static std::unique_ptr<ExecutionEngine> CreateForTesting(
ActorTask& owner_task,
std::unique_ptr<ui::UiEventDispatcher> ui_event_dispatcher);
// Constructors public for std::make_unique but only usable via static Create
// method.
explicit ExecutionEngine(base::PassKey<ExecutionEngine>,
ActorTask& owner_task);
ExecutionEngine(base::PassKey<ExecutionEngine>,
ActorTask& owner_task,
std::unique_ptr<ui::UiEventDispatcher> ui_event_dispatcher);
ExecutionEngine(const ExecutionEngine&) = delete;
ExecutionEngine& operator=(const ExecutionEngine&) = delete;
~ExecutionEngine() override;
// Cancels any ongoing actions.
void CancelOngoingActions(mojom::ActionResultCode reason);
// If there is an ongoing tool request, treat it as having failed with the
// given reason.
void FailCurrentTool(mojom::ActionResultCode reason) override;
// Performs the given tool actions and invokes the callback when completed.
using ActCallback =
base::OnceCallback<void(std::vector<ActionResultWithLatencyInfo>)>;
void Act(std::vector<std::unique_ptr<ToolRequest>>&& actions,
ActCallback callback);
// Invalidated anytime `action_sequence_` is reset.
base::WeakPtr<ExecutionEngine> GetWeakPtr();
bool HasActionSequence() const;
// ToolDelegate:
Profile& GetProfile() override;
AggregatedJournal& GetJournal() override;
favicon::FaviconService* GetFaviconService() override;
void IsAcceptableNavigationDestination(
const GURL& url,
DecisionCallbackWithReason callback) override;
autofill::ActorFormFillingService& GetActorFormFillingService() override;
actor_login::ActorLoginService& GetActorLoginService() override;
void PromptToSelectCredential(
const std::vector<actor_login::Credential>& credentials,
const base::flat_map<std::string, gfx::Image>& icons,
ToolDelegate::CredentialSelectedCallback callback) override;
void SetUserSelectedCredential(
const CredentialWithPermission& credential,
base::OnceClosure affiliations_fetched) override;
const std::optional<CredentialWithPermission> GetUserSelectedCredential(
const url::Origin& request_origin) const override;
void RequestToShowAutofillSuggestions(
std::vector<autofill::ActorFormFillingRequest> requests,
base::WeakPtr<AutofillSelectionDialogEventHandler> event_handler,
AutofillSuggestionSelectedCallback callback) override;
void InterruptFromTool() override;
void UninterruptFromTool() override;
void EnqueueFollowupAction(std::unique_ptr<ToolRequest> action) override;
base::WeakPtr<actor_login::ActionSequenceDelegate> GetActionSequenceDelegate()
override;
// actor_login::ActionSequenceDelegate:
base::CallbackListSubscription RegisterActionSequenceEnded(
base::OnceCallback<void(bool /*success*/)> callback) override;
void OnFederatedLoginOutcome(actor_login::LoginStatusResult result) override;
void AddWritableMainframeOrigins(
const absl::flat_hash_set<url::Origin>& added_writable_mainframe_origins);
// Callback to intercept and handle credential selection for actor login
// programmatically.
using CredentialSelectionOverrideCallback =
base::OnceCallback<void(const std::vector<actor_login::Credential>&,
ToolDelegate::CredentialSelectedCallback)>;
void PreHandleCredentialSelectionDialog(
CredentialSelectionOverrideCallback callback);
// Callback invoked when ConfirmCrossOriginNavigation, which spawns an IPC to
// the web client, receives its response. This callback gets a boolean
// indicating if navigation should continue.
using NavigationDecisionCallback =
base::OnceCallback<void(bool may_continue)>;
// Returns a value indicating how the given navigation should be handled
// (proceed, cancel and ignore, defer, etc.). This method must only be called
// on the primary main frame or a prerendered main frame. `callback` will be
// invoked iff this function returns `content::NavigationThrottle::DEFER`.
content::NavigationThrottle::ThrottleAction ShouldDeferNavigation(
content::NavigationHandle& navigation_handle,
NavigationDecisionCallback callback);
static std::string StateToString(State state);
void OnMayActOnTabDecision(const url::Origin& evaluated_origin,
MayActOnUrlBlockReason block_reason);
void UserTakeover(mojom::ActionResultCode takeover_response_code,
base::OnceCallback<void(bool)> callback);
void RunUserTakeoverCallbackIfExists(bool should_cancel);
void set_user_take_over_result(
std::optional<mojom::ActionResultCode> user_takeover_result) {
user_takeover_result_ = user_takeover_result;
}
std::optional<mojom::ActionResultCode> user_take_over_result() const {
return user_takeover_result_;
}
const base::flat_map<url::Origin, url::Origin>&
GetAffiliatedOriginMapForTesting() const {
return affiliated_origin_map_;
}
void AddObserver(StateObserver* observer);
void RemoveObserver(StateObserver* observer);
void DidUninterruptTask();
void set_tool_invoke_complete_callback_for_testing(
base::OnceClosure callback) {
tool_invoke_complete_callback_for_testing_ = std::move(callback);
}
State state() const { return state_; }
// Currently, navigations are generally forced to happen in the same tab (see
// https://crbug.com/420669167 ). In some cases we need to drop this
// restriction for certain tools to function.
bool TabsCanOpenNewWebContents() const;
protected:
// Allow derived classes to use the natural constructors.
explicit ExecutionEngine(ActorTask& owner_task);
private:
class NewTabWebContentsObserver;
void SetState(State state);
// Starts the next action by calling SafetyChecksForNextAction(). Must only be
// called if there is a next action.
void KickOffNextAction();
// Performs safety checks for next action. This is asynchronous.
void SafetyChecksForNextAction();
// Performs synchronous safety checks for the next action. If everything
// passes calls tool_controller_.Invoke().
void DidFinishAsyncSafetyChecks(const url::Origin& evaluated_origin,
mojom::ActionResultCode result_code);
// If a failure occurs before the next action starts, we associate the tab
// that the action would have acted on with the task, so that we can provide
// tab observations back to the client.
void FailedOnTabBeforeToolCreation();
// Synchronously executes the next action. There are several types of actions,
// including renderer-scoped actions, tab-scoped actions, and global actions.
void ExecuteNextAction();
// Called each time an action finishes.
void PostToolCreate(mojom::ActionResultPtr result);
void FinishedUiPreInvoke(mojom::ActionResultPtr result);
void FinishedToolInvoke(mojom::ActionResultPtr result);
void FinishedUiPostInvoke(mojom::ActionResultPtr result);
void CompleteActions(mojom::ActionResultPtr result,
std::optional<size_t> action_index);
// Returns the next action that will be started when ExecuteNextAction is
// reached.
const ToolRequest& GetNextAction() const;
// Maps an index in `action_sequence_` to an index in `action_results_` by
// counting how many original (non-follow-up) actions preceded it.
size_t GetResultIndexForAction(size_t action_index) const;
// Processes the affiliation service results for the given `source_origin`.
// and saves it into `affiliated_origin_map_`.
void OnAffiliationsReceived(const url::Origin& source_origin,
base::OnceClosure affiliations_fetched,
const std::vector<affiliations::Facet>& results,
bool success);
// Returns the index / action that was last executed and is still in progress.
// It is an error to call this when an action is not in progress.
size_t InProgressActionIndex() const;
const ToolRequest& GetInProgressAction() const;
void LogNavigationGating(const url::Origin& source,
base::optional_ref<const url::Origin> initiator,
const url::Origin& destination,
bool applied_gate) const;
// Returns the highest-priority navigation gating decision. Prioritizes
// blocking navigations over allowing (except on same origin navigations).
GatingDecision DetermineGatingDecision(const GURL& source_url,
const GURL& destination_url) const;
void CheckNavigationSensitiveUrlList(
const url::Origin& source,
const std::optional<url::Origin>& initiator,
const GURL& destination_url,
ukm::SourceId ukm_source_id,
bool skip_prompt,
base::ScopedUmaHistogramTimer timer,
NavigationDecisionCallback callback);
void OnNavigationSensitiveUrlListChecked(
const url::Origin& source,
const std::optional<url::Origin>& initiator,
const url::Origin& destination,
ukm::SourceId ukm_source_id,
bool skip_prompt,
base::ScopedUmaHistogramTimer timer,
NavigationDecisionCallback callback,
bool not_sensitive);
// Called when the browser detects the actor needs to confirm a
// client-side-initiated navigation to a novel origin.
void HandleNavigationToNewOrigin(
const url::Origin& destination,
ukm::SourceId ukm_source_id,
base::ScopedUmaHistogramTimer timer,
ExecutionEngine::NavigationDecisionCallback callback);
void SendNavigationConfirmationRequest(const url::Origin& destination,
ukm::SourceId ukm_source_id,
base::ScopedUmaHistogramTimer timer,
NavigationDecisionCallback callback);
void OnNavigationConfirmationDecision(
const url::Origin& destination,
ukm::SourceId ukm_source_id,
base::ScopedUmaHistogramTimer timer,
NavigationDecisionCallback callback,
webui::mojom::NavigationConfirmationResponsePtr response);
void MaybeRecordNavigationConfirmationMetrics(
ExecutionEngine::State state_for_metrics,
const url::Origin& destination,
bool is_pre_approved);
// Makes the web client confirm with the user that the actor is allowed to
// navigate to this origin.
void SendUserConfirmationDialogRequest(
const url::Origin& destination,
bool for_sensitive_origin,
std::optional<base::ScopedUmaHistogramTimer> timer,
NavigationDecisionCallback callback);
void OnPromptUserToConfirmNavigationDecision(
const url::Origin& destination,
NavigationDecisionCallback callback,
webui::mojom::UserConfirmationDialogResponsePtr response);
State state_ = State::kInit;
static std::optional<base::TimeDelta> action_observation_delay_for_testing_;
// Owns `this`.
const base::raw_ref<ActorTask> task_;
base::SafeRef<AggregatedJournal> journal_;
// Created when task_ is set. Handles execution details for an individual tool
// request.
std::unique_ptr<ToolController> tool_controller_;
std::unique_ptr<actor_login::ActorLoginService> actor_login_service_;
std::unique_ptr<autofill::ActorFormFillingService>
actor_form_filling_service_;
std::unique_ptr<ui::UiEventDispatcher> ui_event_dispatcher_;
base::flat_map<url::Origin, url::Origin> affiliated_origin_map_;
std::vector<std::unique_ptr<ToolRequest>> action_sequence_;
ActCallback act_callback_;
// The index of the next action that will be started when ExecuteNextAction is
// reached.
size_t next_action_index_ = 0;
base::TimeTicks action_start_time_;
// If set, the currently executing tool should be considered failed once it
// completes.
std::optional<mojom::ActionResultCode> external_tool_failure_reason_;
// The results for actions so far.
std::vector<ActionResultWithLatencyInfo> action_results_;
// Manages the sets of origins that have been allowed for navigations and that
// the user has been prompted about.
OriginChecker origin_checker_;
// For overwriting the actor login permission, currently only works for the
// feature `kPasswordCheckupPrototype` for automated password changes.
CredentialSelectionOverrideCallback credential_selection_override_callback_;
// For multi-step login, this is the credential that the user has chosen to
// allow the actor to use. The key is the
// `Credential::request_origin`.
base::flat_map<url::Origin, CredentialWithPermission>
user_selected_credentials_;
base::OnceCallback<void(bool /*should_cancel*/)> user_takeover_callback_;
std::optional<mojom::ActionResultCode> user_takeover_result_;
base::ObserverList<StateObserver> observers_;
base::OnceCallbackList<void(bool /*success*/)>
action_sequence_ended_callbacks_;
// If a tool finishes while the task is in a waiting state, the finish
// callback and processing is deferred until the task is resumed.
base::OnceClosure deferred_finish_tool_invoke_;
base::OnceClosure tool_invoke_complete_callback_for_testing_;
SEQUENCE_CHECKER(sequence_checker_);
// Normally, a WeakPtrFactory only invalidates its WeakPtrs when the object is
// destroyed. However, this class invalidates WeakPtrs anytime a new set of
// actions is passed in. This effectively cancels any ongoing async actions.
base::WeakPtrFactory<ExecutionEngine> actions_weak_ptr_factory_{this};
};
std::ostream& operator<<(std::ostream& o, const ExecutionEngine::State& s);
} // namespace actor
#endif // CHROME_BROWSER_ACTOR_EXECUTION_ENGINE_H_