blob: c5e575bdef83be05c0d8e28e683c7808e9b4f9b6 [file] [log] [blame]
// 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.
#ifndef CHROME_BROWSER_GLIC_WIDGET_GLIC_WINDOW_CONTROLLER_H_
#define CHROME_BROWSER_GLIC_WIDGET_GLIC_WINDOW_CONTROLLER_H_
#include <optional>
#include <vector>
#include "base/callback_list.h"
#include "base/functional/callback_forward.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/observer_list_types.h"
#include "base/scoped_observation.h"
#include "base/scoped_observation_traits.h"
#include "chrome/browser/glic/glic_enabling.h"
#include "chrome/browser/glic/host/glic.mojom.h"
#include "chrome/browser/glic/host/glic_web_client_access.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/base/interaction/element_tracker.h"
#include "ui/views/widget/widget_observer.h"
class Browser;
class WindowFinder;
namespace gfx {
class Size;
class Point;
} // namespace gfx
namespace glic {
// Distance the detached window should be from the top and the right of the
// display when opened unassociated to a browser.
inline constexpr static int kDefaultDetachedTopRightDistance = 48;
DECLARE_CUSTOM_ELEMENT_EVENT_TYPE(kGlicWidgetAttached);
class GlicEnabling;
class GlicWidget;
class GlicKeyedService;
class GlicView;
class GlicWindowAnimator;
class ScopedGlicButtonIndicator;
class GlicFreController;
class GlicButton;
class Host;
enum class AttachChangeReason;
// This class owns and manages the glic window. This class has the same lifetime
// as the GlicKeyedService, so it exists if and only if the profile exists.
//
// See the |State| enum below for the lifecycle of the window. When the glic
// window is open |attached_browser_| indicates if the window is attached or
// standalone. See |IsAttached|
class GlicWindowController : public views::WidgetObserver,
public ui::AcceleratorTarget {
public:
// Observes the state of the glic window.
class StateObserver : public base::CheckedObserver {
public:
virtual void PanelStateChanged(const mojom::PanelState& panel_state,
Browser* attached_browser) = 0;
};
// Observes the state of the WebUI hosted in the glic window.
class WebUiStateObserver : public base::CheckedObserver {
public:
virtual void WebUiStateChanged(mojom::WebUiState state) = 0;
};
GlicWindowController(const GlicWindowController&) = delete;
GlicWindowController& operator=(const GlicWindowController&) = delete;
GlicWindowController(Profile* profile,
signin::IdentityManager* identity_manager,
GlicKeyedService* service,
GlicEnabling* enabling);
~GlicWindowController() override;
// Show, summon, or activate the panel if needed, or close it if it's already
// active and prevent_close is false.
void Toggle(BrowserWindowInterface* browser,
bool prevent_close,
mojom::InvocationSource source);
// If the panel is opened, but sign-in is required, we provide a sign-in
// button which closes the panel. This is called after the user signs in to
// open the panel again.
void ShowAfterSignIn(base::WeakPtr<Browser> browser);
// Handle Toggle when AlwaysDetached is true.
void ToggleWhenNotAlwaysDetached(Browser* new_attached_browser,
bool prevent_close,
mojom::InvocationSource source);
void FocusIfOpen();
// Attaches glic to the last focused Chrome window.
void Attach();
// Detaches glic if attached and moves it to the top right of the current
// display.
void Detach();
// Destroy the glic panel and its web contents.
void Shutdown();
// Sets the size of the glic window to the specified dimensions. Callback runs
// when the animation finishes or is destroyed, or soon if the window
// doesn't exist yet. In this last case `size` will be used for the initial
// size when creating the widget later.
void Resize(const gfx::Size& size,
base::TimeDelta duration,
base::OnceClosure callback);
// Allows the user to manually resize the widget by dragging. If the widget
// hasn't been created yet, apply this setting when it is created. No effect
// if the widget doesn't exist or the feature flag is disabled.
void EnableDragResize(bool enabled);
// Returns the current size of the glic window.
gfx::Size GetSize();
// Sets the areas of the view from which it should be draggable.
void SetDraggableAreas(const std::vector<gfx::Rect>& draggable_areas);
// Sets the minimum widget size that the widget will allow the user to resize
// to.
void SetMinimumWidgetSize(const gfx::Size& size);
// Close the panel but keep the glic WebContents alive in the background.
void Close();
// Sets the audio ducking status. Returns true if the operation succeeded.
bool SetAudioDucking(bool enabled);
// Displays a context menu when the user right clicks on the title bar.
// This is probably Windows only.
void ShowTitleBarContextMenuAt(gfx::Point event_loc);
// Returns true if the mouse has been dragged more than a minimum distance
// from `initial_press_loc`, so a mouse down followed by a move of less than
// the minimum number of pixels doesn't start a window drag.
bool ShouldStartDrag(const gfx::Point& initial_press_loc,
const gfx::Point& mouse_location);
// Drags the glic window following the current mouse location with the given
// `mouse_offset` and checks if the glic window is at a position where it
// could attach to a browser window when a drag ends.
void HandleWindowDragWithOffset(gfx::Vector2d mouse_offset);
const mojom::PanelState& GetPanelState() const { return panel_state_; }
void AddStateObserver(StateObserver* observer);
void RemoveStateObserver(StateObserver* observer);
const mojom::WebUiState& GetWebUiState() const { return webui_state_; }
void AddWebUiStateObserver(WebUiStateObserver* observer);
void RemoveWebUiStateObserver(WebUiStateObserver* observer);
// Returns whether the views::Widget associated with the glic window is active
// (e.g. will receive keyboard events).
bool IsActive();
// Returns true if the state is anything other than kClosed.
// Virtual for testing.
virtual bool IsShowing() const;
// Returns true if either the glic panel or the FRE are showing.
virtual bool IsPanelOrFreShowing() const;
// Returns whether or not the glic window is currently attached to a browser.
// Virtual for testing.
virtual bool IsAttached() const;
// Returns wehether or not the glic window is currently showing detached.
bool IsDetached() const;
using WindowActivationChangedCallback =
base::RepeatingCallback<void(bool active)>;
// Registers |callback| to be called whenever the window activation changes.
base::CallbackListSubscription AddWindowActivationChangedCallback(
WindowActivationChangedCallback callback);
// Warms the glic web contents.
void Preload();
// Warms the fre web contents.
void PreloadFre();
// Reloads the glic web contents or the FRE's web contents (depending on
// which is currently visible).
void Reload();
// Returns whether or not the glic web contents are loaded (this can also be
// true if `IsActive()` (i.e., if the contents are loaded in the glic window).
bool IsWarmed() const;
// Returns a WeakPtr to this instance. It can be destroyed at any time if the
// profile is deleted or if the browser shuts down.
base::WeakPtr<GlicWindowController> GetWeakPtr();
void WebClientInitializeFailed();
// The webview reached a login page.
void LoginPageCommitted();
void SetWebClient(GlicWebClientAccess* web_client);
GlicWebClientAccess* web_client() const { return web_client_; }
// views::WidgetObserver implementation, monitoring the glic window widget.
void OnWidgetActivationChanged(views::Widget* widget, bool active) override;
void OnWidgetDestroyed(views::Widget* widget) override;
void OnWidgetBoundsChanged(views::Widget* widget,
const gfx::Rect& new_bounds) override;
void OnWidgetUserResizeStarted() override;
void OnWidgetUserResizeEnded() override;
GlicView* GetGlicView();
// Called when the programmatic resize has finished. Public for use by
// GlicWindowResizeAnimation.
void ResizeFinished();
// Returns the widget that backs the glic window.
views::Widget* GetGlicWidget();
// Returns the WebContents used for the first-run experience, or nullptr if
// none.
content::WebContents* GetFreWebContents();
// Return the Browser to which the panel is attached, or null if detached.
Browser* attached_browser() { return attached_browser_; }
// Possible states for the glic window. Public for testing.
// * Closed (aka hidden, invisible)
// * OpenAnimation (showing an animation built into chrome, independent of
// the content of the glic window)
// * Waiting for glic to load (the open animation has finished, but the
// glic window contents is not yet ready)
// * Open (aka showing, visible)
// * CloseAnimation
// * Detaching
// * ClosingToReopenDetached
enum class State {
kClosed,
kOpenAnimation,
kWaitingForGlicToLoad,
kOpen,
kDetaching,
kClosingToReopenDetached,
kCloseAnimation,
};
State state() const { return state_; }
void ShowDetachedForTesting();
void WebUiStateChanged(mojom::WebUiState new_state);
GlicFreController* fre_controller() { return fre_controller_.get(); }
GlicWindowAnimator* window_animator() { return glic_window_animator_.get(); }
Profile* profile() { return profile_; }
// Helper function to get the always detached flag.
static bool AlwaysDetached();
bool IsDragging() { return in_move_loop_; }
private:
FRIEND_TEST_ALL_PREFIXES(GlicWindowControllerUiTest, TestInitialBounds);
Host& host() const;
// ui::AcceleratorTarget
bool AcceleratorPressed(const ui::Accelerator& accelerator) override;
bool CanHandleAccelerators() const override;
void AddAccelerators();
// Sets the floating attributes of the glic window.
//
// When set to true, the glic window is set to have a `kFloatingWindow`
// z-order, and on the Mac is set to be "activation independent" (to allow the
// user to interact with it without causing Chromium to be activated), and
// visible on every space (including fullscreen ones).
//
// When set to false, the glic window is set to have a `kNormal` z-order, and
// on the Mac, all special activation and visibility properties are cleared.
void SetGlicWindowToFloatingMode(bool floating);
gfx::Rect GetInitialBounds(Browser* browser);
// Return the default detached bounds which are just below the tab strip
// button on the active browser.
gfx::Rect GetInitialDetachedBoundsFromBrowser(Browser& browser,
const gfx::Size& target_size);
// Return the default detached bounds when there is no active browser. The
// position is relative to the top right of the current display.
gfx::Rect GetInitialDetachedBoundsNoBrowser(const gfx::Size& target_size);
// Return the default bounds when attached to the browser which cover the tab
// strip button on the active browser.
gfx::Rect GetInitialAttachedBounds(Browser& browser);
// Creates the glic view, waits for the web client to initialize, and then
// shows the glic window. If `browser` is non-nullptr then glic will be
// attached to the browser. Otherwise glic will be detached.
void Show(Browser* browser, mojom::InvocationSource source);
// Close the widget and reopen in detached mode.
void CloseAndReopenDetached(mojom::InvocationSource source);
void SetupGlicWidget(Browser* browser);
void SetupGlicWidgetAccessibilityText();
void StartAttachedAnimation(GlicButton* glic_button);
// This sends a message to glic to get ready to show. This will eventually
// result in the callback GlicLoaded().
void WaitForGlicToLoad();
void GlicLoaded(mojom::OpenPanelInfoPtr open_info);
// Called when the open animation is finished.
void OpenAnimationFinished();
// Called once glic is completely loaded and any animations have finished.
// This is the end of the opening process and |state_| will be set to kOpen.
void GlicLoadedAndAnimationDone();
void SetDraggingAreasAndWatchForMouseEvents();
// Internal closing implementation. reopen_detached_source must be set
// if and only if the internal state is kClosingToReopenDetached.
void CloseInternal(
std::optional<mojom::InvocationSource> reopen_detached_source);
// Finishes closing off the widget after running the closing animation.
void CloseFinish(
bool reopen_detached,
std::optional<mojom::InvocationSource> reopen_detached_source);
// Called when the Detach() animation ends.
void DetachFinished();
// Causes an immediate close (eg, for during shutdown).
void ForceClose();
// Save the top-right corner position for re-opening.
void SaveWidgetPosition();
// Clear the previous position if the widget would not be on an existing
// display when shown.
void MaybeResetPreviousPosition(const gfx::Size& target_size);
// Determines the correct position for the glic window when attached to a
// browser window. The top right of the widget should be placed here.
gfx::Point GetTopRightPositionForAttachedGlicWindow(GlicButton* glic_button);
// Runs an animation to move glic to its target position.
// TODO(crbug.com/410629338): Reimplement attachment.
void AttachToBrowser(Browser& browser, AttachChangeReason reason);
// Clamp the mouse drag offsets to keep glic within the visible region.
gfx::Vector2d GetClampedMouseDragOffset(const gfx::Vector2d& mouse_offset);
// Handles end-of-drag:
// - If glic is within attachment distance of a browser window's glic button,
// attach the glic window to the button's position.
// - If glic is still detached and has moved to a display with a different
// work area size, possibly resize the window.
void OnDragComplete();
// Finds a browser within attachment distance of glic to toggle the attachment
// indicator.
void HandleGlicButtonIndicator();
// Find and return a browser within attachment distance. Returns nullptr if no
// browsers are within attachment distance.
Browser* FindBrowserForAttachment();
// Updates the position of the glic window to that of the glic button of
// `browser`'s window. This position change is animated if `animate` is true.
void MovePositionToBrowserGlicButton(const Browser& browser, bool animate);
// Called when the move animation finishes when attaching.
void AttachAnimationFinished();
// This method should be called anytime:
// * state_ transitions to or from kClosed.
// * attached_browser_ changes.
void NotifyIfPanelStateChanged();
mojom::PanelState ComputePanelState() const;
// When the attached browser is closed, this is invoked so we can clean up.
void AttachedBrowserDidClose(BrowserWindowInterface* browser);
// Returns true if a browser is occluded at point in screen coordinates.
bool IsBrowserOccludedAtPoint(Browser* browser, gfx::Point point);
// Return the last size Resize() was called with, or the default initial size
// if Resize() hasn't been called. The return value is clamped to fit between
// the minimum and maximum sizes.
gfx::Size GetLastRequestedSizeClamped() const;
// Possibly adjusts the size of the window appropriate for the current
// display workspace, but only if it's different than the current target size.
void MaybeAdjustSizeForDisplay(bool animate);
// Modifies `state_` to the given new state.
void SetWindowState(State new_state);
// Returns true of the window is showing and the content is loaded.
bool IsWindowOpenAndReady();
// Observes the glic widget.
base::ScopedObservation<views::Widget, views::WidgetObserver>
glic_widget_observation_{this};
// Used for observing closing of the pinned browser.
std::optional<base::CallbackListSubscription> browser_close_subscription_;
// List of callbacks to be notified when window activation has changed.
base::RepeatingCallbackList<void(bool)> window_activation_callback_list_;
const raw_ptr<Profile> profile_;
// Contains the glic webview.
std::unique_ptr<GlicWidget> glic_widget_;
std::unique_ptr<GlicWindowAnimator> glic_window_animator_;
// True if we've hit a login page (and have not yet shown).
bool login_page_committed_ = false;
// This member contains the last size that glic requested. This should be
// reset every time glic is closed but is currently cached.
std::optional<gfx::Size> glic_size_;
// Whether the widget should be user resizable, kept here in case it's
// specified before the widget is created.
bool user_resizable_ = true;
// Used to monitor key and mouse events from native window.
class WindowEventObserver;
std::unique_ptr<WindowEventObserver> window_event_observer_;
// True while RunMoveLoop() has been called on a widget.
bool in_move_loop_ = false;
// This is the last panel state sent to observers. It should only be updated
// in `NotifyIfPanelStateChanged`.
mojom::PanelState panel_state_;
raw_ptr<GlicWebClientAccess> web_client_;
// Modified only by calling `SetWindowState`.
State state_ = State::kClosed;
// If State != kClosed, then the UI must either be associated with a browser
// window, or standalone. That is tracked by this member.
raw_ptr<Browser> attached_browser_ = nullptr;
// Set to true when glic is ready.
bool glic_loaded_ = false;
base::ObserverList<StateObserver> state_observers_;
mojom::WebUiState webui_state_ = mojom::WebUiState::kUninitialized;
base::ObserverList<WebUiStateObserver> webui_state_observers_;
// The announcement should happen the first time focus is lost after the FRE.
bool do_focus_loss_announcement_ = false;
// The invocation source requesting the opening of the web client. Note that
// this value is retained until it is consumed by the web client. Because
// opening the glic window may not actually load the client, there's no
// guarantee that this value is sent to the web client.
std::optional<mojom::InvocationSource> opening_source_;
std::optional<gfx::Point> previous_position_ = std::nullopt;
std::unique_ptr<ScopedGlicButtonIndicator> scoped_glic_button_indicator_;
std::unique_ptr<GlicFreController> fre_controller_;
std::unique_ptr<WindowFinder> window_finder_;
raw_ptr<GlicKeyedService> glic_service_; // Owns this.
raw_ptr<GlicEnabling> enabling_;
base::WeakPtrFactory<GlicWindowController> weak_ptr_factory_{this};
};
} // namespace glic
namespace base {
template <>
struct ScopedObservationTraits<glic::GlicWindowController,
glic::GlicWindowController::StateObserver> {
static void AddObserver(glic::GlicWindowController* source,
glic::GlicWindowController::StateObserver* observer) {
source->AddStateObserver(observer);
}
static void RemoveObserver(
glic::GlicWindowController* source,
glic::GlicWindowController::StateObserver* observer) {
source->RemoveStateObserver(observer);
}
};
} // namespace base
#endif // CHROME_BROWSER_GLIC_WIDGET_GLIC_WINDOW_CONTROLLER_H_