| // Copyright 2024 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/browser/glic/host/glic_page_handler.h" |
| |
| #include "base/callback_list.h" |
| #include "base/feature_list.h" |
| #include "base/files/file_util.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_forward.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/notimplemented.h" |
| #include "base/observer_list.h" |
| #include "base/observer_list_types.h" |
| #include "base/scoped_multi_source_observation.h" |
| #include "base/scoped_observation.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/time/time.h" |
| #include "base/timer/timer.h" |
| #include "base/uuid.h" |
| #include "base/version_info/version_info.h" |
| #include "chrome/browser/actor/actor_keyed_service.h" |
| #include "chrome/browser/actor/actor_policy_checker.h" |
| #include "chrome/browser/actor/actor_task.h" |
| #include "chrome/browser/actor/aggregated_journal.h" |
| #include "chrome/browser/actor/aggregated_journal_file_serializer.h" |
| #include "chrome/browser/actor/aggregated_journal_in_memory_serializer.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/contextual_cueing/contextual_cueing_features.h" |
| #include "chrome/browser/enterprise/browser_management/management_service_factory.h" |
| #include "chrome/browser/feedback/feedback_uploader_chrome.h" |
| #include "chrome/browser/feedback/feedback_uploader_factory_chrome.h" |
| #include "chrome/browser/feedback/system_logs/chrome_system_logs_fetcher.h" |
| #include "chrome/browser/glic/glic_hotkey.h" |
| #include "chrome/browser/glic/glic_metrics.h" |
| #include "chrome/browser/glic/glic_pref_names.h" |
| #include "chrome/browser/glic/glic_profile_manager.h" |
| #include "chrome/browser/glic/glic_settings_util.h" |
| #include "chrome/browser/glic/host/auth_controller.h" |
| #include "chrome/browser/glic/host/context/glic_focused_browser_manager.h" |
| #include "chrome/browser/glic/host/context/glic_tab_data.h" |
| #include "chrome/browser/glic/host/glic.mojom.h" |
| #include "chrome/browser/glic/host/glic_annotation_manager.h" |
| #include "chrome/browser/glic/host/glic_features.mojom.h" |
| #include "chrome/browser/glic/host/glic_synthetic_trial_manager.h" |
| #include "chrome/browser/glic/host/glic_web_client_access.h" |
| #include "chrome/browser/glic/host/host.h" |
| #include "chrome/browser/glic/host/page_metadata_manager.h" |
| #include "chrome/browser/glic/media/glic_media_link_helper.h" |
| #include "chrome/browser/glic/public/context/glic_sharing_manager.h" |
| #include "chrome/browser/glic/public/glic_enabling.h" |
| #include "chrome/browser/glic/public/glic_keyed_service.h" |
| #include "chrome/browser/glic/public/glic_keyed_service_factory.h" |
| #include "chrome/browser/glic/service/glic_instance_metrics.h" |
| #include "chrome/browser/glic/widget/browser_conditions.h" |
| #include "chrome/browser/glic/widget/glic_window_controller.h" |
| #include "chrome/browser/global_features.h" |
| #include "chrome/browser/lens/region_search/lens_region_search_controller.h" |
| #include "chrome/browser/media/audio_ducker.h" |
| #include "chrome/browser/permissions/system/system_permission_settings.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/profiles/profile_attributes_storage.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/browser/signin/identity_manager_factory.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_finder.h" |
| #include "chrome/browser/ui/browser_navigator.h" |
| #include "chrome/browser/ui/browser_navigator_params.h" |
| #include "chrome/browser/ui/browser_window/public/browser_window_interface_iterator.h" |
| #include "chrome/browser/ui/tabs/tab_model.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model_observer.h" |
| #include "chrome/common/actor/journal_details_builder.h" |
| #include "chrome/common/actor/task_id.h" |
| #include "chrome/common/actor_webui.mojom.h" |
| #include "chrome/common/chrome_features.h" |
| #include "components/autofill/core/browser/integrators/glic/actor_form_filling_types.h" |
| #include "components/content_settings/core/common/content_settings_types.h" |
| #include "components/feedback/content/content_tracing_manager.h" |
| #include "components/feedback/feedback_data.h" |
| #include "components/feedback/feedback_uploader.h" |
| #include "components/feedback/system_logs/system_logs_fetcher.h" |
| #include "components/metrics/metrics_service.h" |
| #include "components/optimization_guide/content/browser/page_content_metadata_observer.h" |
| #include "components/optimization_guide/core/model_quality/model_quality_util.h" |
| #include "components/password_manager/core/browser/actor_login/actor_login_types.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/sessions/content/session_tab_helper.h" |
| #include "components/sessions/core/session_id.h" |
| #include "components/signin/public/identity_manager/identity_manager.h" |
| #include "components/tabs/public/tab_interface.h" |
| #include "content/public/browser/devtools_agent_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "extensions/browser/guest_view/web_view/web_view_guest.h" |
| #include "media/base/media_switches.h" |
| #include "mojo/public/cpp/bindings/callback_helpers.h" |
| #include "mojo/public/cpp/bindings/message.h" |
| #include "pdf/buildflags.h" |
| #include "third_party/abseil-cpp/absl/container/flat_hash_map.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/skia/include/core/SkBitmap.h" |
| #include "ui/gfx/geometry/mojom/geometry.mojom.h" |
| #include "ui/gfx/geometry/size.h" |
| #include "ui/views/widget/widget.h" |
| |
| namespace mojo { |
| |
| // Specializes a Mojo EqualsTraits to allow equality checks of SkBitmaps, so |
| // that `FocusedTabData` can be compared for equality. Given the unoptimized |
| // nature of the image comparison logic, this trait is being made available only |
| // within this compilation unit. |
| // TODO(b/426792593): avoid a glic-specific specialization here. |
| template <> |
| struct EqualsTraits<::SkBitmap> { |
| static bool Equals(const ::SkBitmap& a, const ::SkBitmap& b) { |
| return glic::FaviconEquals(a, b); |
| } |
| }; |
| |
| } // namespace mojo |
| |
| namespace glic { |
| |
| namespace { |
| |
| mojom::GetContextResultPtr LogErrorAndUnwrapResult( |
| base::OnceCallback<void(GlicGetContextFromTabError)> error_logger, |
| GlicGetContextResult result) { |
| if (!result.has_value()) { |
| std::move(error_logger).Run(result.error().error_code); |
| return mojom::GetContextResult::NewErrorReason(result.error().message); |
| } |
| return std::move(result.value()); |
| } |
| |
| // Monitors the panel state and the browser widget state. Emits an event any |
| // time the active state changes. |
| // inactive = (panel hidden) || (panel attached) && (window not active) |
| class ActiveStateCalculator : public PanelStateObserver { |
| public: |
| // Observes changes to active state. |
| class Observer : public base::CheckedObserver { |
| public: |
| virtual void ActiveStateChanged(bool is_active) = 0; |
| }; |
| |
| explicit ActiveStateCalculator(Host* host) : host_(host) { |
| host_->AddPanelStateObserver(this); |
| PanelStateChanged(host_->GetPanelState(nullptr), |
| {.attached_browser = nullptr, .glic_widget = nullptr}); |
| } |
| ~ActiveStateCalculator() override { host_->RemovePanelStateObserver(this); } |
| |
| bool IsActive() const { return is_active_; } |
| void AddObserver(Observer* observer) { observers_.AddObserver(observer); } |
| void RemoveObserver(Observer* observer) { |
| observers_.RemoveObserver(observer); |
| } |
| |
| // GlicWindowController::StateObserver implementation. |
| void PanelStateChanged(const glic::mojom::PanelState& panel_state, |
| const PanelStateContext& context) override { |
| panel_state_kind_ = panel_state.kind; |
| SetAttachedBrowser(context.attached_browser); |
| PostRecalcAndNotify(); |
| } |
| |
| private: |
| // Calls RecalculateAndNotify after a short delay. This is required to prevent |
| // transient states from being emitted. |
| void PostRecalcAndNotify() { |
| calc_timer_.Start( |
| FROM_HERE, base::Milliseconds(10), |
| base::BindRepeating(&ActiveStateCalculator::RecalculateAndNotify, |
| base::Unretained(this))); |
| } |
| |
| void RecalculateAndNotify() { |
| if (Calculate() != is_active_) { |
| is_active_ = !is_active_; |
| observers_.Notify(&Observer::ActiveStateChanged, is_active_); |
| } |
| } |
| |
| void AttachedBrowserActiveChanged(BrowserWindowInterface* browser) { |
| PostRecalcAndNotify(); |
| } |
| |
| void AttachedBrowserDidClose(BrowserWindowInterface* browser) { |
| SetAttachedBrowser(nullptr); |
| PostRecalcAndNotify(); |
| } |
| |
| bool SetAttachedBrowser(BrowserWindowInterface* attached_browser) { |
| if (attached_browser_ == attached_browser) { |
| return false; |
| } |
| attached_browser_subscriptions_.clear(); |
| attached_browser_ = attached_browser; |
| |
| if (attached_browser_ && !attached_browser_->GetBrowserForMigrationOnly() |
| ->is_delete_scheduled()) { |
| attached_browser_subscriptions_.push_back( |
| attached_browser_->RegisterDidBecomeActive(base::BindRepeating( |
| &ActiveStateCalculator::AttachedBrowserActiveChanged, |
| base::Unretained(this)))); |
| attached_browser_subscriptions_.push_back( |
| attached_browser_->RegisterDidBecomeInactive(base::BindRepeating( |
| &ActiveStateCalculator::AttachedBrowserActiveChanged, |
| base::Unretained(this)))); |
| attached_browser_subscriptions_.push_back( |
| attached_browser_->RegisterBrowserDidClose(base::BindRepeating( |
| &ActiveStateCalculator::AttachedBrowserDidClose, |
| base::Unretained(this)))); |
| } |
| return true; |
| } |
| |
| bool Calculate() { |
| if (panel_state_kind_ == glic::mojom::PanelStateKind::kHidden) { |
| return false; |
| } |
| // TODO(b:444463509): Implement better calculation. |
| if (GlicEnabling::IsMultiInstanceEnabledByFlags()) { |
| return true; |
| } |
| if (!attached_browser_) { |
| return true; |
| } |
| if (attached_browser_->GetBrowserForMigrationOnly() |
| ->is_delete_scheduled()) { |
| return false; |
| } |
| |
| return attached_browser_->IsActive(); |
| } |
| |
| base::OneShotTimer calc_timer_; |
| std::vector<base::CallbackListSubscription> attached_browser_subscriptions_; |
| |
| raw_ptr<Host> host_; |
| base::ObserverList<Observer> observers_; |
| glic::mojom::PanelStateKind panel_state_kind_; |
| bool is_active_ = false; |
| raw_ptr<BrowserWindowInterface> attached_browser_ = nullptr; |
| }; |
| |
| class BrowserIsOpenCalculator : public BrowserListObserver { |
| public: |
| class Observer : public base::CheckedObserver { |
| public: |
| virtual void BrowserIsOpenChanged(bool browser_is_open) = 0; |
| }; |
| |
| explicit BrowserIsOpenCalculator(Profile* profile, Observer* observer) |
| : profile_(profile) { |
| BrowserList::AddObserver(this); |
| ForEachCurrentBrowserWindowInterfaceOrderedByActivation( |
| [this](BrowserWindowInterface* browser_window_interface) { |
| OnBrowserAdded( |
| browser_window_interface->GetBrowserForMigrationOnly()); |
| return true; |
| }); |
| // Don't notify observer during construction. |
| observer_ = observer; |
| } |
| ~BrowserIsOpenCalculator() override { BrowserList::RemoveObserver(this); } |
| |
| void OnBrowserAdded(Browser* browser) override { |
| if (browser->profile() == profile_) { |
| UpdateBrowserCount(1); |
| } |
| } |
| void OnBrowserRemoved(Browser* browser) override { |
| if (browser->profile() == profile_) { |
| UpdateBrowserCount(-1); |
| } |
| } |
| |
| bool IsOpen() const { return open_browser_count_ > 0; } |
| |
| private: |
| void UpdateBrowserCount(int delta) { |
| bool was_open = IsOpen(); |
| open_browser_count_ += delta; |
| bool is_open = IsOpen(); |
| if (was_open != is_open && observer_) { |
| observer_->BrowserIsOpenChanged(is_open); |
| } |
| } |
| // Profile outlives this class. The glic web contents is torn down along with |
| // GlicKeyedService, which is tied to the profile. |
| raw_ptr<Profile> profile_; |
| raw_ptr<Observer> observer_ = nullptr; |
| int open_browser_count_ = 0; |
| }; |
| |
| // Does time-based debouncing and cache-based deduping of FocusedTabData |
| // updates. |
| // TODO(b/424242331): Debouncing & deduping should happen closer to where |
| // focused tab updates are generated. |
| // TODO(b/424242331): This logic should be moved to a separate file and be made |
| // more generic and configurable. |
| class DebouncerDeduper { |
| public: |
| using DataCallback = void(glic::mojom::FocusedTabDataPtr); |
| |
| DebouncerDeduper(base::TimeDelta debounce_delay, |
| int max_debounces, |
| base::RepeatingCallback<DataCallback> callback) |
| : max_debounces_(max_debounces), |
| update_callback_(callback), |
| debounce_timer_(FROM_HERE, |
| debounce_delay, |
| base::BindRepeating(&DebouncerDeduper::MaybeSendUpdate, |
| base::Unretained(this))), |
| remaining_debounces_(max_debounces_) {} |
| ~DebouncerDeduper() = default; |
| |
| void HandleUpdate(const glic::mojom::FocusedTabDataPtr data) { |
| next_data_candidate_ = data.Clone(); |
| if (remaining_debounces_ > 0) { |
| remaining_debounces_--; |
| debounce_timer_.Reset(); |
| } |
| } |
| |
| private: |
| void MaybeSendUpdate() { |
| if (next_data_candidate_ != last_sent_data_) { |
| last_sent_data_ = next_data_candidate_->Clone(); |
| update_callback_.Run(std::move(next_data_candidate_)); |
| } |
| next_data_candidate_ = nullptr; |
| remaining_debounces_ = max_debounces_; |
| } |
| |
| const int max_debounces_; |
| base::RepeatingCallback<DataCallback> update_callback_; |
| base::RetainingOneShotTimer debounce_timer_; |
| int remaining_debounces_; |
| glic::mojom::FocusedTabDataPtr last_sent_data_; |
| glic::mojom::FocusedTabDataPtr next_data_candidate_; |
| }; |
| |
| const char kGlicActorJournalLog[] = "glic-actor-journal"; |
| |
| // Class that encapsulates interacting with the actor journal. |
| class JournalHandler { |
| public: |
| explicit JournalHandler(Profile* profile) |
| : actor_keyed_service_(actor::ActorKeyedService::Get(profile)) { |
| base::FilePath path = |
| base::CommandLine::ForCurrentProcess()->GetSwitchValuePath( |
| kGlicActorJournalLog); |
| if (!path.empty()) { |
| path = base::GetUniquePathWithSuffixFormat(path, "_%d"); |
| LOG(ERROR) << "Glic Journal: " << path; |
| file_journal_serializer_ = |
| std::make_unique<actor::AggregatedJournalFileSerializer>( |
| actor_keyed_service_->GetJournal()); |
| file_journal_serializer_->Init( |
| path, base::BindOnce(&JournalHandler::FileInitDone, |
| base::Unretained(this))); |
| } |
| } |
| |
| void LogBeginAsyncEvent(uint64_t event_async_id, |
| int32_t task_id, |
| const std::string& event, |
| const std::string& details) { |
| // If there is a matching ID make sure it terminates before the new event is |
| // created. |
| auto it = active_journal_events_.find(event_async_id); |
| if (it != active_journal_events_.end()) { |
| active_journal_events_.erase(it); |
| } |
| |
| auto actor_task_id = actor::TaskId(task_id); |
| active_journal_events_[event_async_id] = |
| actor_keyed_service_->GetJournal().CreatePendingAsyncEntry( |
| /*url=*/GURL::EmptyGURL(), actor_task_id, |
| actor::MakeFrontEndTrackUUID(actor_task_id), event, |
| actor::JournalDetailsBuilder() |
| .Add("begin_details", details) |
| .Build()); |
| } |
| |
| void LogEndAsyncEvent(mojom::WebClientModel model, |
| uint64_t event_async_id, |
| const std::string& details) { |
| auto it = active_journal_events_.find(event_async_id); |
| if (it != active_journal_events_.end()) { |
| it->second->EndEntry( |
| actor::JournalDetailsBuilder().Add("end_details", details).Build()); |
| |
| if (model == mojom::WebClientModel::kActor) { |
| // Log a histogram for each async event. |
| std::string histogram_name; |
| // The event name may have whitespaces and that won't work as a |
| // histogram name. |
| base::RemoveChars(it->second->event_name(), " ", &histogram_name); |
| |
| base::UmaHistogramLongTimes100( |
| "Glic.Actor.JournalEvent." + histogram_name, |
| base::TimeTicks::Now() - it->second->begin_time()); |
| } |
| |
| active_journal_events_.erase(it); |
| } |
| } |
| |
| void LogInstantEvent(int32_t task_id, |
| const std::string& event, |
| const std::string& details) { |
| auto actor_task_id = actor::TaskId(task_id); |
| actor_keyed_service_->GetJournal().Log( |
| /*url=*/GURL::EmptyGURL(), actor_task_id, |
| actor::MakeFrontEndTrackUUID(actor_task_id), event, |
| actor::JournalDetailsBuilder().Add("details", details).Build()); |
| } |
| |
| void Clear() { |
| if (journal_serializer_) { |
| journal_serializer_->Clear(); |
| } |
| } |
| |
| void Snapshot( |
| bool clear_journal, |
| glic::mojom::WebClientHandler::JournalSnapshotCallback callback) { |
| if (!journal_serializer_) { |
| std::move(callback).Run(glic::mojom::Journal::New()); |
| return; |
| } |
| std::move(callback).Run( |
| glic::mojom::Journal::New(journal_serializer_->Snapshot())); |
| if (clear_journal) { |
| journal_serializer_->Clear(); |
| } |
| } |
| |
| std::vector<uint8_t> GetSnapshot(bool clear_journal) { |
| std::vector<uint8_t> result_buffer; |
| if (journal_serializer_) { |
| result_buffer = journal_serializer_->Snapshot(); |
| if (clear_journal) { |
| journal_serializer_->Clear(); |
| } |
| } |
| return result_buffer; |
| } |
| |
| void Start(uint64_t max_bytes, bool capture_screenshots) { |
| journal_serializer_ = |
| std::make_unique<actor::AggregatedJournalInMemorySerializer>( |
| actor_keyed_service_->GetJournal(), max_bytes); |
| journal_serializer_->Init(); |
| } |
| |
| void Stop() { journal_serializer_.reset(); } |
| |
| void RecordFeedback(bool positive, const std::string& reason) { |
| if (base::FeatureList::IsEnabled(features::kGlicRecordActorJournal) && |
| !positive) { |
| SendResponseFeedback(reason); |
| } |
| } |
| |
| private: |
| void SendResponseFeedback(const std::string& reason) { |
| base::WeakPtr<feedback::FeedbackUploader> uploader = |
| feedback::FeedbackUploaderFactoryChrome::GetForBrowserContext( |
| actor_keyed_service_->GetProfile()) |
| ->AsWeakPtr(); |
| scoped_refptr<::feedback::FeedbackData> feedback_data = |
| base::MakeRefCounted<feedback::FeedbackData>( |
| std::move(uploader), ContentTracingManager::Get()); |
| auto journal = GetSnapshot(false); |
| |
| // TODO(b/430054430): Fetch and include system data to the feedback. |
| feedback_data->set_description( |
| reason + " - " + base::Uuid::GenerateRandomV4().AsLowercaseString()); |
| feedback_data->set_product_id( |
| features::kGlicRecordActorJournalFeedbackProductId.Get()); |
| feedback_data->set_category_tag( |
| features::kGlicRecordActorJournalFeedbackCategoryTag.Get()); |
| feedback_data->set_is_offensive_or_unsafe(false); |
| feedback_data->AddFile("actor-journal", journal); |
| |
| signin::IdentityManager* identity_manager = |
| IdentityManagerFactory::GetForProfile( |
| actor_keyed_service_->GetProfile()); |
| if (identity_manager && |
| identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSignin)) { |
| feedback_data->set_user_email( |
| identity_manager->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin) |
| .email); |
| } |
| |
| system_logs::BuildChromeSystemLogsFetcher( |
| actor_keyed_service_->GetProfile(), /*scrub_data=*/false) |
| ->Fetch(base::BindOnce( |
| [](scoped_refptr<::feedback::FeedbackData> feedback_data, |
| std::unique_ptr<system_logs::SystemLogsResponse> |
| system_logs_response) { |
| if (system_logs_response) { |
| feedback_data->AddLogs(*system_logs_response); |
| } |
| feedback_data->CompressSystemInfo(); |
| feedback_data->OnFeedbackPageDataComplete(); |
| }, |
| std::move(feedback_data))); |
| } |
| |
| void FileInitDone(bool success) { |
| if (!success) { |
| file_journal_serializer_.reset(); |
| } |
| } |
| |
| absl::flat_hash_map< |
| uint64_t, |
| std::unique_ptr<actor::AggregatedJournal::PendingAsyncEntry>> |
| active_journal_events_; |
| std::unique_ptr<actor::AggregatedJournalInMemorySerializer> |
| journal_serializer_; |
| std::unique_ptr<actor::AggregatedJournalFileSerializer> |
| file_journal_serializer_; |
| raw_ptr<actor::ActorKeyedService> actor_keyed_service_; |
| }; |
| |
| } // namespace |
| |
| // WARNING: One instance of this class is created per WebUI navigated to |
| // chrome://glic. The design and implementation of this class, which plumbs |
| // events through GlicKeyedService to other components, relies on the assumption |
| // that there is exactly 1 WebUI instance. If this assumption is ever violated |
| // then many classes will break. |
| class GlicWebClientHandler : public glic::mojom::WebClientHandler, |
| public GlicWindowController::StateObserver, |
| public GlicWebClientAccess, |
| public BrowserAttachObserver, |
| public ActiveStateCalculator::Observer, |
| public BrowserIsOpenCalculator::Observer { |
| public: |
| explicit GlicWebClientHandler( |
| GlicPageHandler* page_handler, |
| content::BrowserContext* browser_context, |
| mojo::PendingReceiver<glic::mojom::WebClientHandler> receiver) |
| : profile_(Profile::FromBrowserContext(browser_context)), |
| page_handler_(page_handler), |
| glic_service_( |
| GlicKeyedServiceFactory::GetGlicKeyedService(browser_context)), |
| window_controller_(&glic_service_->window_controller()), |
| pref_service_(profile_->GetPrefs()), |
| active_state_calculator_(&page_handler_->host()), |
| browser_is_open_calculator_(profile_, this), |
| receiver_(this, std::move(receiver)), |
| annotation_manager_( |
| std::make_unique<GlicAnnotationManager>(glic_service_)), |
| journal_handler_(profile_) { |
| active_state_calculator_.AddObserver(this); |
| } |
| |
| ~GlicWebClientHandler() override { |
| active_state_calculator_.RemoveObserver(this); |
| if (web_client_) { |
| Uninstall(); |
| } |
| } |
| |
| Host& host() { return page_handler_->host(); } |
| GlicSharingManager& sharing_manager() { return host().sharing_manager(); } |
| |
| // glic::mojom::WebClientHandler implementation. |
| void SwitchConversation(glic::mojom::ConversationInfoPtr info, |
| SwitchConversationCallback callback) override { |
| if (info && info->conversation_id.empty()) { |
| receiver_.ReportBadMessage("conversation_id cannot be empty."); |
| } |
| page_handler_->host().SwitchConversation(std::move(info), |
| std::move(callback)); |
| } |
| |
| void RegisterConversation(glic::mojom::ConversationInfoPtr info, |
| RegisterConversationCallback callback) override { |
| if (info->conversation_id.empty()) { |
| receiver_.ReportBadMessage("conversation_id cannot be empty."); |
| } |
| page_handler_->host().RegisterConversation(std::move(info), |
| std::move(callback)); |
| } |
| |
| void WebClientCreated( |
| ::mojo::PendingRemote<glic::mojom::WebClient> web_client, |
| WebClientCreatedCallback callback) override { |
| web_client_.Bind(std::move(web_client)); |
| web_client_.set_disconnect_handler(base::BindOnce( |
| &GlicWebClientHandler::WebClientDisconnected, base::Unretained(this))); |
| |
| page_metadata_manager_ = |
| std::make_unique<PageMetadataManager>(web_client_.get()); |
| |
| // Listen for changes to prefs. |
| pref_change_registrar_.Init(pref_service_); |
| pref_change_registrar_.Add( |
| prefs::kGlicMicrophoneEnabled, |
| base::BindRepeating(&GlicWebClientHandler::OnPrefChanged, |
| base::Unretained(this))); |
| pref_change_registrar_.Add( |
| prefs::kGlicGeolocationEnabled, |
| base::BindRepeating(&GlicWebClientHandler::OnPrefChanged, |
| base::Unretained(this))); |
| pref_change_registrar_.Add( |
| prefs::kGlicTabContextEnabled, |
| base::BindRepeating(&GlicWebClientHandler::OnPrefChanged, |
| base::Unretained(this))); |
| pref_change_registrar_.Add( |
| prefs::kGlicClosedCaptioningEnabled, |
| base::BindRepeating(&GlicWebClientHandler::OnPrefChanged, |
| base::Unretained(this))); |
| pref_change_registrar_.Add( |
| prefs::kGlicDefaultTabContextEnabled, |
| base::BindRepeating(&GlicWebClientHandler::OnPrefChanged, |
| base::Unretained(this))); |
| pref_change_registrar_.Add( |
| prefs::kGlicUserEnabledActuationOnWeb, |
| base::BindRepeating(&GlicWebClientHandler::OnPrefChanged, |
| base::Unretained(this))); |
| host().AddPanelStateObserver(this); |
| |
| if (base::FeatureList::IsEnabled( |
| features::kGlicTabFocusDataDedupDebounce)) { |
| const base::TimeDelta debounce_delay = |
| base::Milliseconds(features::kGlicTabFocusDataDebounceDelayMs.Get()); |
| const int max_debounces = features::kGlicTabFocusDataMaxDebounces.Get(); |
| debouncer_deduper_ = std::make_unique<DebouncerDeduper>( |
| debounce_delay, max_debounces, |
| base::BindRepeating( |
| &GlicWebClientHandler::NotifyWebClientFocusedTabChanged, |
| base::Unretained(this))); |
| } |
| |
| focus_changed_subscription_ = |
| sharing_manager().AddFocusedTabChangedCallback( |
| base::BindRepeating(&GlicWebClientHandler::OnFocusedTabChanged, |
| base::Unretained(this))); |
| |
| pinned_tabs_changed_subscription_ = |
| sharing_manager().AddPinnedTabsChangedCallback(base::BindRepeating( |
| &GlicWebClientHandler::OnPinningChanged, base::Unretained(this))); |
| |
| pinned_tab_data_changed_subscription_ = |
| sharing_manager().AddPinnedTabDataChangedCallback( |
| base::BindRepeating(&GlicWebClientHandler::OnPinnedTabDataChanged, |
| base::Unretained(this))); |
| |
| if (base::FeatureList::IsEnabled(features::kGlicGetTabByIdApi)) { |
| tab_data_changed_subscription_ = |
| glic_service_->AddTabDataChangedCallback(base::BindRepeating( |
| &GlicWebClientHandler::OnTabDataChanged, base::Unretained(this))); |
| } |
| |
| focus_data_changed_subscription_ = |
| sharing_manager().AddFocusedTabDataChangedCallback( |
| base::BindRepeating(&GlicWebClientHandler::OnFocusedTabDataChanged, |
| base::Unretained(this))); |
| |
| if (!base::FeatureList::IsEnabled(features::kGlicMultiInstance)) { |
| focused_browser_changed_subscription_ = |
| sharing_manager().AddFocusedBrowserChangedCallback( |
| base::BindRepeating( |
| &GlicWebClientHandler::OnFocusedBrowserChanged, |
| base::Unretained(this))); |
| } |
| |
| browser_attach_observation_ = ObserveBrowserForAttachment(profile_, this); |
| |
| system_permission_settings_observation_ = |
| system_permission_settings::Observe(base::BindRepeating( |
| &GlicWebClientHandler::OnOsPermissionSettingChanged, |
| base::Unretained(this))); |
| |
| if (base::FeatureList::IsEnabled(features::kGlicActor)) { |
| if (auto* actor_service = actor::ActorKeyedService::Get(profile_)) { |
| actor_task_state_changed_subscription_ = |
| actor_service->AddTaskStateChangedCallback(base::BindRepeating( |
| &GlicWebClientHandler::NotifyActorTaskStateChanged, |
| base::Unretained(this))); |
| // CallbackListSubscription prevents these callbacks from being invoked |
| // when this object is destructed. |
| // TODO(crbug.com/445224605): Right now this code assumes that |
| // ActorKeyedService only owns a single Execution engine instance. |
| request_to_show_credential_selection_dialog_subscription_ = |
| actor_service |
| ->AddRequestToShowCredentialSelectionDialogSubscriberCallback( |
| base::BindRepeating( |
| &GlicWebClientHandler:: |
| RequestToShowCredentialSelectionDialog, |
| base::Unretained(this))); |
| request_to_show_autofill_suggestions_dialog_subscription_ = |
| actor_service |
| ->AddRequestToShowAutofillSuggestionsDialogSubscriberCallback( |
| base::BindRepeating( |
| &GlicWebClientHandler:: |
| RequestToShowAutofillSuggestionsDialog, |
| base::Unretained(this))); |
| request_to_show_user_confirmation_dialog_subscription_ = |
| actor_service |
| ->AddRequestToShowUserConfirmationDialogSubscriberCallback( |
| base::BindRepeating(&GlicWebClientHandler:: |
| RequestToShowUserConfirmationDialog, |
| base::Unretained(this))); |
| request_to_confirm_navigation_subscription_ = |
| actor_service->AddRequestToConfirmNavigationSubscriberCallback( |
| base::BindRepeating( |
| &GlicWebClientHandler::RequestToConfirmNavigation, |
| base::Unretained(this))); |
| act_on_web_capability_changed_subscription_ = |
| actor_service->AddActOnWebCapabilityChangedCallback( |
| base::BindRepeating( |
| &GlicWebClientHandler::NotifyActOnWebCapabilityChanged, |
| base::Unretained(this))); |
| } |
| } |
| |
| auto state = glic::mojom::WebClientInitialState::New(); |
| state->chrome_version = version_info::GetVersion(); |
| state->microphone_permission_enabled = |
| pref_service_->GetBoolean(prefs::kGlicMicrophoneEnabled); |
| state->location_permission_enabled = |
| pref_service_->GetBoolean(prefs::kGlicGeolocationEnabled); |
| state->tab_context_permission_enabled = |
| pref_service_->GetBoolean(prefs::kGlicTabContextEnabled); |
| state->os_location_permission_enabled = |
| system_permission_settings::IsAllowed(ContentSettingsType::GEOLOCATION); |
| |
| state->panel_state = host().GetPanelState(this).Clone(); |
| |
| state->focused_tab_data = |
| CreateFocusedTabData(sharing_manager().GetFocusedTabData()); |
| state->can_attach = browser_attach_observation_->CanAttachToBrowser(); |
| state->panel_is_active = active_state_calculator_.IsActive(); |
| |
| if (base::FeatureList::IsEnabled(glic::mojom::features::kGlicMultiTab)) { |
| OnPinningChanged(sharing_manager().GetPinnedTabs()); |
| } |
| |
| state->browser_is_open = browser_is_open_calculator_.IsOpen(); |
| state->instance_is_active = host().instance_delegate().IsActive(); |
| |
| state->always_detached_mode = GlicWindowController::AlwaysDetached(); |
| |
| state->enable_act_in_focused_tab = |
| base::FeatureList::IsEnabled(features::kGlicActor); |
| state->enable_scroll_to = |
| base::FeatureList::IsEnabled(features::kGlicScrollTo); |
| state->enable_zero_state_suggestions = |
| contextual_cueing::IsZeroStateSuggestionsEnabled(); |
| |
| local_state_pref_change_registrar_.Init(g_browser_process->local_state()); |
| local_state_pref_change_registrar_.Add( |
| prefs::kGlicLauncherHotkey, |
| base::BindRepeating(&GlicWebClientHandler::OnLocalStatePrefChanged, |
| base::Unretained(this))); |
| state->hotkey = GetHotkeyString(); |
| state->enable_default_tab_context_setting_feature = |
| base::FeatureList::IsEnabled(features::kGlicDefaultTabContextSetting); |
| state->default_tab_context_setting_enabled = |
| pref_service_->GetBoolean(prefs::kGlicDefaultTabContextEnabled); |
| state->enable_closed_captioning_feature = |
| base::FeatureList::IsEnabled(features::kGlicClosedCaptioning); |
| state->closed_captioning_setting_enabled = |
| pref_service_->GetBoolean(prefs::kGlicClosedCaptioningEnabled); |
| state->enable_maybe_refresh_user_status = |
| base::FeatureList::IsEnabled(features::kGlicUserStatusCheck) && |
| features::kGlicUserStatusRefreshApi.Get(); |
| state->enable_multi_tab = |
| base::FeatureList::IsEnabled(glic::mojom::features::kGlicMultiTab); |
| state->enable_get_context_actor = base::FeatureList::IsEnabled( |
| glic::mojom::features::kGlicActorTabContext); |
| state->enable_web_actuation_setting_feature = |
| base::FeatureList::IsEnabled(features::kGlicWebActuationSetting); |
| state->actuation_on_web_setting_enabled = |
| pref_service_->GetBoolean(prefs::kGlicUserEnabledActuationOnWeb); |
| |
| #if BUILDFLAG(ENABLE_PDF) |
| if (features::kGlicScrollToPDF.Get()) { |
| state->host_capabilities.push_back(mojom::HostCapability::kScrollToPdf); |
| } |
| #endif |
| if (base::FeatureList::IsEnabled( |
| features::kGlicPanelResetSizeAndLocationOnOpen)) { |
| state->host_capabilities.push_back( |
| mojom::HostCapability::kResetSizeAndLocationOnOpen); |
| } |
| if (GlicEnabling::IsMultiInstanceEnabledByFlags()) { |
| state->host_capabilities.push_back(mojom::HostCapability::kMultiInstance); |
| } |
| state->enable_get_page_metadata = |
| base::FeatureList::IsEnabled(blink::features::kFrameMetadataObserver); |
| state->enable_api_activation_gating = |
| base::FeatureList::IsEnabled(features::kGlicApiActivationGating); |
| if (base::FeatureList::IsEnabled( |
| glic::mojom::features::kGlicAppendModelQualityClientId)) { |
| state->host_capabilities.push_back( |
| mojom::HostCapability::kGetModelQualityClientId); |
| } |
| state->enable_capture_region = |
| base::FeatureList::IsEnabled(features::kGlicCaptureRegion); |
| state->can_act_on_web = false; |
| if (base::FeatureList::IsEnabled(features::kGlicActor)) { |
| if (auto* actor_service = actor::ActorKeyedService::Get(profile_)) { |
| state->can_act_on_web = |
| actor_service->GetPolicyChecker().can_act_on_web(); |
| } |
| } |
| state->enable_activate_tab = |
| base::FeatureList::IsEnabled(features::kGlicActivateTabApi); |
| state->enable_get_tab_by_id = |
| base::FeatureList::IsEnabled(features::kGlicGetTabByIdApi); |
| |
| std::move(callback).Run(std::move(state)); |
| } |
| |
| void WebClientInitializeFailed() override { |
| host().WebClientInitializeFailed(this); |
| } |
| |
| void WebClientInitialized() override { |
| host().SetWebClient(this); |
| // If chrome://glic is opened in a tab for testing, send a synthetic open |
| // signal. |
| if (page_handler_->webui_contents() != host().webui_contents()) { |
| mojom::PanelOpeningDataPtr panel_opening_data = |
| mojom::PanelOpeningData::New(); |
| panel_opening_data->panel_state = host().GetPanelState(this).Clone(); |
| panel_opening_data->invocation_source = |
| mojom::InvocationSource::kUnsupported; |
| base::UmaHistogramBoolean("Glic.Host.OpenedInRegularTab", true); |
| web_client_->NotifyPanelWillOpen(std::move(panel_opening_data), |
| base::DoNothing()); |
| } |
| } |
| |
| void GetZeroStateSuggestionsAndSubscribe( |
| bool has_active_subscription, |
| mojom::ZeroStateSuggestionsOptionsPtr options, |
| GetZeroStateSuggestionsAndSubscribeCallback callback) override { |
| host().instance_delegate().GetZeroStateSuggestionsAndSubscribe( |
| has_active_subscription, *options, std::move(callback)); |
| } |
| |
| void CreateTab(const ::GURL& url, |
| bool open_in_background, |
| const std::optional<int32_t> window_id, |
| CreateTabCallback callback) override { |
| if (base::FeatureList::IsEnabled(media::kMediaLinkHelpers)) { |
| if (auto* tab = sharing_manager().GetFocusedTabData().focus()) { |
| const bool replaced = |
| GlicMediaLinkHelper(tab->GetContents()).MaybeReplaceNavigation(url); |
| base::UmaHistogramBoolean("Glic.MaybeReplaceNavigation.Result", |
| replaced); |
| if (replaced) { |
| std::move(callback).Run(nullptr); |
| return; |
| } |
| } |
| } |
| host().instance_delegate().CreateTab(url, open_in_background, window_id, |
| std::move(callback)); |
| } |
| |
| void OpenGlicSettingsPage(mojom::OpenSettingsOptionsPtr options) override { |
| switch (options->highlightField) { |
| case mojom::SettingsPageField::kOsHotkey: |
| ::glic::OpenGlicKeyboardShortcutSetting(profile_); |
| base::RecordAction( |
| base::UserMetricsAction("GlicSessionSettingsOpened.OsHotkey")); |
| break; |
| case mojom::SettingsPageField::kOsEntrypointToggle: |
| ::glic::OpenGlicOsToggleSetting(profile_); |
| base::RecordAction(base::UserMetricsAction( |
| "GlicSessionSettingsOpened.OsEntrypointToggle")); |
| break; |
| case mojom::SettingsPageField::kNone: // Default value. |
| ::glic::OpenGlicSettingsPage(profile_); |
| base::RecordAction( |
| base::UserMetricsAction("GlicSessionSettingsOpened.Default")); |
| break; |
| } |
| } |
| |
| void ClosePanel() override { host().ClosePanel(page_handler_); } |
| |
| void ClosePanelAndShutdown() override { |
| if (GlicEnabling::IsMultiInstanceEnabledByFlags()) { |
| ClosePanel(); |
| } else { |
| // This call will tear down the web client after closing the window. |
| glic_service_->CloseAndShutdown(); |
| } |
| } |
| |
| void AttachPanel() override { |
| if (GlicWindowController::AlwaysDetached()) { |
| receiver_.ReportBadMessage( |
| "AttachPanel cannot be called when always detached mode is enabled."); |
| return; |
| } |
| host().AttachPanel(page_handler_); |
| } |
| |
| void DetachPanel() override { |
| if (GlicWindowController::AlwaysDetached()) { |
| receiver_.ReportBadMessage( |
| "DetachPanel cannot be called when always detached mode is enabled."); |
| return; |
| } |
| host().DetachPanel(page_handler_); |
| } |
| |
| void ShowProfilePicker() override { |
| glic::GlicProfileManager::GetInstance()->ShowProfilePicker(); |
| } |
| |
| void OnModeChange(glic::mojom::WebClientMode new_mode) override { |
| glic_service_->metrics()->SetWebClientMode(new_mode); |
| host().OnInteractionModeChange(page_handler_, new_mode); |
| } |
| |
| void ResizeWidget(const gfx::Size& size, |
| base::TimeDelta duration, |
| ResizeWidgetCallback callback) override { |
| host().ResizePanel(page_handler_, size, duration, std::move(callback)); |
| } |
| |
| void GetModelQualityClientId( |
| GetModelQualityClientIdCallback callback) override { |
| auto* local_state = g_browser_process->local_state(); |
| std::string client_id = |
| optimization_guide::GetOrCreateGlicModelQualityClientId(local_state); |
| std::move(callback).Run(std::move(client_id)); |
| } |
| |
| void GetContextFromFocusedTab( |
| glic::mojom::GetTabContextOptionsPtr options, |
| GetContextFromFocusedTabCallback callback) override { |
| FocusedTabData ftd = sharing_manager().GetFocusedTabData(); |
| if (ftd.unfocused_tab()) { |
| CHECK(!ftd.focus()); |
| // Fail early if the active tab is un-focusable. |
| glic_service_->metrics()->LogGetContextFromFocusedTabError( |
| GlicGetContextFromTabError::kPermissionDenied); |
| std::move(callback).Run( |
| mojom::GetContextResult::NewErrorReason("permission denied")); |
| return; |
| } |
| |
| tabs::TabInterface* tab = ftd.focus(); |
| if (auto* instance_metrics = host().instance_metrics()) { |
| instance_metrics->DidRequestContextFromFocusedTab(); |
| } |
| auto tab_handle = tab ? tab->GetHandle() : tabs::TabHandle::Null(); |
| sharing_manager().GetContextFromTab( |
| tab_handle, *options, |
| base::BindOnce( |
| &LogErrorAndUnwrapResult, |
| base::BindOnce(&GlicMetrics::LogGetContextFromFocusedTabError, |
| base::Unretained(glic_service_->metrics()))) |
| .Then(std::move(callback))); |
| } |
| |
| void GetContextFromTab(int32_t tab_id, |
| glic::mojom::GetTabContextOptionsPtr options, |
| GetContextFromTabCallback callback) override { |
| // Extra activation gating is done in this function. |
| sharing_manager().GetContextFromTab( |
| tabs::TabHandle(tab_id), *options, |
| base::BindOnce( |
| &LogErrorAndUnwrapResult, |
| base::BindOnce(&GlicMetrics::LogGetContextFromTabError, |
| base::Unretained(glic_service_->metrics()))) |
| .Then(std::move(callback))); |
| } |
| |
| void GetContextForActorFromTab( |
| int32_t tab_id, |
| glic::mojom::GetTabContextOptionsPtr options, |
| GetContextForActorFromTabCallback callback) override { |
| sharing_manager().GetContextForActorFromTab( |
| tabs::TabHandle(tab_id), *options, |
| base::BindOnce( |
| &LogErrorAndUnwrapResult, |
| base::BindOnce(&GlicMetrics::LogGetContextForActorFromTabError, |
| base::Unretained(glic_service_->metrics()))) |
| .Then(std::move(callback))); |
| } |
| |
| void SetMaximumNumberOfPinnedTabs( |
| uint32_t num_tabs, |
| SetMaximumNumberOfPinnedTabsCallback callback) override { |
| uint32_t effective_max = sharing_manager().SetMaxPinnedTabs(num_tabs); |
| std::move(callback).Run(effective_max); |
| } |
| |
| void PinTabs(const std::vector<int32_t>& tab_ids, |
| PinTabsCallback callback) override { |
| std::vector<tabs::TabHandle> tab_handles; |
| for (auto tab_id : tab_ids) { |
| tab_handles.push_back(tabs::TabHandle(tab_id)); |
| } |
| std::move(callback).Run(sharing_manager().PinTabs(tab_handles)); |
| } |
| |
| void UnpinTabs(const std::vector<int32_t>& tab_ids, |
| UnpinTabsCallback callback) override { |
| std::vector<tabs::TabHandle> tab_handles; |
| for (auto tab_id : tab_ids) { |
| tab_handles.push_back(tabs::TabHandle(tab_id)); |
| } |
| std::move(callback).Run(sharing_manager().UnpinTabs(tab_handles)); |
| } |
| |
| void UnpinAllTabs() override { sharing_manager().UnpinAllTabs(); } |
| |
| void CreateTask(actor::webui::mojom::TaskOptionsPtr options, |
| CreateTaskCallback callback) override { |
| host().instance_delegate().CreateTask(nullptr, std::move(options), |
| std::move(callback)); |
| } |
| |
| void PerformActions(const std::vector<uint8_t>& actions_proto, |
| PerformActionsCallback callback) override { |
| host().instance_delegate().PerformActions(actions_proto, |
| std::move(callback)); |
| } |
| |
| void StopActorTask(int32_t task_id, |
| mojom::ActorTaskStopReason stop_reason) override { |
| if (!base::FeatureList::IsEnabled(features::kGlicActor)) { |
| receiver_.ReportBadMessage( |
| "StopActorTask cannot be called without GlicActor enabled."); |
| return; |
| } |
| host().instance_delegate().StopActorTask(actor::TaskId(task_id), |
| stop_reason); |
| } |
| |
| void PauseActorTask(int32_t task_id, |
| mojom::ActorTaskPauseReason pause_reason, |
| std::optional<int32_t> tab_id) override { |
| if (!base::FeatureList::IsEnabled(features::kGlicActor)) { |
| receiver_.ReportBadMessage( |
| "PauseActorTask cannot be called without GlicActor enabled."); |
| return; |
| } |
| tabs::TabInterface::Handle tab_handle; |
| if (tab_id.has_value()) { |
| tab_handle = tabs::TabInterface::Handle(*tab_id); |
| } |
| host().instance_delegate().PauseActorTask(actor::TaskId(task_id), |
| pause_reason, tab_handle); |
| } |
| |
| void ResumeActorTask(int32_t task_id, |
| glic::mojom::GetTabContextOptionsPtr context_options, |
| ResumeActorTaskCallback callback) override { |
| if (!base::FeatureList::IsEnabled(features::kGlicActor)) { |
| receiver_.ReportBadMessage( |
| "ResumeActorTask cannot be called without GlicActor enabled."); |
| return; |
| } |
| host().instance_delegate().ResumeActorTask( |
| actor::TaskId(task_id), *context_options, std::move(callback)); |
| } |
| |
| void InterruptActorTask(int32_t task_id) override { |
| if (!base::FeatureList::IsEnabled(features::kGlicActor)) { |
| receiver_.ReportBadMessage( |
| "InterruptActorTask cannot be called without GlicActor enabled."); |
| return; |
| } |
| host().instance_delegate().InterruptActorTask(actor::TaskId(task_id)); |
| } |
| |
| void UninterruptActorTask(int32_t task_id) override { |
| if (!base::FeatureList::IsEnabled(features::kGlicActor)) { |
| receiver_.ReportBadMessage( |
| "UninterruptActorTask cannot be called without GlicActor enabled."); |
| return; |
| } |
| host().instance_delegate().UninterruptActorTask(actor::TaskId(task_id)); |
| } |
| |
| void ActivateTab(int32_t tab_id) override { |
| if (!base::FeatureList::IsEnabled(features::kGlicActivateTabApi)) { |
| return; |
| } |
| |
| tabs::TabInterface* tab = tabs::TabHandle(tab_id).Get(); |
| if (!tab) { |
| return; |
| } |
| content::WebContents* contents = tab->GetContents(); |
| if (!contents) { |
| return; |
| } |
| |
| contents->GetDelegate()->ActivateContents(contents); |
| } |
| |
| void CaptureScreenshot(CaptureScreenshotCallback callback) override { |
| host().CaptureScreenshot(std::move(callback)); |
| } |
| |
| void CaptureRegion( |
| mojo::PendingRemote<mojom::CaptureRegionObserver> observer) override { |
| content::WebContents* web_contents = nullptr; |
| const FocusedTabData& focus = sharing_manager().GetFocusedTabData(); |
| // Prioritize the focused tab, but fall back to the unfocused tab if one is |
| // available. This is useful in cases where the active tab is not |
| // "focusable" by Glic (e.g. chrome:// pages). |
| tabs::TabInterface* active_tab = |
| focus.is_focus() ? focus.focus() : focus.unfocused_tab(); |
| |
| if (active_tab) { |
| web_contents = active_tab->GetContents(); |
| } |
| glic_service_->CaptureRegion(web_contents, std::move(observer)); |
| } |
| |
| void SetAudioDucking(bool enabled, |
| SetAudioDuckingCallback callback) override { |
| content::RenderFrameHost* guest_frame = page_handler_->GetGuestMainFrame(); |
| if (!guest_frame) { |
| std::move(callback).Run(false); |
| return; |
| } |
| AudioDucker* audio_ducker = |
| AudioDucker::GetOrCreateForPage(guest_frame->GetPage()); |
| std::move(callback).Run(enabled ? audio_ducker->StartDuckingOtherAudio() |
| : audio_ducker->StopDuckingOtherAudio()); |
| } |
| |
| void SetPanelDraggableAreas( |
| const std::vector<gfx::Rect>& draggable_areas, |
| SetPanelDraggableAreasCallback callback) override { |
| if (!draggable_areas.empty()) { |
| host().SetPanelDraggableAreas(page_handler_, draggable_areas); |
| } else { |
| // Default to the top bar area of the panel. |
| // TODO(cuianthony): Define panel dimensions constants in shared location. |
| host().SetPanelDraggableAreas(page_handler_, {{0, 0, 400, 80}}); |
| } |
| std::move(callback).Run(); |
| } |
| |
| void SetMinimumPanelSize(const gfx::Size& size) override { |
| host().SetMinimumWidgetSize(page_handler_, size); |
| } |
| |
| void SetMicrophonePermissionState( |
| bool enabled, |
| SetMicrophonePermissionStateCallback callback) override { |
| pref_service_->SetBoolean(prefs::kGlicMicrophoneEnabled, enabled); |
| if (enabled) { |
| base::RecordAction( |
| base::UserMetricsAction("GlicMicrophonePermissionEnabled")); |
| } else { |
| base::RecordAction( |
| base::UserMetricsAction("GlicMicrophonePermissionDisabled")); |
| } |
| std::move(callback).Run(); |
| } |
| |
| void SetLocationPermissionState( |
| bool enabled, |
| SetLocationPermissionStateCallback callback) override { |
| pref_service_->SetBoolean(prefs::kGlicGeolocationEnabled, enabled); |
| if (enabled) { |
| base::RecordAction( |
| base::UserMetricsAction("GlicLocationPermissionEnabled")); |
| } else { |
| base::RecordAction( |
| base::UserMetricsAction("GlicLocationPermissionDisabled")); |
| } |
| std::move(callback).Run(); |
| } |
| |
| void SetTabContextPermissionState( |
| bool enabled, |
| SetTabContextPermissionStateCallback callback) override { |
| pref_service_->SetBoolean(prefs::kGlicTabContextEnabled, enabled); |
| if (enabled) { |
| base::RecordAction( |
| base::UserMetricsAction("GlicTabContextPermissionEnabled")); |
| } else { |
| base::RecordAction( |
| base::UserMetricsAction("GlicTabContextPermissionDisabled")); |
| } |
| std::move(callback).Run(); |
| } |
| |
| void SetClosedCaptioningSetting( |
| bool enabled, |
| SetClosedCaptioningSettingCallback callback) override { |
| if (!base::FeatureList::IsEnabled(features::kGlicClosedCaptioning)) { |
| receiver_.ReportBadMessage( |
| "Client should not be able to call SetClosedCaptioningSetting " |
| "without the GlicClosedCaptioning feature enabled."); |
| return; |
| } |
| pref_service_->SetBoolean(prefs::kGlicClosedCaptioningEnabled, enabled); |
| if (enabled) { |
| base::RecordAction( |
| base::UserMetricsAction("GlicClosedCaptioningEnabled")); |
| } else { |
| base::RecordAction( |
| base::UserMetricsAction("GlicClosedCaptioningDisabled")); |
| } |
| std::move(callback).Run(); |
| } |
| |
| void SetActuationOnWebSetting( |
| bool enabled, |
| SetActuationOnWebSettingCallback callback) override { |
| pref_service_->SetBoolean(prefs::kGlicUserEnabledActuationOnWeb, enabled); |
| base::RecordAction( |
| enabled ? base::UserMetricsAction("GlicUserEnabledActuationOnWeb") |
| : base::UserMetricsAction("GlicUserDisabledActuationOnWeb")); |
| std::move(callback).Run(); |
| } |
| |
| void ShouldAllowMediaPermissionRequest( |
| ShouldAllowMediaPermissionRequestCallback callback) override { |
| std::move(callback).Run( |
| pref_service_->GetBoolean(prefs::kGlicMicrophoneEnabled) && |
| host().IsWidgetShowing(this)); |
| } |
| |
| void ShouldAllowGeolocationPermissionRequest( |
| ShouldAllowGeolocationPermissionRequestCallback callback) override { |
| std::move(callback).Run( |
| pref_service_->GetBoolean(prefs::kGlicGeolocationEnabled) && |
| host().IsWidgetShowing(this)); |
| } |
| |
| void SetContextAccessIndicator(bool enabled) override { |
| host().SetContextAccessIndicator(page_handler_, enabled); |
| } |
| |
| void GetUserProfileInfo(GetUserProfileInfoCallback callback) override { |
| ProfileAttributesEntry* entry = |
| g_browser_process->profile_manager() |
| ->GetProfileAttributesStorage() |
| .GetProfileAttributesWithPath(profile_->GetPath()); |
| if (!entry) { |
| std::move(callback).Run(nullptr); |
| return; |
| } |
| |
| auto result = glic::mojom::UserProfileInfo::New(); |
| // TODO(crbug.com/382794680): Determine the correct size. |
| gfx::Image icon = entry->GetAvatarIcon(512); |
| if (!icon.IsEmpty()) { |
| result->avatar_icon = icon.AsBitmap(); |
| } |
| result->display_name = base::UTF16ToUTF8(entry->GetGAIAName()); |
| result->email = base::UTF16ToUTF8(entry->GetUserName()); |
| result->given_name = base::UTF16ToUTF8(entry->GetGAIAGivenName()); |
| result->local_profile_name = |
| base::UTF16ToUTF8(entry->GetLocalProfileName()); |
| policy::ManagementService* management_service = |
| policy::ManagementServiceFactory::GetForProfile(profile_); |
| result->is_managed = |
| management_service && management_service->IsAccountManaged(); |
| std::move(callback).Run(std::move(result)); |
| } |
| |
| void SyncCookies(SyncCookiesCallback callback) override { |
| glic_service_->GetAuthController().ForceSyncCookies(std::move(callback)); |
| } |
| |
| void LogBeginAsyncEvent(uint64_t event_async_id, |
| int32_t task_id, |
| const std::string& event, |
| const std::string& details) override { |
| journal_handler_.LogBeginAsyncEvent(event_async_id, task_id, event, |
| details); |
| } |
| |
| void LogEndAsyncEvent(uint64_t event_async_id, |
| const std::string& details) override { |
| journal_handler_.LogEndAsyncEvent(glic_service_->metrics()->current_model(), |
| event_async_id, details); |
| } |
| |
| void LogInstantEvent(int32_t task_id, |
| const std::string& event, |
| const std::string& details) override { |
| journal_handler_.LogInstantEvent(task_id, event, details); |
| } |
| |
| void JournalClear() override { journal_handler_.Clear(); } |
| |
| void JournalSnapshot(bool clear_journal, |
| JournalSnapshotCallback callback) override { |
| journal_handler_.Snapshot(clear_journal, std::move(callback)); |
| } |
| |
| void JournalStart(uint64_t max_bytes, bool capture_screenshots) override { |
| journal_handler_.Start(max_bytes, capture_screenshots); |
| } |
| |
| void JournalStop() override { journal_handler_.Stop(); } |
| |
| void JournalRecordFeedback(bool positive, |
| const std::string& reason) override { |
| journal_handler_.RecordFeedback(positive, reason); |
| } |
| |
| // TODO(crbug.com/450026474): Remove call to GlicMetrics once |
| // non-profile-scoped metrics are logged entirely from GlicInstanceMetrics. |
| void OnUserInputSubmitted(mojom::WebClientMode mode) override { |
| glic_service_->OnUserInputSubmitted(mode); |
| glic_service_->metrics()->OnUserInputSubmitted(mode); |
| if (auto* instance_metrics = host().instance_metrics()) { |
| instance_metrics->OnUserInputSubmitted(mode); |
| } |
| } |
| |
| void OnContextUploadStarted() override { |
| glic_service_->metrics()->OnContextUploadStarted(); |
| } |
| |
| void OnContextUploadCompleted() override { |
| glic_service_->metrics()->OnContextUploadCompleted(); |
| } |
| |
| // TODO(crbug.com/450026474): Remove call to GlicMetrics once |
| // non-profile-scoped metrics are logged entirely from GlicInstanceMetrics. |
| void OnReaction(mojom::MetricUserInputReactionType reaction_type) override { |
| glic_service_->metrics()->OnReaction(reaction_type); |
| if (auto* instance_metrics = host().instance_metrics()) { |
| instance_metrics->OnReaction(reaction_type); |
| } |
| } |
| |
| // TODO(crbug.com/450026474): Remove call to GlicMetrics once |
| // non-profile-scoped metrics are logged entirely from GlicInstanceMetrics. |
| void OnResponseStarted() override { |
| glic_service_->metrics()->OnResponseStarted(); |
| if (auto* instance_metrics = host().instance_metrics()) { |
| instance_metrics->OnResponseStarted(); |
| } |
| } |
| |
| // TODO(crbug.com/450026474): Remove call to GlicMetrics once |
| // non-profile-scoped metrics are logged entirely from GlicInstanceMetrics. |
| void OnResponseStopped(mojom::OnResponseStoppedDetailsPtr details) override { |
| mojom::ResponseStopCause cause = mojom::ResponseStopCause::kUnknown; |
| if (details) { |
| cause = details->cause; |
| } |
| glic_service_->metrics()->OnResponseStopped(cause); |
| if (auto* instance_metrics = host().instance_metrics()) { |
| instance_metrics->OnResponseStopped(cause); |
| } |
| } |
| |
| void OnSessionTerminated() override { |
| glic_service_->metrics()->OnSessionTerminated(); |
| } |
| |
| // TODO(crbug.com/450026474): Remove call to GlicMetrics once |
| // non-profile-scoped metrics are logged entirely from GlicInstanceMetrics. |
| void OnTurnCompleted(glic::mojom::WebClientModel model, |
| base::TimeDelta duration) override { |
| glic_service_->metrics()->OnTurnCompleted(model, duration); |
| if (auto* instance_metrics = host().instance_metrics()) { |
| instance_metrics->OnTurnCompleted(model, duration); |
| } |
| } |
| |
| void OnModelChanged(glic::mojom::WebClientModel model) override { |
| glic_service_->metrics()->OnModelChanged(model); |
| } |
| |
| void OnRecordUseCounter(uint16_t counter) override { |
| glic_service_->metrics()->OnRecordUseCounter(counter); |
| } |
| |
| void OnResponseRated(bool positive) override { |
| glic_service_->metrics()->OnResponseRated(positive); |
| } |
| |
| void OnClosedCaptionsShown() override { |
| if (!base::FeatureList::IsEnabled(features::kGlicClosedCaptioning)) { |
| receiver_.ReportBadMessage( |
| "Client should not be able to call OnClosedCaptionsShown " |
| "without the GlicClosedCaptioning feature enabled."); |
| return; |
| } |
| |
| glic_service_->metrics()->LogClosedCaptionsShown(); |
| } |
| |
| void ScrollTo(mojom::ScrollToParamsPtr params, |
| ScrollToCallback callback) override { |
| if (!base::FeatureList::IsEnabled(features::kGlicScrollTo)) { |
| receiver_.ReportBadMessage( |
| "Client should not be able to call ScrollTo without the GlicScrollTo " |
| "feature enabled."); |
| return; |
| } |
| annotation_manager_->ScrollTo(std::move(params), std::move(callback), |
| &host()); |
| } |
| |
| void DropScrollToHighlight() override { |
| if (!base::FeatureList::IsEnabled(features::kGlicScrollTo)) { |
| receiver_.ReportBadMessage( |
| "Client should not be able to call DropScrollToHighlight without the " |
| "GlicScrollTo feature enabled."); |
| return; |
| } |
| annotation_manager_->RemoveAnnotation( |
| mojom::ScrollToErrorReason::kDroppedByWebClient); |
| } |
| |
| void SetSyntheticExperimentState(const std::string& trial_name, |
| const std::string& group_name) override { |
| g_browser_process->GetFeatures() |
| ->glic_synthetic_trial_manager() |
| ->SetSyntheticExperimentState(trial_name, group_name); |
| } |
| |
| void OpenOsPermissionSettingsMenu(ContentSettingsType type) override { |
| if (type != ContentSettingsType::MEDIASTREAM_MIC && |
| type != ContentSettingsType::GEOLOCATION) { |
| // This will terminate the render process. |
| receiver_.ReportBadMessage( |
| "OpenOsPermissionSettingsMenu received for unsupported " |
| "OS permission."); |
| return; |
| } |
| system_permission_settings::OpenSystemSettings( |
| page_handler_->webui_contents(), type); |
| } |
| |
| void GetOsMicrophonePermissionStatus( |
| GetOsMicrophonePermissionStatusCallback callback) override { |
| std::move(callback).Run(system_permission_settings::IsAllowed( |
| ContentSettingsType::MEDIASTREAM_MIC)); |
| } |
| |
| void SubscribeToPinCandidates( |
| mojom::GetPinCandidatesOptionsPtr options, |
| mojo::PendingRemote<mojom::PinCandidatesObserver> observer) override { |
| sharing_manager().SubscribeToPinCandidates(std::move(options), |
| std::move(observer)); |
| } |
| |
| void OnViewChanged(mojom::ViewChangedNotificationPtr notification) override { |
| host().OnViewChanged(this, notification->current_view); |
| } |
| |
| // GlicWindowController::StateObserver implementation. |
| void PanelStateChanged( |
| const glic::mojom::PanelState& panel_state, |
| const GlicWindowController::PanelStateContext& context) override { |
| web_client_->NotifyPanelStateChange(panel_state.Clone()); |
| } |
| |
| // GlicWebClientAccess implementation. |
| |
| void PanelWillOpen(glic::mojom::PanelOpeningDataPtr panel_opening_data, |
| PanelWillOpenCallback done) override { |
| base::UmaHistogramBoolean("Glic.Host.OpenedInRegularTab", false); |
| web_client_->NotifyPanelWillOpen( |
| std::move(panel_opening_data), |
| base::BindOnce( |
| [](PanelWillOpenCallback done, GlicMetrics* metrics, |
| glic::mojom::OpenPanelInfoPtr info) { |
| base::UmaHistogramEnumeration("Glic.Api.NotifyPanelWillOpen", |
| info->web_client_mode); |
| metrics->SetWebClientMode(info->web_client_mode); |
| std::move(done).Run(std::move(info)); |
| }, |
| std::move(done), glic_service_->metrics())); |
| } |
| |
| void PanelWasClosed(base::OnceClosure done) override { |
| web_client_->NotifyPanelWasClosed( |
| mojo::WrapCallbackWithDefaultInvokeIfNotRun(std::move(done))); |
| } |
| |
| void PanelStateChanged(const glic::mojom::PanelState& panel_state) override { |
| web_client_->NotifyPanelStateChange(panel_state.Clone()); |
| } |
| |
| void ManualResizeChanged(bool resizing) override { |
| web_client_->NotifyManualResizeChanged(resizing); |
| } |
| |
| void RequestViewChange(mojom::ViewChangeRequestPtr request) override { |
| web_client_->RequestViewChange(std::move(request)); |
| } |
| |
| void NotifyAdditionalContext(mojom::AdditionalContextPtr context) override { |
| web_client_->NotifyAdditionalContext(std::move(context)); |
| } |
| |
| // BrowserAttachmentObserver implementation. |
| void CanAttachToBrowserChanged(bool can_attach) override { |
| web_client_->NotifyPanelCanAttachChange(can_attach); |
| } |
| // ActiveStateCalculator implementation. |
| void ActiveStateChanged(bool is_active) override { |
| if (web_client_) { |
| web_client_->NotifyPanelActiveChange(is_active); |
| } |
| } |
| |
| // BrowserIsOpenCalculator implementation. |
| void BrowserIsOpenChanged(bool is_open) override { |
| if (web_client_) { |
| web_client_->NotifyBrowserIsOpenChanged(is_open); |
| } |
| } |
| |
| void GetZeroStateSuggestionsForFocusedTab( |
| std::optional<bool> is_fre, |
| GetZeroStateSuggestionsForFocusedTabCallback callback) override { |
| if (!contextual_cueing::IsZeroStateSuggestionsEnabled()) { |
| receiver_.ReportBadMessage( |
| "Client should not call " |
| "GetZeroStateSuggestionsForFocusedTab " |
| "without the GlicZeroStateSuggestions feature enabled."); |
| return; |
| } |
| |
| // TODO(crbug.com/424472586): Pass supported tools to service from web |
| // client. |
| host().instance_delegate().FetchZeroStateSuggestions( |
| is_fre.value_or(false), |
| /*supported_tools=*/{}, |
| base::BindOnce( |
| [](GetZeroStateSuggestionsForFocusedTabCallback callback, |
| base::TimeTicks start, |
| glic::mojom::ZeroStateSuggestionsPtr suggestions) { |
| base::UmaHistogramTimes( |
| "Glic.Api.FetchZeroStateSuggestionsLatency", |
| base::TimeTicks::Now() - start); |
| std::move(callback).Run(std::move(suggestions)); |
| }, |
| std::move(callback), base::TimeTicks::Now())); |
| } |
| |
| void MaybeRefreshUserStatus() override { |
| if (!base::FeatureList::IsEnabled(features::kGlicUserStatusCheck) || |
| !features::kGlicUserStatusRefreshApi.Get()) { |
| receiver_.ReportBadMessage( |
| "Client should not call MaybeRefreshUserStatus without the " |
| "GlicUserStatusCheck feature enabled with the refresh API."); |
| return; |
| } |
| glic_service_->enabling().UpdateUserStatusWithThrottling(); |
| } |
| |
| void IsDebuggerAttached(IsDebuggerAttachedCallback callback) override { |
| content::RenderFrameHost* guest_main_frame = |
| page_handler_->GetGuestMainFrame(); |
| if (!guest_main_frame) { |
| std::move(callback).Run(false); |
| return; |
| } |
| content::WebContents* guest_web_contents = |
| content::WebContents::FromRenderFrameHost(guest_main_frame); |
| std::move(callback).Run( |
| content::DevToolsAgentHost::IsDebuggerAttached(guest_web_contents)); |
| } |
| |
| void OnOsPermissionSettingChanged(ContentSettingsType content_type, |
| bool is_blocked) { |
| // Ignore other content types. |
| if (content_type == ContentSettingsType::GEOLOCATION) { |
| web_client_->NotifyOsLocationPermissionStateChanged(!is_blocked); |
| } |
| } |
| |
| void OnPinningChanged( |
| const std::vector<content::WebContents*>& pinned_contents) { |
| std::vector<glic::mojom::TabDataPtr> tab_data; |
| for (content::WebContents* web_contents : pinned_contents) { |
| tab_data.push_back(CreateTabData(web_contents)); |
| } |
| web_client_->NotifyPinnedTabsChanged(std::move(tab_data)); |
| } |
| |
| void SubscribeToPageMetadata( |
| int32_t tab_id, |
| const std::vector<std::string>& names, |
| SubscribeToPageMetadataCallback callback) override { |
| page_metadata_manager_->SubscribeToPageMetadata(tab_id, names, |
| std::move(callback)); |
| } |
| |
| void OnPinnedTabDataChanged(const TabDataChange& change) { |
| if (!change.tab_data) { |
| return; |
| } |
| web_client_->NotifyPinnedTabDataChanged(change.tab_data->Clone()); |
| } |
| |
| void OnTabDataChanged(const TabDataChange& change) { |
| if (!change.tab_data) { |
| return; |
| } |
| web_client_->NotifyTabDataChanged(change.tab_data->Clone()); |
| } |
| |
| void NotifyZeroStateSuggestionsChanged( |
| glic::mojom::ZeroStateSuggestionsV2Ptr suggestions, |
| mojom::ZeroStateSuggestionsOptionsPtr options) { |
| web_client_->NotifyZeroStateSuggestionsChanged(std::move(suggestions), |
| std::move(options)); |
| } |
| |
| void NotifyActOnWebCapabilityChanged(bool can_act_on_web) { |
| web_client_->NotifyActOnWebCapabilityChanged(can_act_on_web); |
| } |
| |
| private: |
| void Uninstall() { |
| page_metadata_manager_.reset(); |
| SetAudioDucking(false, base::DoNothing()); |
| host().UnsetWebClient(this); |
| pref_change_registrar_.Reset(); |
| local_state_pref_change_registrar_.Reset(); |
| host().RemovePanelStateObserver(this); |
| focus_changed_subscription_ = {}; |
| pinned_tabs_changed_subscription_ = {}; |
| pinned_tab_data_changed_subscription_ = {}; |
| request_to_show_credential_selection_dialog_subscription_ = {}; |
| request_to_show_autofill_suggestions_dialog_subscription_ = {}; |
| request_to_show_user_confirmation_dialog_subscription_ = {}; |
| request_to_confirm_navigation_subscription_ = {}; |
| browser_attach_observation_.reset(); |
| if (glic_service_->zero_state_suggestions_manager()) { |
| glic_service_->zero_state_suggestions_manager()->Reset(); |
| } |
| } |
| |
| void WebClientDisconnected() { Uninstall(); } |
| |
| void OnPrefChanged(const std::string& pref_name) { |
| bool is_enabled = pref_service_->GetBoolean(pref_name); |
| if (pref_name == prefs::kGlicMicrophoneEnabled) { |
| web_client_->NotifyMicrophonePermissionStateChanged(is_enabled); |
| } else if (pref_name == prefs::kGlicGeolocationEnabled) { |
| web_client_->NotifyLocationPermissionStateChanged(is_enabled); |
| } else if (pref_name == prefs::kGlicTabContextEnabled) { |
| web_client_->NotifyTabContextPermissionStateChanged(is_enabled); |
| } else if (pref_name == prefs::kGlicClosedCaptioningEnabled) { |
| web_client_->NotifyClosedCaptioningSettingChanged(is_enabled); |
| } else if (pref_name == prefs::kGlicDefaultTabContextEnabled) { |
| web_client_->NotifyDefaultTabContextPermissionStateChanged(is_enabled); |
| } else if (pref_name == prefs::kGlicUserEnabledActuationOnWeb) { |
| web_client_->NotifyActuationOnWebSettingChanged(is_enabled); |
| } else { |
| DCHECK(false) << "Unknown Glic permission pref changed: " << pref_name; |
| } |
| } |
| |
| void OnLocalStatePrefChanged(const std::string& pref_name) { |
| if (pref_name == prefs::kGlicLauncherHotkey) { |
| web_client_->NotifyOsHotkeyStateChanged(GetHotkeyString()); |
| } else { |
| CHECK(false) << "Unknown local state pref changed: " << pref_name; |
| } |
| } |
| |
| void OnFocusedTabChanged(const FocusedTabData& focused_tab_data) { |
| MaybeNotifyFocusedTabChanged(CreateFocusedTabData(focused_tab_data)); |
| } |
| |
| void OnFocusedTabDataChanged(const glic::mojom::TabData* tab_data) { |
| if (!tab_data) { |
| return; |
| } |
| MaybeNotifyFocusedTabChanged( |
| glic::mojom::FocusedTabData::NewFocusedTab(tab_data->Clone())); |
| } |
| |
| void OnFocusedBrowserChanged(BrowserWindowInterface* browser_interface) { |
| const bool is_browser_active = browser_interface != nullptr; |
| NotifyInstanceActivationChanged(is_browser_active); |
| } |
| |
| void NotifyInstanceActivationChanged(bool is_active) override { |
| web_client_->NotifyInstanceActivationChanged(is_active); |
| } |
| |
| bool ShouldDoApiActivationGating() const { |
| return base::FeatureList::IsEnabled(features::kGlicApiActivationGating) && |
| !active_state_calculator_.IsActive(); |
| } |
| |
| void MaybeNotifyFocusedTabChanged( |
| glic::mojom::FocusedTabDataPtr focused_tab_data) { |
| if (debouncer_deduper_) { |
| debouncer_deduper_->HandleUpdate(std::move(focused_tab_data)); |
| return; |
| } |
| NotifyWebClientFocusedTabChanged(std::move(focused_tab_data)); |
| } |
| |
| void NotifyWebClientFocusedTabChanged(glic::mojom::FocusedTabDataPtr data) { |
| web_client_->NotifyFocusedTabChanged(std::move(data)); |
| } |
| |
| void NotifyActorTaskStateChanged(const actor::ActorTask& task) { |
| const mojom::ActorTaskState state = [&]() { |
| switch (task.GetState()) { |
| case actor::ActorTask::State::kCreated: |
| case actor::ActorTask::State::kReflecting: |
| case actor::ActorTask::State::kWaitingOnUser: |
| return mojom::ActorTaskState::kIdle; |
| case actor::ActorTask::State::kActing: |
| return mojom::ActorTaskState::kActing; |
| case actor::ActorTask::State::kPausedByActor: |
| case actor::ActorTask::State::kPausedByUser: |
| return mojom::ActorTaskState::kPaused; |
| case actor::ActorTask::State::kCancelled: |
| case actor::ActorTask::State::kFinished: |
| return mojom::ActorTaskState::kStopped; |
| } |
| }(); |
| web_client_->NotifyActorTaskStateChanged(task.id().value(), state); |
| } |
| |
| void RequestToShowAutofillSuggestionsDialog( |
| actor::TaskId task_id, |
| const std::vector<autofill::ActorFormFillingRequest>& requests, |
| actor::ActorKeyedService::AutofillSuggestionsSelectedCallback |
| on_autofill_suggestions_selected) { |
| std::vector<actor::webui::mojom::FormFillingRequestPtr> mojo_requests; |
| for (const auto& request : requests) { |
| auto mojo_request = actor::webui::mojom::FormFillingRequest::New(); |
| mojo_request->requested_data = |
| static_cast<int64_t>(request.requested_data); |
| for (const auto& suggestion : request.suggestions) { |
| auto mojo_suggestion = actor::webui::mojom::AutofillSuggestion::New(); |
| mojo_suggestion->id = base::NumberToString(suggestion.id.value()); |
| mojo_suggestion->title = suggestion.title; |
| mojo_suggestion->details = suggestion.details; |
| if (suggestion.icon) { |
| mojo_suggestion->icon = suggestion.icon->AsBitmap(); |
| } |
| mojo_request->suggestions.push_back(std::move(mojo_suggestion)); |
| } |
| mojo_requests.push_back(std::move(mojo_request)); |
| } |
| |
| auto dialog_request = |
| actor::webui::mojom::SelectAutofillSuggestionsDialogRequest::New( |
| task_id.value(), std::move(mojo_requests)); |
| |
| web_client_->RequestToShowAutofillSuggestionsDialog( |
| std::move(dialog_request), std::move(on_autofill_suggestions_selected)); |
| } |
| |
| void RequestToShowCredentialSelectionDialog( |
| actor::TaskId task_id, |
| const base::flat_map<std::string, gfx::Image>& icons, |
| const std::vector<actor_login::Credential>& credentials, |
| actor::ActorKeyedService::CredentialSelectedCallback |
| on_credential_selected) { |
| // Note: mojom::<Type>Ptr is not copyable, meaning it can't be passed to the |
| // argument of base::RepeatingCallbackList::Notify (who makes a copy of the |
| // argument). All of the mojom::<Type>Ptr will be constructed locally before |
| // being passed into the mojom interface. |
| std::vector<actor::webui::mojom::CredentialPtr> mojo_credentials; |
| for (const auto& credential : credentials) { |
| mojo_credentials.push_back(actor::webui::mojom::Credential::New( |
| credential.id.value(), base::UTF16ToUTF8(credential.username), |
| base::UTF16ToUTF8(credential.source_site_or_app))); |
| } |
| base::flat_map<std::string, SkBitmap> mojo_icons; |
| for (const auto& [site_or_app, image] : icons) { |
| CHECK(!image.IsEmpty()); |
| mojo_icons.insert({site_or_app, image.AsBitmap()}); |
| } |
| auto dialog_request = |
| actor::webui::mojom::SelectCredentialDialogRequest::New( |
| task_id.value(), |
| // TODO(crbug.com/440147814): `show_dialog` should be based on the |
| // user granted permission duration. |
| /*show_dialog=*/true, std::move(mojo_credentials), |
| std::move(mojo_icons)); |
| |
| web_client_->RequestToShowCredentialSelectionDialog( |
| std::move(dialog_request), std::move(on_credential_selected)); |
| } |
| |
| void RequestToShowUserConfirmationDialog( |
| const url::Origin& navigation_origin, |
| actor::ActorKeyedService::UserConfirmationDialogCallback callback) { |
| actor::webui::mojom::UserConfirmationDialogPayloadPtr payload = nullptr; |
| payload = |
| actor::webui::mojom::UserConfirmationDialogPayload::NewNavigationOrigin( |
| navigation_origin); |
| web_client_->RequestToShowUserConfirmationDialog( |
| actor::webui::mojom::UserConfirmationDialogRequest::New( |
| std::move(payload)), |
| std::move(callback)); |
| } |
| |
| void RequestToConfirmNavigation( |
| const actor::TaskId& task_id, |
| const url::Origin& navigation_origin, |
| actor::ActorKeyedService::NavigationConfirmationCallback callback) { |
| web_client_->RequestToConfirmNavigation( |
| actor::webui::mojom::NavigationConfirmationRequest::New( |
| task_id.value(), navigation_origin), |
| std::move(callback)); |
| } |
| |
| PrefChangeRegistrar pref_change_registrar_; |
| PrefChangeRegistrar local_state_pref_change_registrar_; |
| raw_ptr<Profile> profile_; |
| raw_ptr<GlicPageHandler> page_handler_; |
| raw_ptr<GlicKeyedService> glic_service_; |
| raw_ptr<GlicWindowController> window_controller_; |
| raw_ptr<PrefService> pref_service_; |
| ActiveStateCalculator active_state_calculator_; |
| BrowserIsOpenCalculator browser_is_open_calculator_; |
| base::CallbackListSubscription focus_changed_subscription_; |
| base::CallbackListSubscription pinned_tabs_changed_subscription_; |
| base::CallbackListSubscription pinned_tab_data_changed_subscription_; |
| base::CallbackListSubscription tab_data_changed_subscription_; |
| base::CallbackListSubscription focus_data_changed_subscription_; |
| base::CallbackListSubscription focused_browser_changed_subscription_; |
| base::CallbackListSubscription active_browser_changed_subscription_; |
| base::CallbackListSubscription actor_task_state_changed_subscription_; |
| base::CallbackListSubscription |
| request_to_show_credential_selection_dialog_subscription_; |
| base::CallbackListSubscription |
| request_to_show_autofill_suggestions_dialog_subscription_; |
| base::CallbackListSubscription |
| request_to_show_user_confirmation_dialog_subscription_; |
| base::CallbackListSubscription request_to_confirm_navigation_subscription_; |
| base::CallbackListSubscription act_on_web_capability_changed_subscription_; |
| mojo::Receiver<glic::mojom::WebClientHandler> receiver_; |
| mojo::Remote<glic::mojom::WebClient> web_client_; |
| std::unique_ptr<BrowserAttachObservation> browser_attach_observation_; |
| const std::unique_ptr<GlicAnnotationManager> annotation_manager_; |
| std::unique_ptr<system_permission_settings::ScopedObservation> |
| system_permission_settings_observation_; |
| JournalHandler journal_handler_; |
| std::unique_ptr<DebouncerDeduper> debouncer_deduper_; |
| std::unique_ptr<PageMetadataManager> page_metadata_manager_; |
| }; |
| |
| GlicPageHandler::GlicPageHandler( |
| content::WebContents* webui_contents, |
| mojo::PendingReceiver<glic::mojom::PageHandler> receiver, |
| mojo::PendingRemote<mojom::Page> page) |
| : webui_contents_(webui_contents), |
| browser_context_(webui_contents->GetBrowserContext()), |
| receiver_(this, std::move(receiver)), |
| page_(std::move(page)) { |
| host_ = GetGlicService()->host_manager().WebUIPageHandlerAdded(this); |
| host_->AddPanelStateObserver(this); |
| UpdatePageState(host().GetPanelState(web_client_handler_.get()).kind); |
| subscriptions_.push_back( |
| GetGlicService()->enabling().RegisterProfileReadyStateChanged( |
| base::BindRepeating(&GlicPageHandler::UpdateProfileReadyState, |
| base::Unretained(this)))); |
| UpdateProfileReadyState(); |
| } |
| |
| GlicPageHandler::~GlicPageHandler() { |
| host_->RemovePanelStateObserver(this); |
| WebUiStateChanged(glic::mojom::WebUiState::kUninitialized); |
| // `GlicWebClientHandler` holds a pointer back to us, so delete it first. |
| web_client_handler_.reset(); |
| host_ = nullptr; |
| GetGlicService()->host_manager().WebUIPageHandlerRemoved(this); |
| } |
| |
| GlicKeyedService* GlicPageHandler::GetGlicService() { |
| return GlicKeyedServiceFactory::GetGlicKeyedService(browser_context_); |
| } |
| |
| void GlicPageHandler::CreateWebClient( |
| ::mojo::PendingReceiver<glic::mojom::WebClientHandler> |
| web_client_receiver) { |
| web_client_handler_ = std::make_unique<GlicWebClientHandler>( |
| this, browser_context_, std::move(web_client_receiver)); |
| } |
| |
| void GlicPageHandler::PrepareForClient( |
| base::OnceCallback<void(mojom::PrepareForClientResult)> callback) { |
| GetGlicService()->GetAuthController().CheckAuthBeforeLoad( |
| std::move(callback)); |
| } |
| |
| void GlicPageHandler::WebviewCommitted(const GURL& url) { |
| // TODO(crbug.com/388328847): Remove this code once launch issues are ironed |
| // out. |
| if (url.DomainIs("login.corp.google.com") || |
| url.DomainIs("accounts.google.com")) { |
| host().LoginPageCommitted(this); |
| } |
| } |
| |
| void GlicPageHandler::NotifyWindowIntentToShow() { |
| page_->IntentToShow(); |
| } |
| |
| content::RenderFrameHost* GlicPageHandler::GetGuestMainFrame() { |
| extensions::WebViewGuest* web_view_guest = nullptr; |
| content::RenderFrameHost* webui_frame = |
| webui_contents_->GetPrimaryMainFrame(); |
| if (!webui_frame) { |
| return nullptr; |
| } |
| webui_frame->ForEachRenderFrameHostWithAction( |
| [&web_view_guest](content::RenderFrameHost* rfh) { |
| auto* web_view = extensions::WebViewGuest::FromRenderFrameHost(rfh); |
| if (web_view && web_view->attached()) { |
| web_view_guest = web_view; |
| return content::RenderFrameHost::FrameIterationAction::kStop; |
| } |
| return content::RenderFrameHost::FrameIterationAction::kContinue; |
| }); |
| return web_view_guest ? web_view_guest->GetGuestMainFrame() : nullptr; |
| } |
| |
| void GlicPageHandler::ClosePanel(ClosePanelCallback callback) { |
| host().ClosePanel(this); |
| std::move(callback).Run(); |
| } |
| |
| void GlicPageHandler::OpenProfilePickerAndClosePanel() { |
| glic::GlicProfileManager::GetInstance()->ShowProfilePicker(); |
| host().ClosePanel(this); |
| } |
| |
| void GlicPageHandler::OpenDisabledByAdminLinkAndClosePanel() { |
| GURL disabled_by_admin_link_url = GURL(features::kGlicCaaLinkUrl.Get()); |
| NavigateParams params(Profile::FromBrowserContext(browser_context_), |
| disabled_by_admin_link_url, |
| ui::PAGE_TRANSITION_AUTO_TOPLEVEL); |
| params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB; |
| Navigate(¶ms); |
| host().ClosePanel(this); |
| base::RecordAction( |
| base::UserMetricsAction("Glic.DisabledByAdminPanelLinkClicked")); |
| } |
| |
| void GlicPageHandler::SignInAndClosePanel() { |
| GetGlicService()->GetAuthController().ShowReauthForAccount(base::BindOnce( |
| &GlicWindowController::ShowAfterSignIn, |
| // Unretained is safe because the keyed service owns the |
| // auth controller and the window controller. |
| base::Unretained(&GetGlicService()->window_controller()), nullptr)); |
| host().ClosePanel(this); |
| } |
| |
| void GlicPageHandler::ResizeWidget(const gfx::Size& size, |
| base::TimeDelta duration, |
| ResizeWidgetCallback callback) { |
| host().ResizePanel(this, size, duration, std::move(callback)); |
| } |
| |
| void GlicPageHandler::EnableDragResize(bool enabled) { |
| // features::kGlicUserResize is not checked here because the WebUI page |
| // invokes this method when it is disabled, too (when its state changes). |
| host().EnableDragResize(this, enabled); |
| } |
| |
| void GlicPageHandler::WebUiStateChanged(glic::mojom::WebUiState new_state) { |
| host().WebUiStateChanged(this, new_state); |
| } |
| |
| void GlicPageHandler::PanelStateChanged( |
| const glic::mojom::PanelState& panel_state, |
| const PanelStateContext& context) { |
| UpdatePageState(panel_state.kind); |
| } |
| |
| void GlicPageHandler::UpdateProfileReadyState() { |
| page_->SetProfileReadyState(GlicEnabling::GetProfileReadyState( |
| Profile::FromBrowserContext(browser_context_))); |
| } |
| |
| void GlicPageHandler::UpdatePageState(mojom::PanelStateKind panelStateKind) { |
| page_->UpdatePageState(panelStateKind); |
| } |
| |
| void GlicPageHandler::ZeroStateSuggestionChanged( |
| mojom::ZeroStateSuggestionsV2Ptr returned_suggestions, |
| mojom::ZeroStateSuggestionsOptions returned_options) { |
| if (!web_client_handler_) { |
| return; |
| } |
| |
| auto options = mojom::ZeroStateSuggestionsOptions::New(); |
| options->is_first_run = std::move(returned_options.is_first_run); |
| options->supported_tools = std::move(returned_options.supported_tools); |
| web_client_handler_->NotifyZeroStateSuggestionsChanged( |
| std::move(returned_suggestions), std::move(options)); |
| } |
| |
| } // namespace glic |