blob: df6a0eea2be4ac7c77bfc48f2fe8e446efb8a2ea [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_GLIC_SERVICE_GLIC_INSTANCE_IMPL_H_
#define CHROME_BROWSER_GLIC_SERVICE_GLIC_INSTANCE_IMPL_H_
#include "base/callback_list.h"
#include "base/containers/flat_map.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/scoped_observation.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "chrome/browser/actor/actor_task.h"
#include "chrome/browser/glic/actor/glic_actor_task_manager.h"
#include "chrome/browser/glic/host/context/glic_delegating_sharing_manager.h"
#include "chrome/browser/glic/host/context/glic_sharing_manager_impl.h"
#include "chrome/browser/glic/host/context/glic_sharing_manager_provider.h"
#include "chrome/browser/glic/host/glic.mojom.h"
#include "chrome/browser/glic/host/host.h"
#include "chrome/browser/glic/public/glic_instance.h"
#include "chrome/browser/glic/service/glic_instance_helper.h"
#include "chrome/browser/glic/service/glic_instance_metrics.h"
#include "chrome/browser/glic/service/glic_ui_embedder.h"
#include "chrome/browser/glic/service/glic_ui_types.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_list_observer.h"
#include "components/tabs/public/tab_interface.h"
class Profile;
namespace tabs {
class TabInterface;
}
namespace contextual_cueing {
class ContextualCueingService;
}
namespace glic {
class GlicUiEmbedder;
class EmptyEmbedderDelegate;
class GlicTabContentsObserver;
class GlicZeroStateSuggestionsManager;
// A GlicInstance owns a single host keeping any state that must exist for the
// lifetime of the host. When a host is showing, the GlicInstance creates a
// GlicUiEmbedder to display the webcontents in. An instance (and host) exist
// even if it has no GlicUiEmbedder showing the UI. A host could have many
// different GlicUiEmbedders during its lifetime.
class GlicInstanceImpl : public GlicInstance,
public BrowserListObserver,
public Host::InstanceDelegate,
public Host::Observer,
public GlicSharingManagerProvider,
public GlicUiEmbedder::Delegate,
public actor::ActorTaskDelegate {
public:
class InstanceCoordinatorDelegate {
public:
virtual ~InstanceCoordinatorDelegate() = default;
virtual void RemoveInstance(GlicInstanceImpl* instance) = 0;
// Called by an instance when its visibility state changes.
virtual void OnInstanceVisibilityChanged(GlicInstanceImpl* instance,
bool is_showing) = 0;
virtual void OnInstanceActivationChanged(GlicInstanceImpl* instance,
bool is_active) = 0;
virtual void SwitchConversation(
GlicInstanceImpl& source_instance,
const ShowOptions& options,
glic::mojom::ConversationInfoPtr info,
mojom::WebClientHandler::SwitchConversationCallback callback) = 0;
virtual void UnbindTabFromAnyInstance(tabs::TabInterface* tab) = 0;
// Called by an instance when user requests to undock to Floaty.
virtual void OnWillCreateFloaty() = 0;
};
GlicInstanceImpl(
Profile* profile,
InstanceId instance_id,
base::WeakPtr<InstanceCoordinatorDelegate> coordinator_delegate,
GlicMetrics* metrics,
contextual_cueing::ContextualCueingService* contextual_cueing_service);
~GlicInstanceImpl() override;
GlicInstanceImpl(const GlicInstanceImpl&) = delete;
GlicInstanceImpl& operator=(const GlicInstanceImpl&) = delete;
Profile* profile() { return profile_; }
// GlicSharingManagerProvider implementation.
GlicSharingManager& sharing_manager() override;
void NotifyInstanceActivationChanged(bool is_active);
base::TimeTicks GetLastActiveTime() const;
bool IsHibernated() const;
void Hibernate();
void CloseInstanceAndShutdown();
// GlicInstance implementation.
bool IsShowing() const override;
bool IsAttached() override;
gfx::Size GetPanelSize() override;
bool IsActive() override;
bool IsDetached();
bool IsActuating() const;
// These methods should only be called by the GlicInstanceCoordinator.
// This method will either show an embedder or create an inactive embedder and
// bind a tab to conversation.
void Show(const ShowOptions& options) override;
void Close(EmbedderKey key);
// Returns true when toggle shows the instance and false when it is closed.
bool Toggle(ShowOptions&& options,
bool prevent_close,
glic::mojom::InvocationSource source);
void UnbindEmbedder(EmbedderKey key);
GlicUiEmbedder* GetEmbedderForTab(tabs::TabInterface* tab);
// GlicInstance:
Host& host() override;
const InstanceId& id() const override;
std::optional<std::string> conversation_id() const;
base::CallbackListSubscription RegisterStateChange(
StateChangeCallback callback) override;
// Host::InstanceDelegate:
// TODO: Currently, both GlicInstanceImpl and GlicKeyedService implement
// Host::InstanceDelegate. The CreateTab function here should only return the
// tab for GlicKeyedService, but not GlicInstanceImpl. We should figure out a
// way to decouple this.
tabs::TabInterface* CreateTab(
const ::GURL& url,
bool open_in_background,
const std::optional<int32_t>& window_id,
glic::mojom::WebClientHandler::CreateTabCallback callback) override;
void CreateTask(
base::WeakPtr<actor::ActorTaskDelegate> delegate,
actor::webui::mojom::TaskOptionsPtr options,
mojom::WebClientHandler::CreateTaskCallback callback) override;
void PerformActions(
const std::vector<uint8_t>& actions_proto,
mojom::WebClientHandler::PerformActionsCallback callback) override;
void StopActorTask(actor::TaskId task_id,
mojom::ActorTaskStopReason stop_reason) override;
void PauseActorTask(actor::TaskId task_id,
mojom::ActorTaskPauseReason pause_reason,
tabs::TabInterface::Handle tab_handle) override;
void ResumeActorTask(
actor::TaskId task_id,
const mojom::GetTabContextOptions& context_options,
glic::mojom::WebClientHandler::ResumeActorTaskCallback callback) override;
void InterruptActorTask(actor::TaskId task_id) override;
void UninterruptActorTask(actor::TaskId task_id) override;
void FetchZeroStateSuggestions(
bool is_first_run,
std::optional<std::vector<std::string>> supported_tools,
glic::mojom::WebClientHandler::
GetZeroStateSuggestionsForFocusedTabCallback callback) override;
void RegisterConversation(
glic::mojom::ConversationInfoPtr info,
mojom::WebClientHandler::RegisterConversationCallback callback) override;
void GetZeroStateSuggestionsAndSubscribe(
bool has_active_subscription,
const mojom::ZeroStateSuggestionsOptions& options,
mojom::WebClientHandler::GetZeroStateSuggestionsAndSubscribeCallback
callback) override;
void OnWebClientCleared() override;
void PrepareForOpen() override;
void OnInteractionModeChange(mojom::WebClientMode new_mode) override;
glic::GlicInstanceMetrics* instance_metrics() override;
// GlicUiEmbedder::Delegate:
void OnEmbedderWindowActivationChanged(bool has_focus) override;
void SwitchConversation(
const ShowOptions& options,
glic::mojom::ConversationInfoPtr info,
mojom::WebClientHandler::SwitchConversationCallback callback) override;
void WillCloseFor(EmbedderKey key) override;
void NotifyPanelStateChanged() override;
// Opens the floating UI for this instance
void Detach(tabs::TabInterface* tab) override;
// Host::InstanceInterface:
mojom::PanelState GetPanelState() override;
void AddStateObserver(PanelStateObserver* observer) override;
void RemoveStateObserver(PanelStateObserver* observer) override;
// BrowserListObserver:
void OnBrowserSetLastActive(Browser* browser) override;
// Host::Observer
void WebUiStateChanged(mojom::WebUiState state) override;
// ActorTaskDelegate:
void OnTabAddedToTask(actor::TaskId task_id,
const tabs::TabInterface::Handle& tab_handle) override;
glic::GlicInstanceMetrics* metrics() { return &instance_metrics_; }
// Test support.
void CloseAllEmbeddersForTesting();
views::View* GetActiveEmbedderGlicViewForTesting();
private:
// We use a delegating constructor pattern so we can hand off ownership of the
// focused browser manager as well as provide a reference to it to another
// object.
GlicInstanceImpl(
Profile* profile,
InstanceId instance_id,
base::WeakPtr<InstanceCoordinatorDelegate> coordinator_delegate,
GlicMetrics* metrics,
contextual_cueing::ContextualCueingService* contextual_cueing_service,
GlicFocusedBrowserManager* focused_browser_manager);
struct EmbedderEntry {
EmbedderEntry();
~EmbedderEntry();
EmbedderEntry(EmbedderEntry&&);
EmbedderEntry& operator=(EmbedderEntry&&);
std::unique_ptr<GlicUiEmbedder> embedder;
base::CallbackListSubscription destruction_subscription;
base::CallbackListSubscription tab_activation_subscription;
std::unique_ptr<GlicTabContentsObserver> tab_web_contents_observer;
};
struct ConversationInfo {
std::string conversation_id;
std::string conversation_title;
};
void NotifyStateChange();
GlicUiEmbedder* GetActiveEmbedder();
GlicUiEmbedder* GetEmbedderForKey(EmbedderKey key);
void DeactivateCurrentEmbedder();
GlicUiEmbedder* CreateActiveEmbedder(const ShowOptions& options);
GlicUiEmbedder* CreateActiveEmbedderForSidePanel(tabs::TabInterface* tab);
GlicUiEmbedder* CreateActiveEmbedderForFloaty(
const gfx::Rect& initial_bounds);
void ShowInactiveSidePanelEmbedderFor(tabs::TabInterface* tab);
void SetActiveEmbedderAndNotifyStateChange(
std::optional<EmbedderKey> new_key);
void ClearActiveEmbedderAndNotifyStateChange();
void MaybeShowHostUi(GlicUiEmbedder* embedder);
void OnBoundTabDestroyed(tabs::TabInterface* tab,
const InstanceId& instance_id);
void OnBoundTabActivated(tabs::TabInterface* tab);
bool ShouldDoAutomaticActivation() const;
void OnZeroStateSuggestionsFetched(
mojom::ZeroStateSuggestionsPtr suggestions,
mojom::WebClientHandler::GetZeroStateSuggestionsForFocusedTabCallback
callback,
std::vector<std::string> returned_suggestions);
void MaybeDeactivateEmbedder(EmbedderKey key);
void MaybeActivateForegroundEmbedder();
EmbedderEntry& BindTab(tabs::TabInterface* tab);
// For any pinned tab not already bound to a conversation bind it to this one.
void OnTabPinningStatusChanged(tabs::TabInterface* tab, bool pinned);
void NotifyPanelWillOpen(mojom::InvocationSource invocation_source);
using StateChangeCallbackList =
base::RepeatingCallbackList<void(bool, mojom::CurrentView view)>;
StateChangeCallbackList state_change_callback_list_;
base::ObserverList<PanelStateObserver> state_observers_;
raw_ptr<Profile> profile_;
raw_ptr<GlicKeyedService> service_;
base::WeakPtr<InstanceCoordinatorDelegate> coordinator_delegate_;
InstanceId id_;
GlicInstanceMetrics instance_metrics_;
// The single source of truth for all embedders.
// A tabs::TabInterface* key is a tab-bound side panel.
// A FloatingEmbedderKey key is the instance-bound floating panel.
base::flat_map<EmbedderKey, EmbedderEntry> embedders_;
// The single, unambiguous source of truth for the active UI.
std::optional<EmbedderKey> active_embedder_key_;
// The empty embedder delegate is owned by this instance and its lifetime is
// guaranteed to be longer than `host_` because it is declared before `host_`.
glic::EmptyEmbedderDelegate empty_embedder_delegate_;
// `IsActive` can be called by `host_`, so the member needs to outlive it.
bool is_active_ = false;
Host host_;
std::optional<ConversationInfo> conversation_info_;
// The pinned tab manager for the instance.
// TODO (crbug.com/452150693): move ownership of this instance into the
// GlicStablePinningDelegatingSharingManager.
GlicPinnedTabManager pinned_tab_manager_;
// The sharing manager used internally for detached mode.
GlicSharingManagerImpl detached_mode_sharing_manager_;
// The sharing manager used internally for attached mode.
GlicSharingManagerImpl attached_mode_sharing_manager_;
// The source of truth sharing manager for the instance.
GlicStablePinningDelegatingSharingManager sharing_manager_;
// Tracks the last non-hidden panel state kind for the instance. This is
// useful for responding to changes in attached/detached state.
mojom::PanelStateKind last_non_hidden_panel_state_kind_;
mojom::WebClientMode interaction_mode_;
base::ScopedObservation<BrowserList, BrowserListObserver>
browser_list_observation_{this};
base::ScopedObservation<Host, Host::Observer> host_observation_{this};
std::unique_ptr<GlicZeroStateSuggestionsManager>
zero_state_suggestions_manager_;
std::unique_ptr<GlicActorTaskManager> actor_task_manager_;
base::CallbackListSubscription pinned_tabs_change_subscription_;
base::OneShotTimer inactivity_timer_;
base::TimeTicks last_active_time_;
base::WeakPtrFactory<GlicInstanceImpl> weak_ptr_factory_{this};
};
} // namespace glic
#endif // CHROME_BROWSER_GLIC_SERVICE_GLIC_INSTANCE_IMPL_H_