#ifndef CHROME_BROWSER_COMPOSE_CHROME_COMPOSE_CLIENT_H_
#define CHROME_BROWSER_COMPOSE_CHROME_COMPOSE_CLIENT_H_
#include <memory>
#include <optional>
#include <string>
#include "base/containers/flat_map.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/token.h"
#include "chrome/browser/compose/compose_session.h"
#include "chrome/browser/compose/proactive_nudge_tracker.h"
#include "chrome/common/compose/compose.mojom.h"
#include "components/autofill/core/browser/foundations/autofill_manager.h"
#include "components/autofill/core/browser/foundations/scoped_autofill_managers_observation.h"
#include "components/autofill/core/common/unique_ids.h"
#include "components/compose/core/browser/compose_client.h"
#include "components/compose/core/browser/compose_dialog_controller.h"
#include "components/compose/core/browser/compose_manager.h"
#include "components/compose/core/browser/compose_manager_impl.h"
#include "components/optimization_guide/core/model_execution/remote_model_executor.h"
#include "components/prefs/pref_member.h"
#include "content/public/browser/context_menu_params.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/browser/web_contents_user_data.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
namespace content {
class Page;
class WebContents;
}
namespace optimization_guide {
class ModelQualityLogsUploaderService;
class OptimizationGuideDecider;
class RemoteModelExecutor;
}
class ComposeEnabling;
class ChromeComposeClient
: public compose::ComposeClient,
public content::WebContentsObserver,
public content::WebContentsUserData<ChromeComposeClient>,
public autofill::AutofillManager::Observer,
public compose::mojom::ComposeClientUntrustedPageHandler,
public compose::ProactiveNudgeTracker::Delegate,
public ComposeSession::Observer,
public InnerTextProvider {
public:
using EntryPoint = autofill::AutofillComposeDelegate::UiEntryPoint;
class FieldChangeObserver : public autofill::AutofillManager::Observer {
public:
explicit FieldChangeObserver(content::WebContents* web_contents);
~FieldChangeObserver() override;
void OnAfterTextFieldValueChanged(
autofill::AutofillManager& manager,
autofill::FormGlobalId form,
autofill::FieldGlobalId field,
const std::u16string& text_value) override;
void OnSuggestionsShown(
autofill::AutofillManager& manager,
base::span<const autofill::Suggestion> suggestions) override;
void HideComposeNudges();
void SetSkipSuggestionTypeForTest(bool skip_suggestion_type);
bool skip_suggestion_type_for_test_ = false;
raw_ptr<content::WebContents> web_contents_;
unsigned int text_field_value_change_event_count_ = 0;
autofill::ScopedAutofillManagersObservation autofill_managers_observation_{
this};
};
ChromeComposeClient(const ChromeComposeClient&) = delete;
ChromeComposeClient& operator=(const ChromeComposeClient&) = delete;
~ChromeComposeClient() override;
compose::ComposeManager& GetManager() override;
void ShowComposeDialog(
EntryPoint ui_entry_point,
const autofill::FormFieldData& trigger_field,
std::optional<autofill::AutofillClient::PopupScreenLocation>
popup_screen_location,
ComposeCallback callback) override;
bool HasSession(const autofill::FieldGlobalId& trigger_field_id) override;
bool ShouldTriggerPopup(
const autofill::FormData& form_data,
const autofill::FormFieldData& trigger_field,
autofill::AutofillSuggestionTriggerSource trigger_source) override;
compose::PageUkmTracker* GetPageUkmTracker() override;
void DisableProactiveNudge() override;
void OpenProactiveNudgeSettings() override;
void AddSiteToNeverPromptList(const url::Origin& origin) override;
void OnSessionComplete(autofill::FieldGlobalId field_global_id,
compose::ComposeSessionCloseReason close_reason,
const compose::ComposeSessionEvents& events) override;
void OnAfterFocusOnFormField(autofill::AutofillManager& manager,
autofill::FormGlobalId form,
autofill::FieldGlobalId field) override;
void ShowUI() override;
void CloseUI(compose::mojom::CloseReason reason) override;
void CompleteFirstRun() override;
void OpenComposeSettings() override;
void GetInnerText(content::RenderFrameHost& host,
std::optional<int> node_id,
content_extraction::InnerTextCallback callback) override;
bool GetMSBBStateFromPrefs();
void UpdateAllSessionsWithFirstRunComplete();
virtual bool ShouldTriggerContextMenu(content::RenderFrameHost* rfh,
content::ContextMenuParams& params);
void BindComposeDialog(
mojo::PendingReceiver<compose::mojom::ComposeClientUntrustedPageHandler>
client_handler,
mojo::PendingReceiver<compose::mojom::ComposeSessionUntrustedPageHandler>
handler,
mojo::PendingRemote<compose::mojom::ComposeUntrustedDialog> dialog);
void PrimaryPageChanged(content::Page& page) override;
void OnWebContentsFocused(
content::RenderWidgetHost* render_widget_host) override;
void DidGetUserInteraction(const blink::WebInputEvent& event) override;
void OnFocusChangedInPage(
const content::FocusedNodeDetails& details) override;
void ShowProactiveNudge(autofill::FormGlobalId form,
autofill::FieldGlobalId field,
compose::ComposeEntryPoint entry_point) override;
compose::ComposeHintMetadata GetComposeHintMetadata() override;
ComposeEnabling& GetComposeEnabling();
bool IsDialogShowing();
bool IsPopupTimerRunning();
int GetSessionCountForTest();
void SetOptimizationGuideForTest(
optimization_guide::OptimizationGuideDecider* opt_guide);
void SetModelExecutorForTest(
optimization_guide::RemoteModelExecutor* model_executor);
void SetModelQualityLogsUploaderServiceForTest(
optimization_guide::ModelQualityLogsUploaderService*
model_quality_logs_uploader_service);
void SetSkipShowDialogForTest(bool should_skip);
void SetSessionIdForTest(base::Token session_id);
void SetInnerTextProviderForTest(InnerTextProvider* inner_text);
void OpenFeedbackPageForTest(std::string feedback_id);
protected:
explicit ChromeComposeClient(content::WebContents* web_contents);
optimization_guide::RemoteModelExecutor* GetModelExecutor();
optimization_guide::ModelQualityLogsUploaderService*
GetModelQualityLogsUploaderService();
optimization_guide::OptimizationGuideDecider* GetOptimizationGuide();
base::Token GetSessionId();
InnerTextProvider* GetInnerTextProvider();
std::unique_ptr<ComposeEnabling> compose_enabling_;
private:
friend class content::WebContentsUserData<ChromeComposeClient>;
FRIEND_TEST_ALL_PREFIXES(ChromeComposeClientTest,
TestComposeQualityFeedbackPositive);
FRIEND_TEST_ALL_PREFIXES(ChromeComposeClientTest,
TestComposeQualityFeedbackNegative);
FRIEND_TEST_ALL_PREFIXES(ChromeComposeClientTest,
TextFieldChangeThresholdHidesProactiveNudge);
raw_ptr<Profile> profile_;
raw_ptr<PrefService> pref_service_;
void PrepareToResumeExistingSession(ComposeCallback callback,
bool has_selection,
bool popup_clicked);
void CreateNewSession(ComposeCallback callback,
const autofill::FormFieldData& trigger_field,
std::string_view selected_text,
bool popup_clicked);
void SetFirstRunSessionCloseReason(
compose::ComposeFreOrMsbbSessionCloseReason close_reason);
void SetMSBBSessionCloseReason(
compose::ComposeFreOrMsbbSessionCloseReason close_reason);
void SetSessionCloseReason(compose::ComposeSessionCloseReason close_reason);
void LaunchHatsSurveyForActiveSession(
compose::ComposeSessionCloseReason close_reason);
void RemoveActiveSession();
void RemoveAllSessions();
void ShowSavedStateNotification(autofill::FieldGlobalId field_id);
ComposeSession* GetSessionForActiveComposeField();
bool ActiveFieldHasUnexpiredSession();
bool IsPageLanguageSupported();
compose::ComposeManagerImpl manager_{this};
std::unique_ptr<compose::ComposeDialogController> compose_dialog_controller_;
raw_ptr<optimization_guide::OptimizationGuideDecider> opt_guide_;
std::optional<optimization_guide::RemoteModelExecutor*>
model_executor_for_test_;
std::optional<optimization_guide::ModelQualityLogsUploaderService*>
logs_uploader_service_for_test_;
std::optional<base::Token> session_id_for_test_;
std::optional<FieldIdentifier> active_compose_ids_;
autofill::AutofillSuggestionTriggerSource last_popup_trigger_source_ =
autofill::AutofillSuggestionTriggerSource::kUnspecified;
std::optional<InnerTextProvider*> inner_text_provider_for_test_;
base::flat_map<autofill::FieldGlobalId, std::unique_ptr<ComposeSession>>
sessions_;
mojo::Receiver<compose::mojom::ComposeClientUntrustedPageHandler>
client_page_receiver_{this};
base::TimeTicks show_dialog_start_;
std::unique_ptr<ComposeSession> debug_session_;
std::unique_ptr<compose::PageUkmTracker> page_ukm_tracker_;
bool skip_show_dialog_for_test_ = false;
bool open_settings_requested_ = false;
compose::ProactiveNudgeTracker nudge_tracker_;
FieldChangeObserver field_change_observer_;
autofill::ScopedAutofillManagersObservation autofill_managers_observation_{
this};
BooleanPrefMember proactive_nudge_enabled_;
base::TimeTicks page_change_time_;
compose::ComposeEntryPoint most_recent_nudge_entry_point_ =
compose::ComposeEntryPoint::kProactiveNudge;
base::WeakPtrFactory<ChromeComposeClient> weak_ptr_factory_{this};
WEB_CONTENTS_USER_DATA_KEY_DECL();
};
#endif