| // 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_ |