blob: 98ba3ffdf7071191b6b6f49ee29ae803e3cf64a2 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_CONTROLLER_H_
#define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_CONTROLLER_H_
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "base/callback_helpers.h"
#include "base/macros.h"
#include "base/optional.h"
#include "components/autofill_assistant/browser/basic_interactions.h"
#include "components/autofill_assistant/browser/bottom_sheet_state.h"
#include "components/autofill_assistant/browser/client.h"
#include "components/autofill_assistant/browser/client_settings.h"
#include "components/autofill_assistant/browser/element_area.h"
#include "components/autofill_assistant/browser/event_handler.h"
#include "components/autofill_assistant/browser/metrics.h"
#include "components/autofill_assistant/browser/public/runtime_manager_impl.h"
#include "components/autofill_assistant/browser/script.h"
#include "components/autofill_assistant/browser/script_executor_delegate.h"
#include "components/autofill_assistant/browser/script_tracker.h"
#include "components/autofill_assistant/browser/service.pb.h"
#include "components/autofill_assistant/browser/service/service.h"
#include "components/autofill_assistant/browser/state.h"
#include "components/autofill_assistant/browser/trigger_context.h"
#include "components/autofill_assistant/browser/ui_delegate.h"
#include "components/autofill_assistant/browser/user_action.h"
#include "components/autofill_assistant/browser/user_data.h"
#include "components/autofill_assistant/browser/user_model.h"
#include "components/autofill_assistant/browser/web/element_store.h"
#include "components/autofill_assistant/browser/web/web_controller.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/browser/web_contents_observer.h"
namespace content {
class RenderFrameHost;
class WebContents;
} // namespace content
namespace base {
class TickClock;
} // namespace base
namespace autofill_assistant {
class ControllerTest;
// Autofill assistant controller controls autofill assistant action detection,
// display, execution and so on. The instance of this object self deletes when
// the web contents is being destroyed.
class Controller : public ScriptExecutorDelegate,
public virtual UiDelegate,
public ScriptTracker::Listener,
private content::WebContentsObserver,
public UserModel::Observer {
public:
// |web_contents|, |client|, |tick_clock| and |runtime_manager| must remain
// valid for the lifetime of the instance. Controller will take ownership of
// |service| if specified, otherwise will create and own the default service.
Controller(content::WebContents* web_contents,
Client* client,
const base::TickClock* tick_clock,
base::WeakPtr<RuntimeManagerImpl> runtime_manager,
std::unique_ptr<Service> service);
~Controller() override;
// Let the controller know it should keep tracking script availability for the
// current domain, to be ready to report any scripts as action.
//
// Activates the controller, if needed and runs it in the background, without
// showing any UI until a script is started or Start() is called.
//
// Only the context of the first call to Track() is taken into account.
//
// If non-null |on_first_check_done| is called once the result of the first
// check of script availability are in - whether they're positive or negative.
void Track(std::unique_ptr<TriggerContext> trigger_context,
base::OnceCallback<void()> on_first_check_done);
// Returns true if we are in tracking mode and the first round of script
// checks has been completed.
bool HasRunFirstCheck() const;
// Called when autofill assistant should start.
//
// This shows a UI, containing a progress bar, and executes the first
// available autostartable script. Starts checking for scripts, if necessary.
//
// Start() does nothing if called more than once or if a script is already
// running.
//
// Start() will overwrite any context previously set by Track().
bool Start(const GURL& deeplink_url,
std::unique_ptr<TriggerContext> trigger_context);
// Returns true if the controller is in a state where UI is necessary.
bool NeedsUI() const { return needs_ui_; }
// Overrides ScriptExecutorDelegate:
const ClientSettings& GetSettings() override;
const GURL& GetCurrentURL() override;
const GURL& GetDeeplinkURL() override;
const GURL& GetScriptURL() override;
Service* GetService() override;
WebController* GetWebController() override;
ElementStore* GetElementStore() const override;
const TriggerContext* GetTriggerContext() override;
autofill::PersonalDataManager* GetPersonalDataManager() override;
WebsiteLoginManager* GetWebsiteLoginManager() override;
content::WebContents* GetWebContents() override;
std::string GetEmailAddressForAccessTokenAccount() override;
std::string GetLocale() override;
void SetTouchableElementArea(const ElementAreaProto& area) override;
void SetStatusMessage(const std::string& message) override;
std::string GetStatusMessage() const override;
void SetBubbleMessage(const std::string& message) override;
std::string GetBubbleMessage() const override;
void SetDetails(std::unique_ptr<Details> details) override;
void SetInfoBox(const InfoBox& info_box) override;
void ClearInfoBox() override;
void SetProgress(int progress) override;
bool SetProgressActiveStepIdentifier(
const std::string& active_step_identifier) override;
void SetProgressActiveStep(int active_step) override;
void SetProgressVisible(bool visible) override;
void SetProgressBarErrorState(bool error) override;
void SetStepProgressBarConfiguration(
const ShowProgressBarProto::StepProgressBarConfiguration& configuration)
override;
void SetUserActions(
std::unique_ptr<std::vector<UserAction>> user_actions) override;
void SetViewportMode(ViewportMode mode) override;
void SetPeekMode(ConfigureBottomSheetProto::PeekMode peek_mode) override;
void ExpandBottomSheet() override;
void CollapseBottomSheet() override;
bool SetForm(
std::unique_ptr<FormProto> form,
base::RepeatingCallback<void(const FormProto::Result*)> changed_callback,
base::OnceCallback<void(const ClientStatus&)> cancel_callback) override;
void ExpectNavigation() override;
bool IsNavigatingToNewDocument() override;
bool HasNavigationError() override;
void SetGenericUi(
std::unique_ptr<GenericUserInterfaceProto> generic_ui,
base::OnceCallback<void(const ClientStatus&)> end_action_callback,
base::OnceCallback<void(const ClientStatus&)>
view_inflation_finished_callback) override;
void ClearGenericUi() override;
void SetBrowseModeInvisible(bool invisible) override;
// Show the UI if it's not already shown. This is only meaningful while in
// states where showing the UI is optional, such as RUNNING, in tracking mode.
void RequireUI() override;
void SetUiShown(bool shown) override;
void AddNavigationListener(
ScriptExecutorDelegate::NavigationListener* listener) override;
void RemoveNavigationListener(
ScriptExecutorDelegate::NavigationListener* listener) override;
void AddListener(ScriptExecutorDelegate::Listener* listener) override;
void RemoveListener(ScriptExecutorDelegate::Listener* listener) override;
void SetExpandSheetForPromptAction(bool expand) override;
void SetBrowseDomainsAllowlist(std::vector<std::string> domains) override;
bool EnterState(AutofillAssistantState state) override;
void SetOverlayBehavior(
ConfigureUiStateProto::OverlayBehavior overlay_behavior) override;
void SetCollectUserDataOptions(CollectUserDataOptions* options) override;
void SetLastSuccessfulUserDataOptions(std::unique_ptr<CollectUserDataOptions>
collect_user_data_options) override;
const CollectUserDataOptions* GetLastSuccessfulUserDataOptions()
const override;
void WriteUserData(
base::OnceCallback<void(UserData*, UserData::FieldChange*)>) override;
void OnScriptError(const std::string& error_message,
Metrics::DropOutReason reason);
// Overrides autofill_assistant::UiDelegate:
AutofillAssistantState GetState() const override;
void OnUserInteractionInsideTouchableArea() override;
const Details* GetDetails() const override;
const InfoBox* GetInfoBox() const override;
int GetProgress() const override;
base::Optional<int> GetProgressActiveStep() const override;
bool GetProgressVisible() const override;
bool GetProgressBarErrorState() const override;
base::Optional<ShowProgressBarProto::StepProgressBarConfiguration>
GetStepProgressBarConfiguration() const override;
const std::vector<UserAction>& GetUserActions() const override;
bool PerformUserActionWithContext(
int index,
std::unique_ptr<TriggerContext> context) override;
std::string GetDebugContext() override;
const CollectUserDataOptions* GetCollectUserDataOptions() const override;
const UserData* GetUserData() const override;
void SetShippingAddress(
std::unique_ptr<autofill::AutofillProfile> address) override;
void SetContactInfo(
std::unique_ptr<autofill::AutofillProfile> profile) override;
void SetCreditCard(
std::unique_ptr<autofill::CreditCard> card,
std::unique_ptr<autofill::AutofillProfile> billing_profile) override;
void SetTermsAndConditions(
TermsAndConditionsState terms_and_conditions) override;
void SetLoginOption(std::string identifier) override;
void OnTextLinkClicked(int link) override;
void OnFormActionLinkClicked(int link) override;
void SetDateTimeRangeStartDate(
const base::Optional<DateProto>& date) override;
void SetDateTimeRangeStartTimeSlot(
const base::Optional<int>& timeslot_index) override;
void SetDateTimeRangeEndDate(const base::Optional<DateProto>& date) override;
void SetDateTimeRangeEndTimeSlot(
const base::Optional<int>& timeslot_index) override;
void SetAdditionalValue(const std::string& client_memory_key,
const ValueProto& value) override;
void GetTouchableArea(std::vector<RectF>* area) const override;
void GetRestrictedArea(std::vector<RectF>* area) const override;
void GetVisualViewport(RectF* visual_viewport) const override;
void OnFatalError(const std::string& error_message,
Metrics::DropOutReason reason) override;
void OnStop(const std::string& message,
const std::string& button_label) override;
void MaybeReportFirstCheckDone();
ViewportMode GetViewportMode() override;
ConfigureBottomSheetProto::PeekMode GetPeekMode() override;
BottomSheetState GetBottomSheetState() override;
void SetBottomSheetState(BottomSheetState state) override;
bool IsTabSelected() override;
void SetTabSelected(bool selected) override;
void GetOverlayColors(OverlayColors* colors) const override;
const ClientSettings& GetClientSettings() const override;
const FormProto* GetForm() const override;
const FormProto::Result* GetFormResult() const override;
void SetCounterValue(int input_index, int counter_index, int value) override;
void SetChoiceSelected(int input_index,
int choice_index,
bool selected) override;
void AddObserver(ControllerObserver* observer) override;
void RemoveObserver(const ControllerObserver* observer) override;
void DispatchEvent(const EventHandler::EventKey& key) override;
UserModel* GetUserModel() override;
EventHandler* GetEventHandler() override;
bool ShouldPromptActionExpandSheet() const override;
BasicInteractions* GetBasicInteractions() override;
const GenericUserInterfaceProto* GetGenericUiProto() const override;
bool ShouldShowOverlay() const override;
void ShutdownIfNecessary() override;
bool IsRunningLiteScript() const override;
void OnKeyboardVisibilityChanged(bool visible) override;
private:
friend ControllerTest;
void SetWebControllerForTest(std::unique_ptr<WebController> web_controller);
// Called when the committed URL has or might have changed.
void OnUrlChange();
// Returns true if the controller should keep checking for scripts according
// to the current state.
bool ShouldCheckScripts();
// If the controller's state requires scripts to be checked, check them
// once right now and schedule regular checks. Otherwise, do nothing.
void GetOrCheckScripts();
void OnGetScripts(const GURL& url,
int http_status,
const std::string& response);
// Execute |script_path| and, if execution succeeds, enter |end_state| and
// call |on_success|.
void ExecuteScript(const std::string& script_path,
const std::string& start_message,
bool needs_ui,
std::unique_ptr<TriggerContext> context,
AutofillAssistantState end_state);
void OnScriptExecuted(const std::string& script_path,
AutofillAssistantState end_state,
const ScriptExecutor::Result& result);
// Check script preconditions every few seconds for a certain number of times.
// If checks are already running, StartPeriodicScriptChecks resets the count.
//
// TODO(crbug.com/806868): Find a better solution. This is a brute-force
// solution that reacts slowly to changes.
void StartPeriodicScriptChecks();
void StopPeriodicScriptChecks();
void OnPeriodicScriptCheck();
// Runs autostart scripts from |runnable_scripts|, if the conditions are
// right. Returns true if a script was auto-started.
bool MaybeAutostartScript(const std::vector<ScriptHandle>& runnable_scripts);
void DisableAutostart();
void InitFromParameters();
// Called when a script is selected.
void OnScriptSelected(const ScriptHandle& handle,
std::unique_ptr<TriggerContext> context);
void UpdateCollectUserDataActions();
void OnCollectUserDataContinueButtonClicked();
void OnCollectUserDataAdditionalActionTriggered(int index);
// Overrides ScriptTracker::Listener:
void OnNoRunnableScriptsForPage() override;
void OnRunnableScriptsChanged(
const std::vector<ScriptHandle>& runnable_scripts) override;
// Overrides content::WebContentsObserver:
void DidFinishLoad(content::RenderFrameHost* render_frame_host,
const GURL& validated_url) override;
void DidStartNavigation(
content::NavigationHandle* navigation_handle) override;
void DidFinishNavigation(
content::NavigationHandle* navigation_handle) override;
void DocumentAvailableInMainFrame() override;
void RenderProcessGone(base::TerminationStatus status) override;
void OnWebContentsFocused(
content::RenderWidgetHost* render_widget_host) override;
// Overrides autofill_assistant::UserModel::Observer:
void OnValueChanged(const std::string& identifier,
const ValueProto& new_value) override;
void OnTouchableAreaChanged(const RectF& visual_viewport,
const std::vector<RectF>& touchable_areas,
const std::vector<RectF>& restricted_areas);
void SetOverlayColors(std::unique_ptr<OverlayColors> colors);
void ReportNavigationStateChanged();
void SetProfile(const std::string& key,
UserData::FieldChange field_change,
std::unique_ptr<autofill::AutofillProfile> profile);
// Show the first "Opening..." message and enter START state.
void ShowFirstMessageAndStart();
// Clear out visible state and enter the stopped state.
void EnterStoppedState();
ElementArea* touchable_element_area();
ScriptTracker* script_tracker();
bool allow_autostart() { return state_ == AutofillAssistantState::STARTING; }
void RecordDropOutOrShutdown(Metrics::DropOutReason reason);
void PerformDelayedShutdownIfNecessary();
bool StateNeedsUI(AutofillAssistantState state);
void SetVisibilityAndUpdateUserActions();
ClientSettings settings_;
Client* const client_;
const base::TickClock* const tick_clock_;
base::WeakPtr<RuntimeManagerImpl> runtime_manager_;
// Lazily instantiate in GetWebController().
std::unique_ptr<WebController> web_controller_;
// Lazily initiate in GetElementStore();
mutable std::unique_ptr<ElementStore> element_store_;
// Lazily instantiate in GetService().
std::unique_ptr<Service> service_;
std::unique_ptr<TriggerContext> trigger_context_;
AutofillAssistantState state_ = AutofillAssistantState::INACTIVE;
bool can_recover_from_stopped_ = false;
// The URL passed to Start(). Used only as long as there's no committed URL.
// Note that this is the deeplink passed by a caller.
GURL deeplink_url_;
// The last URL the controller requested scripts from. Note that this is
// reported to the backend in an initial get action request.
GURL script_url_;
// Whether a task for periodic checks is scheduled.
bool periodic_script_check_scheduled_ = false;
// Number of remaining periodic checks.
int periodic_script_check_count_ = 0;
// Run this script if no scripts become autostartable after
// absolute_autostart_timeout.
//
// Ignored unless |allow_autostart_| is true.
std::string autostart_timeout_script_path_;
// How long to wait for an autostartable script before failing.
base::TimeDelta autostart_timeout_;
// Ticks at which we'll have reached |autostart_timeout_|.
base::TimeTicks absolute_autostart_timeout_;
// Area of the screen that corresponds to the current set of touchable
// elements.
// Lazily instantiate in touchable_element_area().
std::unique_ptr<ElementArea> touchable_element_area_;
// Current status message, may be empty.
std::string status_message_;
// Current bubble / tooltip message, may be empty.
std::string bubble_message_;
// Current details, may be null.
std::unique_ptr<Details> details_;
// Current info box, may be null.
std::unique_ptr<InfoBox> info_box_;
// Current progress.
int progress_ = 0;
base::Optional<int> progress_active_step_;
// Current visibility of the progress bar. It is initially visible.
bool progress_visible_ = true;
bool progress_bar_error_state_ = false;
base::Optional<ShowProgressBarProto::StepProgressBarConfiguration>
step_progress_bar_configuration_;
// Current set of user actions. May be null, but never empty.
std::unique_ptr<std::vector<UserAction>> user_actions_;
// Current viewport mode.
ViewportMode viewport_mode_ = ViewportMode::NO_RESIZE;
// Current peek mode.
ConfigureBottomSheetProto::PeekMode peek_mode_ =
ConfigureBottomSheetProto::HANDLE;
bool auto_change_peek_mode_ = false;
// The latest bottom sheet state stored.
BottomSheetState bottom_sheet_state_ = BottomSheetState::UNDEFINED;
// Whether the tab associated with this controller is currently selected.
bool tab_selected_ = true;
std::unique_ptr<OverlayColors> overlay_colors_;
// A copy of the most recently set user data options. Can be used to determine
// which information was requested.
std::unique_ptr<CollectUserDataOptions> last_collect_user_data_options_;
CollectUserDataOptions* collect_user_data_options_ = nullptr;
std::unique_ptr<UserData> user_data_;
std::unique_ptr<FormProto> form_;
std::unique_ptr<FormProto::Result> form_result_;
base::RepeatingCallback<void(const FormProto::Result*)>
form_changed_callback_ = base::DoNothing();
base::OnceCallback<void(const ClientStatus&)> form_cancel_callback_ =
base::DoNothing();
// Value for ScriptExecutorDelegate::IsNavigatingToNewDocument()
bool navigating_to_new_document_ = false;
// If this is set, the controller was still navigating during startup. Wait
// for DidFinishNavigation and execute the callback.
base::OnceClosure start_after_navigation_;
// Value for ScriptExecutorDelegate::HasNavigationError()
bool navigation_error_ = false;
base::ObserverList<ScriptExecutorDelegate::NavigationListener>
navigation_listeners_;
base::ObserverList<ScriptExecutorDelegate::Listener> listeners_;
// The next DidStartNavigation will not cause an error.
bool expect_navigation_ = false;
// Tracks scripts and script execution. It's kept at the end, as it tend to
// depend on everything the controller support, through script and script
// actions.
// Lazily instantiate in script_tracker().
std::unique_ptr<ScriptTracker> script_tracker_;
base::ObserverList<ControllerObserver> observers_;
// If true, the controller is supposed to stay up and running in the
// background even without UI, keeping track of scripts.
//
// This has two main effects:
// - the controllers stays alive even after a fatal error, just so it can
// immediately report that no actions are available on that website.
// - scripts error are not considered fatal errors. The controller reverts
// to TRACKING mode.
//
// This is set by Track().
bool tracking_ = false;
// Whether the controller is in a state in which a UI should be shown.
bool needs_ui_ = false;
// Whether UI is shown.
bool ui_shown_ = false;
// True once the controller has run the first set of scripts and have either
// declared it invalid - and entered stopped state - or have processed its
// result - and updated the state and set of available actions.
bool has_run_first_check_ = false;
// Whether the overlay should be set according to state or always hidden.
ConfigureUiStateProto::OverlayBehavior overlay_behavior_ =
ConfigureUiStateProto::DEFAULT;
// Callbacks to call when |has_run_first_check_| becomes true.
std::vector<base::OnceCallback<void()>> on_has_run_first_check_;
// If set, the controller entered the STOPPED state but shutdown was delayed
// until the browser has left the |script_url_.host()| for which the decision
// was taken.
base::Optional<Metrics::DropOutReason> delayed_shutdown_reason_;
EventHandler event_handler_;
UserModel user_model_;
BasicInteractions basic_interactions_{this};
bool expand_sheet_for_prompt_action_ = true;
std::vector<std::string> browse_domains_allowlist_;
bool browse_mode_invisible_ = false;
bool is_keyboard_showing_ = false;
// Only set during a ShowGenericUiAction.
std::unique_ptr<GenericUserInterfaceProto> generic_user_interface_;
base::WeakPtrFactory<Controller> weak_ptr_factory_{this};
DISALLOW_COPY_AND_ASSIGN(Controller);
};
} // namespace autofill_assistant
#endif // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_CONTROLLER_H_