| // 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/widget/glic_window_controller_impl.h" |
| |
| #include <algorithm> |
| |
| #include "base/check.h" |
| #include "base/check_deref.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/user_metrics.h" |
| #include "base/notimplemented.h" |
| #include "base/time/time.h" |
| #include "chrome/browser/actor/actor_keyed_service.h" |
| #include "chrome/browser/actor/ui/actor_ui_state_manager_interface.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/glic/browser_ui/scoped_glic_button_indicator.h" |
| #include "chrome/browser/glic/fre/glic_fre_controller.h" |
| #include "chrome/browser/glic/fre/glic_fre_dialog_view.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/host/context/glic_screenshot_capturer.h" |
| #include "chrome/browser/glic/host/glic.mojom.h" |
| #include "chrome/browser/glic/host/host.h" |
| #include "chrome/browser/glic/host/webui_contents_container.h" |
| #include "chrome/browser/glic/public/glic_enabling.h" |
| #include "chrome/browser/glic/public/glic_keyed_service.h" |
| #include "chrome/browser/glic/widget/application_hotkey_delegate.h" |
| #include "chrome/browser/glic/widget/browser_conditions.h" |
| #include "chrome/browser/glic/widget/glic_panel_hotkey_delegate.h" |
| #include "chrome/browser/glic/widget/glic_view.h" |
| #include "chrome/browser/glic/widget/glic_widget.h" |
| #include "chrome/browser/glic/widget/glic_window_animator.h" |
| #include "chrome/browser/glic/widget/glic_window_config.h" |
| #include "chrome/browser/profiles/keep_alive/profile_keep_alive_types.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/browser/ui/browser_element_identifiers.h" |
| #include "chrome/browser/ui/browser_finder.h" |
| #include "chrome/browser/ui/browser_window/public/browser_window_interface.h" |
| #include "chrome/browser/ui/browser_window/public/browser_window_interface_iterator.h" |
| #include "chrome/browser/ui/layout_constants.h" |
| #include "chrome/browser/ui/tabs/public/tab_dialog_manager.h" |
| #include "chrome/browser/ui/tabs/public/tab_features.h" |
| #include "chrome/browser/ui/views/chrome_widget_sublevel.h" |
| #include "chrome/browser/ui/views/frame/browser_view.h" |
| #include "chrome/browser/ui/views/frame/tab_strip_region_view.h" |
| #include "chrome/browser/ui/views/frame/tab_strip_view_interface.h" |
| #include "chrome/browser/ui/views/interaction/browser_elements_views.h" |
| #include "chrome/browser/ui/views/side_panel/side_panel.h" |
| #include "chrome/browser/ui/views/side_panel/side_panel_coordinator.h" |
| #include "chrome/browser/ui/views/tabs/glic_button.h" |
| #include "chrome/browser/ui/views/tabs/tab_strip_action_container.h" |
| #include "chrome/browser/ui/views/tabs/window_finder.h" |
| #include "chrome/common/chrome_features.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/tabs/public/tab_interface.h" |
| #include "components/web_modal/web_contents_modal_dialog_manager.h" |
| #include "content/public/browser/web_contents.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/ui_base_types.h" |
| #include "ui/display/display.h" |
| #include "ui/display/display_finder.h" |
| #include "ui/display/display_observer.h" |
| #include "ui/display/screen.h" |
| #include "ui/events/event_observer.h" |
| #include "ui/views/accessibility/view_accessibility.h" |
| #include "ui/views/controls/webview/webview.h" |
| #include "ui/views/event_monitor.h" |
| #include "ui/views/interaction/element_tracker_views.h" |
| #include "ui/views/widget/native_widget.h" |
| #include "ui/views/widget/widget_observer.h" |
| |
| #if BUILDFLAG(IS_WIN) |
| #include "ui/aura/window.h" |
| #include "ui/aura/window_tree_host.h" |
| #include "ui/base/win/event_creation_utils.h" |
| #include "ui/display/win/screen_win.h" |
| #include "ui/views/win/hwnd_util.h" |
| #endif // BUILDFLAG(IS_WIN) |
| |
| namespace glic { |
| |
| DEFINE_CUSTOM_ELEMENT_EVENT_TYPE(kGlicWidgetAttached); |
| |
| namespace { |
| |
| // Default value for adding a buffer to the attachment zone. |
| constexpr static int kAttachmentBuffer = 20; |
| |
| constexpr static base::TimeDelta kAnimationDuration = base::Milliseconds(300); |
| |
| mojom::PanelState CreatePanelState(bool widget_visible, |
| Browser* attached_browser) { |
| mojom::PanelState panel_state; |
| if (!widget_visible) { |
| panel_state.kind = mojom::PanelStateKind::kHidden; |
| } else if (attached_browser) { |
| panel_state.kind = mojom::PanelStateKind::kAttached; |
| panel_state.window_id = attached_browser->session_id().id(); |
| } else { |
| panel_state.kind = mojom::PanelStateKind::kDetached; |
| } |
| return panel_state; |
| } |
| |
| std::optional<int> GetOptionalIntPreference(PrefService* prefs, |
| std::string_view path) { |
| const PrefService::Preference& pref = |
| CHECK_DEREF(prefs->FindPreference(path)); |
| if (pref.IsDefaultValue()) { |
| return std::nullopt; |
| } |
| return pref.GetValue()->GetInt(); |
| } |
| |
| // Get the previous position or none if the window has not been dragged before. |
| std::optional<gfx::Point> GetPreviousPositionFromPrefs(PrefService* prefs) { |
| if (!prefs) { |
| return std::nullopt; |
| } |
| |
| auto x_pos = GetOptionalIntPreference(prefs, prefs::kGlicPreviousPositionX); |
| auto y_pos = GetOptionalIntPreference(prefs, prefs::kGlicPreviousPositionY); |
| |
| if (!x_pos.has_value() || !y_pos.has_value()) { |
| return std::nullopt; |
| } |
| return gfx::Point(x_pos.value(), y_pos.value()); |
| } |
| } // namespace |
| |
| GlicWindowControllerImpl::GlicWindowControllerImpl( |
| Profile* profile, |
| signin::IdentityManager* identity_manager, |
| GlicKeyedService* glic_service, |
| GlicEnabling* enabling) |
| : profile_(profile), |
| host_(profile, nullptr, this, glic_service), |
| window_finder_(std::make_unique<WindowFinder>()), |
| glic_service_(glic_service), |
| enabling_(enabling), |
| id_(base::Uuid::GenerateRandomV4()) { |
| host_manager_ = std::make_unique<HostManager>(profile, GetWeakPtr()); |
| if (window_config_.ShouldResetOnStart()) { |
| previous_position_.reset(); |
| } else { |
| previous_position_ = GetPreviousPositionFromPrefs(profile_->GetPrefs()); |
| } |
| application_hotkey_manager_ = |
| MakeApplicationHotkeyManager(weak_ptr_factory_.GetWeakPtr()); |
| host_.SetDelegate(this); |
| host_observation_.Observe(&host()); |
| } |
| |
| GlicWindowControllerImpl::~GlicWindowControllerImpl() = default; |
| |
| void GlicWindowControllerImpl::WebClientInitializeFailed() { |
| if (state_ == State::kWaitingForGlicToLoad) { |
| // TODO(crbug.com/388328847): The web client failed to initialize. Decide |
| // what the fallback behavior is. Additionally, we probably need some kind |
| // of timeout and/or loading indicator if loading takes too much time. For |
| // now, show the UI anyway, which should be helpful in development. |
| LOG(ERROR) |
| << "Glic web client failed to initialize, it won't work properly."; |
| glic_service_->metrics()->OnGlicWindowOpenInterrupted(); |
| GlicLoadedAndReadyToDisplay(); |
| } |
| } |
| |
| void GlicWindowControllerImpl::LoginPageCommitted() { |
| login_page_committed_ = true; |
| if (state_ == State::kWaitingForGlicToLoad && !host().IsReady()) { |
| // TODO(crbug.com/388328847): Temporarily allow showing the UI when a login |
| // page is reached. |
| glic_service_->metrics()->OnGlicWindowOpenInterrupted(); |
| GlicLoadedAndReadyToDisplay(); |
| } |
| } |
| |
| // Monitoring the glic widget. |
| void GlicWindowControllerImpl::OnWidgetActivationChanged(views::Widget* widget, |
| bool active) { |
| if (IsDetached() && GetGlicWidget() != widget) { |
| return; |
| } |
| if (!active && do_focus_loss_announcement_) { |
| widget->widget_delegate()->SetAccessibleTitle( |
| l10n_util::GetStringUTF16(IDS_GLIC_WINDOW_TITLE)); |
| GetGlicView()->GetViewAccessibility().AnnounceAlert( |
| l10n_util::GetStringFUTF16( |
| IDS_GLIC_WINDOW_FIRST_FOCUS_LOST_ANNOUNCEMENT, |
| LocalHotkeyManager::GetConfigurableAccelerator( |
| LocalHotkeyManager::Hotkey::kFocusToggle) |
| .GetShortcutText())); |
| do_focus_loss_announcement_ = false; |
| } |
| window_activation_callback_list_.Notify(active); |
| } |
| |
| // Monitoring the glic widget. |
| void GlicWindowControllerImpl::OnWidgetDestroyed(views::Widget* widget) { |
| // This is used to handle the case where the native window is closed |
| // directly (e.g., Windows context menu close on the title bar). |
| // Conceptually this should synchronously call Close(), but the Widget |
| // implementation currently does not support this. |
| if (IsDetached() && GetGlicWidget() == widget) { |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&GlicWindowControllerImpl::Close, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| } |
| |
| void GlicWindowControllerImpl::OnWidgetBoundsChanged( |
| views::Widget* widget, |
| const gfx::Rect& new_bounds) { |
| if (window_event_observer_->IsDragging() && !AlwaysDetached()) { |
| // While in a move loop, look for nearby browsers to toggle the drop to |
| // attach indicator. |
| HandleGlicButtonIndicator(); |
| } |
| |
| modal_dialog_host_observers_.Notify( |
| &web_modal::ModalDialogHostObserver::OnPositionRequiresUpdate); |
| } |
| |
| void GlicWindowControllerImpl::OnWidgetUserResizeStarted() { |
| user_resizing_ = true; |
| glic_service_->metrics()->OnWidgetUserResizeStarted(); |
| if (GlicWebClientAccess* client = host().GetPrimaryWebClient()) { |
| client->ManualResizeChanged(true); |
| } |
| } |
| |
| void GlicWindowControllerImpl::OnWidgetUserResizeEnded() { |
| if (!IsDetached()) { |
| // TODO(crbug.com/439745838): Implement for side panel if needed. |
| NOTIMPLEMENTED(); |
| return; |
| } |
| glic_service_->metrics()->OnWidgetUserResizeEnded(); |
| if (GlicWebClientAccess* client = host().GetPrimaryWebClient()) { |
| client->ManualResizeChanged(false); |
| } |
| |
| if (GetGlicView()) { |
| GetGlicView()->UpdatePrimaryDraggableAreaOnResize(); |
| } |
| |
| if (GetGlicWidget()) { |
| glic_size_ = GetGlicWidget()->GetClientAreaBoundsInScreen().size(); |
| SaveWidgetPosition(/*user_modified=*/true); |
| } |
| |
| glic_window_animator_->ResetLastTargetSize(); |
| user_resizing_ = false; |
| } |
| |
| void GlicWindowControllerImpl::OnDisplayMetricsChanged( |
| const display::Display& display, |
| uint32_t changed_metrics) { |
| if (!IsDetached()) { |
| return; |
| } |
| |
| MaybeAdjustSizeForDisplay(/*animate=*/false); |
| window_event_observer_->AdjustPositionIfNeeded(); |
| } |
| |
| void GlicWindowControllerImpl::ShowAfterSignIn(base::WeakPtr<Browser> browser) { |
| Toggle(browser.get(), true, |
| // Prefer the source that triggered the sign-in, but if that's not |
| // available, report it as coming from the sign-in flow. |
| opening_source_.value_or(mojom::InvocationSource::kAfterSignIn)); |
| } |
| |
| void GlicWindowControllerImpl::Toggle(BrowserWindowInterface* bwi, |
| bool prevent_close, |
| mojom::InvocationSource source) { |
| Browser* new_attached_browser = |
| bwi ? bwi->GetBrowserForMigrationOnly() : nullptr; |
| |
| if (!AlwaysDetached()) { |
| ToggleWhenNotAlwaysDetached(new_attached_browser, prevent_close, source); |
| return; |
| } |
| |
| auto maybe_close = [this, prevent_close] { |
| if (!prevent_close) { |
| Close(); |
| } |
| }; |
| |
| // Send a change view request if the current view is different than the |
| // source. |
| // TODO(crbug.com/437140901): The client may not be connected yet. If not, |
| // this request is dropped. |
| MaybeSendViewChangeRequest(source); |
| |
| // If floaty is closed, open floaty |
| if (state_ == State::kClosed) { |
| Show(new_attached_browser, source); |
| return; |
| } |
| |
| #if BUILDFLAG(IS_WIN) |
| // Clicking status tray on Windows makes floaty not active so always close. |
| if (source == mojom::InvocationSource::kOsButton) { |
| Close(); |
| return; |
| } |
| #endif // BUILDFLAG(IS_WIN) |
| |
| // TODO(crbug.com/438164568): Add handling to always close on the second |
| // click of the same source. |
| // If floaty is focused and click is not from the Task Icon or Glic |
| // Button, close it. If floaty is open and the current view matches the |
| // expected view, close it. If floaty is unfocused and open, focus it. |
| if ((IsActive() && (source != mojom::InvocationSource::kActorTaskIcon && |
| source != mojom::InvocationSource::kTopChromeButton)) || |
| (InvocationSourceMatchesCurrentView(source) && |
| !base::FeatureList::IsEnabled(features::kGlicZOrderChanges))) { |
| maybe_close(); |
| } else if (state_ == State::kOpen) { |
| // TODO(crbug.com/404601783): Bring focus to the textbox. |
| GetGlicWidget()->Activate(); |
| GetGlicView()->GetWebContents()->Focus(); |
| } |
| } |
| |
| void GlicWindowControllerImpl::MaybeSendViewChangeRequest( |
| mojom::InvocationSource source) { |
| auto current_view = host().GetPrimaryCurrentView(); |
| if (source == mojom::InvocationSource::kActorTaskIcon && |
| current_view == mojom::CurrentView::kConversation) { |
| MaybeSendActuationViewRequest(); |
| } else if (source == mojom::InvocationSource::kTopChromeButton && |
| current_view == mojom::CurrentView::kActuation) { |
| MaybeSendConversationViewRequest(); |
| } |
| } |
| |
| void GlicWindowControllerImpl::ToggleWhenNotAlwaysDetached( |
| Browser* new_attached_browser, |
| bool prevent_close, |
| mojom::InvocationSource source) { |
| auto maybe_close = [this, prevent_close] { |
| if (!prevent_close) { |
| Close(); |
| } |
| }; |
| |
| // Pressing the button or the hotkey when the window is open, or waiting to |
| // load should close it. The latter is required because otherwise if there |
| // were an error loading the backend (or if it just took a long time) then the |
| // button/hotkey would become unresponsive. |
| // |
| // In the future, when the WebUI can send its status back to the controller |
| // via mojom, we could explicitly restrict the second case to loading, |
| // offline, and error states. |
| if (state_ == State::kOpen || state_ == State::kWaitingForGlicToLoad || |
| state_ == State::kWaitingForSidePanelToShow) { |
| if (new_attached_browser) { |
| if (new_attached_browser == attached_browser_) { |
| // Button was clicked on same browser: close. |
| // There are three ways for this to happen. Normally the glic window |
| // obscures the glic button. Either the user used keyboard navigation to |
| // click the glic button, the user clicked the button early and the |
| // button click was eventually processed asynchronously after the button |
| // was obscured, or the user invokes the glic hotkey while glic is |
| // attached to the active window. |
| maybe_close(); |
| } else { |
| // Button clicked on a different browser: attach to that one. |
| AttachToBrowserAndShow(*new_attached_browser, |
| AttachChangeReason::kInit); |
| } |
| return; |
| } |
| |
| // Everything else in this block handles the case where the user invokes the |
| // hotkey and the most recently used window from the glic profile is not |
| // active. |
| |
| // Already attached? |
| if (attached_browser_) { |
| bool should_close = IsActive(); |
| #if BUILDFLAG(IS_WIN) |
| // On Windows, clicking the system tray icon de-activates the active |
| // window, so fall back to checking if `attached_browser_` is visible for |
| // both the hot key and system tray click cases. |
| should_close = attached_browser_->window() |
| ->GetNativeWindow() |
| ->GetHost() |
| ->GetNativeWindowOcclusionState() == |
| aura::Window::OcclusionState::VISIBLE; |
| #endif // BUILDFLAG(IS_WIN) |
| |
| if (should_close) { |
| // Hotkey when glic active and attached: close. |
| maybe_close(); |
| return; |
| } |
| |
| // Hotkey when glic is inactive and attached: |
| if (attached_browser_->IsActive()) { |
| // Hotkey when glic inactive but attached to active browser: close. |
| // Note: this should not be possible, since if the attached browser is |
| // active, new_attached_browser must not have been null. |
| maybe_close(); |
| } else { |
| // Hotkey when neither attached browser nor glic are active: detach |
| // current side panel. |
| Detach(); |
| } |
| return; |
| } |
| |
| // Hotkey invoked when glic is already detached. |
| maybe_close(); |
| |
| } else if (state_ != State::kClosed) { |
| // Currently in the process of showing the widget, allow that to finish. |
| return; |
| } else { |
| Show(new_attached_browser, source); |
| } |
| } |
| |
| void GlicWindowControllerImpl::FocusIfOpen() { |
| if (!IsShowing() || IsActive()) { |
| return; |
| } |
| |
| if (IsDetached()) { |
| GetGlicWidget()->Activate(); |
| } |
| GetGlicView()->GetWebContents()->Focus(); |
| return; |
| } |
| |
| void GlicWindowControllerImpl::ShowDetachedForTesting() { |
| glic::GlicProfileManager::GetInstance()->SetActiveGlic(glic_service_); |
| Show(nullptr, mojom::InvocationSource::kOsHotkey); |
| } |
| |
| void GlicWindowControllerImpl::SetPreviousPositionForTesting( |
| gfx::Point position) { |
| previous_position_ = position; |
| } |
| |
| Host& GlicWindowControllerImpl::host() { |
| return host_; |
| } |
| |
| const InstanceId& GlicWindowControllerImpl::id() const { |
| return id_; |
| } |
| |
| HostManager& GlicWindowControllerImpl::host_manager() { |
| return *host_manager_; |
| } |
| |
| std::vector<GlicInstance*> GlicWindowControllerImpl::GetInstances() { |
| return {this}; |
| } |
| |
| GlicInstance* GlicWindowControllerImpl::GetInstanceForTab( |
| tabs::TabInterface* tab) { |
| return this; |
| } |
| |
| bool GlicWindowControllerImpl::FindInstanceFromIdAndBindToTab( |
| const InstanceId& instance_id, |
| tabs::TabInterface* tab_to_bind) { |
| NOTIMPLEMENTED(); |
| return false; |
| } |
| |
| bool GlicWindowControllerImpl::BeforeViewCreated( |
| Browser* browser, |
| mojom::InvocationSource source) { |
| if (state_ == State::kWaitingForSidePanelToShow) { |
| return false; |
| } |
| // At this point State must be kClosed, and all glic window state must be |
| // unset. |
| CHECK(!attached_browser_); |
| opening_source_ = source; |
| if (!glic_service_->GetAuthController().CheckAuthBeforeShowSync( |
| base::BindOnce(&GlicWindowControllerImpl::ShowAfterSignIn, |
| weak_ptr_factory_.GetWeakPtr(), |
| browser ? browser->AsWeakPtr() : nullptr))) { |
| return false; |
| } |
| |
| SetWindowState(State::kWaitingForGlicToLoad); |
| |
| glic_service_->metrics()->OnGlicWindowStartedOpening(/*attached=*/browser, |
| source); |
| glic_service_->GetAuthController().OnGlicWindowOpened(); |
| |
| MaybeResetPanelPostionOnShow(source); |
| |
| host().CreateContents(/*initially_hidden=*/false); |
| host().NotifyWindowIntentToShow(); |
| |
| glic_panel_hotkey_manager_ = |
| MakeGlicWindowHotkeyManager(weak_ptr_factory_.GetWeakPtr()); |
| return true; |
| } |
| |
| void GlicWindowControllerImpl::AfterViewShown() { |
| glic_panel_hotkey_manager_->InitializeAccelerators(); |
| |
| // Notify the web client that the panel will open, and wait for the response |
| // to actually show the window. Note that we have to call |
| // `NotifyIfPanelStateChanged()` first, so that the host will receive the |
| // correct panel state. |
| NotifyIfPanelStateChanged(); |
| host().PanelWillOpen(opening_source_.value(), {}); |
| |
| if (login_page_committed_) { |
| // This indicates that we've warmed the web client and it has hit a login |
| // page. See LoginPageCommitted. |
| GlicLoadedAndReadyToDisplay(); |
| } else if (IsDetached()) { |
| // This adds dragging functionality to special case panels (e.g. error, |
| // offline, loading). |
| window_event_observer_->SetDraggingAreasAndWatchForMouseEvents(); |
| } |
| } |
| |
| void GlicWindowControllerImpl::Show(Browser* browser, |
| mojom::InvocationSource source) { |
| if (!BeforeViewCreated(browser, source)) { |
| return; |
| } |
| if (browser && !AlwaysDetached()) { |
| AttachToBrowserAndShow(*browser, AttachChangeReason::kInit); |
| } else { |
| SetupAndShowGlicWidget(browser); |
| AfterViewShown(); |
| } |
| } |
| |
| std::unique_ptr<views::View> GlicWindowControllerImpl::CreateViewForSidePanel( |
| tabs::TabInterface& tab) { |
| // GetBrowserForMigrationOnly() is a stop-gap until the rest of the code in |
| // GlicWindowController is updated to use BrowserWindowInterface instead of |
| // Browser |
| auto* browser = tab.GetBrowserWindowInterface()->GetBrowserForMigrationOnly(); |
| // TODO: Add Invocation source for toolbar button |
| if (BeforeViewCreated(browser, mojom::InvocationSource::kThreeDotsMenu) && |
| browser) { |
| AttachToBrowser(*browser, AttachChangeReason::kInit); |
| } |
| auto glic_view = |
| std::make_unique<GlicView>(profile_, GlicWidget::GetInitialSize(), |
| glic_panel_hotkey_manager_->GetWeakPtr()); |
| glic_view->SetWebContents(host().webui_contents()); |
| glic_view->UpdateBackgroundColor(); |
| glic_view_ = glic_view.get(); |
| SetWindowState(GlicWindowController::State::kWaitingForSidePanelToShow); |
| return glic_view; |
| } |
| |
| void GlicWindowControllerImpl::SetupAndShowGlicWidget(Browser* browser) { |
| auto initial_bounds = GetInitialBounds(browser); |
| glic_widget_ = GlicWidget::Create(profile_, initial_bounds, |
| glic_panel_hotkey_manager_->GetWeakPtr(), |
| user_resizable_); |
| glic_widget_observation_.Observe(glic_widget_.get()); |
| SetupGlicWidgetAccessibilityText(); |
| |
| SetGlicWindowToFloatingMode(true); |
| |
| glic_window_animator_ = std::make_unique<GlicWindowAnimator>( |
| glic_widget_->GetWeakPtr(), |
| base::BindRepeating(&GlicWindowControllerImpl::MaybeSetWidgetCanResize, |
| weak_ptr_factory_.GetWeakPtr())); |
| |
| window_event_observer_ = std::make_unique<GlicWindowEventObserver>( |
| glic_widget_->GetWeakPtr(), this); |
| |
| glic_widget_->Show(); |
| |
| // This is needed in case of theme difference between OS and chrome. |
| GetGlicWidget()->ThemeChanged(); |
| |
| // This is used to handle the case where the native window is closed |
| // directly (e.g., Windows context menu close on the title bar). It fixes the |
| // bug where the window position was not restored after closing with the |
| // context menu close menu item. |
| GetGlicWidget()->MakeCloseSynchronous(base::BindOnce( |
| &GlicWindowControllerImpl::CloseWithReason, base::Unretained(this))); |
| |
| // Immediately hook up the WebView to the WebContents. |
| GetGlicView()->SetWebContents(host().webui_contents()); |
| GetGlicView()->UpdateBackgroundColor(); |
| |
| // TODO(crbug.com/439745838): This be needed for sidepanel. |
| // Add capability to show web modal dialogs (e.g. Data Controls Dialogs for |
| // enterprise users) via constrained_window APIs. |
| web_modal::WebContentsModalDialogManager::CreateForWebContents( |
| host().webui_contents()); |
| web_modal::WebContentsModalDialogManager::FromWebContents( |
| host().webui_contents()) |
| ->SetDelegate(this); |
| |
| std::optional<display::Display> display = |
| GetGlicWidget()->GetNearestDisplay(); |
| glic_service_->metrics()->OnGlicWindowShown( |
| browser, display, GetGlicWidget()->GetWindowBoundsInScreen()); |
| } |
| |
| void GlicWindowControllerImpl::SetupGlicWidgetAccessibilityText() { |
| auto* widget_delegate = glic_widget_->widget_delegate(); |
| if (opening_source_ == mojom::InvocationSource::kFre) { |
| widget_delegate->SetAccessibleTitle(l10n_util::GetStringFUTF16( |
| IDS_GLIC_WINDOW_TITLE_FIRST_LOAD, |
| LocalHotkeyManager::GetConfigurableAccelerator( |
| LocalHotkeyManager::Hotkey::kFocusToggle) |
| .GetShortcutText())); |
| do_focus_loss_announcement_ = true; |
| } else { |
| widget_delegate->SetAccessibleTitle( |
| l10n_util::GetStringUTF16(IDS_GLIC_WINDOW_TITLE)); |
| } |
| } |
| |
| void GlicWindowControllerImpl::SetGlicWindowToFloatingMode(bool floating) { |
| GetGlicWidget()->SetZOrderLevel(floating ? ui::ZOrderLevel::kFloatingWindow |
| : ui::ZOrderLevel::kNormal); |
| #if BUILDFLAG(IS_MAC) |
| GetGlicWidget()->SetActivationIndependence(floating); |
| GetGlicWidget()->SetVisibleOnAllWorkspaces(floating); |
| GetGlicWidget()->SetCanAppearInExistingFullscreenSpaces(floating); |
| #endif |
| } |
| |
| gfx::Rect GlicWindowControllerImpl::GetInitialBounds(Browser* browser) { |
| gfx::Size target_size = GlicWidget::ClampSize(glic_size_, GetGlicWidget()); |
| |
| // Reset previous position if it results in an invalid location. |
| if (previous_position_.has_value() && |
| !GlicWidget::IsWidgetLocationAllowed( |
| {previous_position_.value(), target_size})) { |
| previous_position_.reset(); |
| } |
| // Use the previous position if there is one. |
| if (previous_position_.has_value()) { |
| return {previous_position_.value(), target_size}; |
| } |
| |
| return GlicWidget::GetInitialBounds(browser, target_size); |
| } |
| |
| void GlicWindowControllerImpl::MaybeResetPanelPostionOnShow( |
| mojom::InvocationSource source) { |
| if (source == mojom::InvocationSource::kTopChromeButton && |
| window_config_.ShouldResetOnOpen()) { |
| previous_position_.reset(); |
| base::RecordAction( |
| base::UserMetricsAction("Glic.Widget.ResetPositionOnOpen")); |
| } |
| if (window_config_.ShouldResetOnNewSession()) { |
| previous_position_.reset(); |
| } |
| if (window_config_.ShouldResetSizeAndLocationOnShow()) { |
| previous_position_.reset(); |
| gfx::Size initial_size = GlicWidget::GetInitialSize(); |
| // Keep the old height if it is larger than the initial size. |
| if (glic_size_.has_value() && |
| glic_size_->height() > initial_size.height()) { |
| initial_size.set_height((glic_size_->height())); |
| } |
| glic_size_ = initial_size; |
| } |
| window_config_.SetLastOpenTime(); |
| } |
| |
| void GlicWindowControllerImpl::ClientReadyToShow( |
| const mojom::OpenPanelInfo& open_info) { |
| DVLOG(1) << "Glic client ready to show " << open_info.web_client_mode; |
| glic_service_->metrics()->OnGlicWindowOpenAndReady(); |
| if (open_info.panelSize.has_value()) { |
| Resize(*open_info.panelSize, open_info.resizeDuration, base::DoNothing()); |
| } |
| EnableDragResize(open_info.can_user_resize); |
| |
| if (state_ == State::kWaitingForGlicToLoad) { |
| GlicLoadedAndReadyToDisplay(); |
| } |
| } |
| |
| void GlicWindowControllerImpl::OnViewChanged(mojom::CurrentView view) { |
| state_change_callback_list_.Notify(IsShowing(), view); |
| } |
| |
| void GlicWindowControllerImpl::ContextAccessIndicatorChanged(bool enabled) { |
| glic_service_->SetContextAccessIndicator(enabled && IsShowing()); |
| } |
| |
| void GlicWindowControllerImpl::GlicLoadedAndReadyToDisplay() { |
| login_page_committed_ = false; |
| if (state_ == State::kClosed || state_ == State::kOpen) { |
| return; |
| } |
| |
| // Update the background color after showing the webview so the transition |
| // isn't visible. This will be the widget background color the user sees next |
| // time. |
| GetGlicView()->UpdateBackgroundColor(); |
| |
| // In the case that the open animation was skipped, the web view should still |
| // be visible now. |
| SetWindowState(State::kOpen); |
| |
| // Whenever the glic window is shown, it should have focus. The following line |
| // of code appears to be necessary but not sufficient and there are still some |
| // edge cases. |
| // TODO(crbug.com/390637019): Fully fix and remove this comment. |
| GetGlicView()->GetWebContents()->Focus(); |
| |
| window_event_observer_->SetDraggingAreasAndWatchForMouseEvents(); |
| NotifyIfPanelStateChanged(); |
| } |
| |
| GlicView* GlicWindowControllerImpl::GetGlicView() const { |
| if (!IsShowing()) { |
| return nullptr; |
| } |
| |
| if (glic_view_) { |
| return glic_view_; |
| } |
| if (IsDetached()) { |
| return GetGlicWidget()->GetGlicView(); |
| } |
| return nullptr; |
| } |
| |
| base::WeakPtr<views::View> GlicWindowControllerImpl::GetView() { |
| if (auto* view = GetGlicView()) { |
| return view->GetWeakPtr(); |
| } |
| return nullptr; |
| } |
| |
| GlicWindowAnimator* GlicWindowControllerImpl::window_animator() { |
| return glic_window_animator_.get(); |
| } |
| |
| GlicWidget* GlicWindowControllerImpl::GetGlicWidget() const { |
| return glic_widget_.get(); |
| } |
| |
| void GlicWindowControllerImpl::AttachedBrowserDidClose( |
| BrowserWindowInterface* browser) { |
| Close(); |
| } |
| |
| void GlicWindowControllerImpl::Attach() { |
| if (!GetGlicWidget()) { |
| return; |
| } |
| |
| BrowserWindowInterface* browser = glic::FindBrowserForAttachment(profile_); |
| if (!browser) { |
| return; |
| } |
| if (AlwaysDetached()) { |
| return; |
| } |
| AttachToBrowserAndShow(*browser->GetBrowserForMigrationOnly(), |
| AttachChangeReason::kMenu); |
| } |
| |
| void GlicWindowControllerImpl::Detach() { |
| if (state_ != State::kOpen || !attached_browser_ || AlwaysDetached()) { |
| return; |
| } |
| SetWindowState(State::kDetaching); |
| |
| // Close the existing side panel. |
| auto current_browser = attached_browser_; |
| ResetAndHidePanel(); |
| |
| // Open the panel detached. |
| SetupAndShowGlicWidget(current_browser); |
| window_event_observer_->SetDraggingAreasAndWatchForMouseEvents(); |
| SetWindowState(State::kOpen); |
| NotifyIfPanelStateChanged(); |
| } |
| |
| void GlicWindowControllerImpl::AttachToBrowser(Browser& browser, |
| AttachChangeReason reason) { |
| CHECK(!AlwaysDetached()); |
| glic_service_->metrics()->OnAttachedToBrowser(reason); |
| |
| ResetAndHidePanel(); |
| |
| attached_browser_ = &browser; |
| user_resizing_ = true; |
| browser_close_subscription_ = attached_browser_->RegisterBrowserDidClose( |
| base::BindRepeating(&GlicWindowControllerImpl::AttachedBrowserDidClose, |
| base::Unretained(this))); |
| } |
| |
| void GlicWindowControllerImpl::AttachToBrowserAndShow( |
| Browser& browser, |
| AttachChangeReason reason) { |
| AttachToBrowser(browser, reason); |
| SetWindowState(GlicWindowController::State::kWaitingForSidePanelToShow); |
| |
| auto* side_panel_coordinator = browser.GetFeatures().side_panel_coordinator(); |
| side_panel_coordinator->Show(SidePanelEntry::Id::kGlic); |
| } |
| |
| void GlicWindowControllerImpl::SidePanelShown(BrowserWindowInterface* browser) { |
| SetWindowState(State::kOpen); |
| NotifyIfPanelStateChanged(); |
| |
| // Trigger custom event for testing. |
| views::ElementTrackerViews::GetInstance()->NotifyCustomEvent( |
| kGlicWidgetAttached, GlicButton::FromBrowser(browser)); |
| AfterViewShown(); |
| } |
| |
| void GlicWindowControllerImpl::Resize(const gfx::Size& size, |
| base::TimeDelta duration, |
| base::OnceClosure callback) { |
| glic_size_ = size; |
| glic_service_->metrics()->OnGlicWindowResize(); |
| |
| const bool in_resizable_state = |
| IsDetached() && |
| (state_ == State::kOpen || state_ == State::kWaitingForGlicToLoad); |
| |
| // TODO(https://crbug.com/379164689): Drive resize animations for error states |
| // from the browser. For now, we allow animations during the waiting state. |
| // TODO(https://crbug.com/392668958): If the widget is ready and asks for a |
| // resize before the opening animation is finished, we will stop the current |
| // animation and resize to the final size. Investigate a smoother way to |
| // animate this transition. |
| if (in_resizable_state && !user_resizing_) { |
| glic_window_animator_->AnimateSize( |
| GlicWidget::ClampSize(glic_size_, GetGlicWidget()), duration, |
| std::move(callback)); |
| } else { |
| // If the glic window is closed, or the widget isn't ready (e.g. because |
| // it's currently still animating closed) immediately post the callback. |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, std::move(callback)); |
| } |
| } |
| |
| void GlicWindowControllerImpl::EnableDragResize(bool enabled) { |
| user_resizable_ = enabled; |
| if (!IsDetached()) { |
| return; |
| } |
| |
| if (base::FeatureList::IsEnabled(features::kGlicZOrderChanges)) { |
| // Drag-resizability implies text mode, which isn't floating, while |
| // non-resizability implies audio mode, which is floating. |
| SetGlicWindowToFloatingMode(!enabled); |
| } |
| |
| MaybeSetWidgetCanResize(); |
| GetGlicView()->UpdateBackgroundColor(); |
| glic_window_animator_->MaybeAnimateToTargetSize(); |
| } |
| |
| void GlicWindowControllerImpl::MaybeSetWidgetCanResize() { |
| if (!IsDetached()) { |
| return; |
| } |
| if (GetGlicWidget()->widget_delegate()->CanResize() == user_resizable_ || |
| glic_window_animator_->IsAnimating()) { |
| // If the resize state is already correct or the widget is animating do not |
| // update the resize state. |
| return; |
| } |
| |
| #if BUILDFLAG(IS_WIN) |
| // On Windows when resize is enabled there is an invisible border added |
| // around the client area. We need to make the widget larger or smaller to |
| // keep the visible client area the same size. |
| gfx::Rect previous_client_bounds = |
| GetGlicWidget()->GetClientAreaBoundsInScreen(); |
| #endif // BUILDFLAG(IS_WIN) |
| |
| // Update resize state on widget delegate. |
| GetGlicWidget()->widget_delegate()->SetCanResize(user_resizable_); |
| |
| #if BUILDFLAG(IS_WIN) |
| if (user_resizable_) { |
| // Resizable so the widget area is larger than the client area. |
| gfx::Rect new_widget_bounds = |
| GetGlicWidget()->VisibleToWidgetBounds(previous_client_bounds); |
| GetGlicWidget()->SetBoundsConstrained(new_widget_bounds); |
| } else { |
| // Not resizable so the client and widget areas are the same. |
| GetGlicWidget()->SetBoundsConstrained(previous_client_bounds); |
| } |
| #endif // BUILDFLAG(IS_WIN) |
| } |
| |
| gfx::Size GlicWindowControllerImpl::GetPanelSize() { |
| if (IsDetached()) { |
| return GetGlicWidget()->GetSize(); |
| } |
| BrowserView* browser_view = |
| BrowserView::GetBrowserViewForBrowser(attached_browser_); |
| CHECK(browser_view->contents_height_side_panel()); |
| // This returns the size of the entire side panel (including content, |
| // heading, and surrounding padding). |
| return browser_view->contents_height_side_panel()->size(); |
| } |
| |
| void GlicWindowControllerImpl::SetDraggableAreas( |
| const std::vector<gfx::Rect>& draggable_areas) { |
| GlicView* glic_view = GetGlicView(); |
| if (!glic_view) { |
| return; |
| } |
| |
| glic_view->SetDraggableAreas(draggable_areas); |
| } |
| |
| void GlicWindowControllerImpl::SetMinimumWidgetSize(const gfx::Size& size) { |
| if (!IsDetached()) { |
| return; |
| } |
| |
| GetGlicWidget()->SetMinimumSize(size); |
| } |
| |
| void GlicWindowControllerImpl::CloseWithReason( |
| views::Widget::ClosedReason reason) { |
| Close(); |
| } |
| |
| bool GlicWindowControllerImpl::ActivateBrowser() { |
| if (IsAttached()) { |
| attached_browser()->window()->Activate(); |
| return true; |
| } |
| |
| if (auto* const last_active_bwi = |
| GetLastActiveBrowserWindowInterfaceWithAnyProfile()) { |
| last_active_bwi->GetWindow()->Activate(); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void GlicWindowControllerImpl::Close() { |
| if (state_ == State::kClosed || state_ == State::kDetaching) { |
| return; |
| } |
| window_config_.SetLastCloseTime(); |
| |
| if (IsDetached()) { |
| std::optional<display::Display> display = |
| GetGlicWidget()->GetNearestDisplay(); |
| BrowserWindowInterface* const last_active_bwi = |
| GetLastActiveBrowserWindowInterfaceWithAnyProfile(); |
| Browser* const last_active_browser = |
| last_active_bwi ? last_active_bwi->GetBrowserForMigrationOnly() |
| : nullptr; |
| glic_service_->metrics()->OnGlicWindowClose( |
| last_active_browser, display, |
| GetGlicWidget()->GetWindowBoundsInScreen()); |
| } |
| base::UmaHistogramEnumeration("Glic.PanelWebUiState.FinishState2", |
| host().GetPrimaryWebUiState()); |
| |
| ResetAndHidePanel(); |
| |
| SetWindowState(State::kClosed); |
| glic_panel_hotkey_manager_.reset(); |
| user_resizing_ = false; |
| window_activation_callback_list_.Notify(false); |
| NotifyIfPanelStateChanged(); |
| |
| host().PanelWasClosed(); |
| if (base::FeatureList::IsEnabled(features::kGlicUnloadOnClose)) { |
| host().Shutdown(); |
| } |
| } |
| |
| void GlicWindowControllerImpl::ClosePanel() { |
| Close(); |
| if (screenshot_capturer_) { |
| screenshot_capturer_->CloseScreenPicker(); |
| } |
| } |
| |
| void GlicWindowControllerImpl::ResetAndHidePanel() { |
| if (IsDetached()) { |
| SaveWidgetPosition(/*user_modified=*/false); |
| |
| modal_dialog_host_observers_.Notify( |
| &web_modal::ModalDialogHostObserver::OnHostDestroying); |
| web_modal::WebContentsModalDialogManager::FromWebContents( |
| host().webui_contents()) |
| ->SetDelegate(nullptr); |
| } else if (IsAttached()) { |
| // Closing the side panel destroys its WebView which hides its webcontents. |
| // This creates a race where the webcontents might be hidden when showing |
| // again after the side panel closes. Prevent this by unsetting the |
| // webcontents first. |
| if (glic_view_) { |
| glic_view_->SetWebContents(nullptr); |
| } |
| attached_browser_->GetFeatures().side_panel_coordinator()->Close(); |
| } |
| |
| // The following state is always safe to reset regardless of if the panel is |
| // detached, attached or currently closed. |
| |
| // Floating Panel State |
| window_event_observer_.reset(); |
| glic_window_animator_.reset(); |
| glic_widget_observation_.Reset(); |
| glic_widget_.reset(); |
| scoped_glic_button_indicator_.reset(); |
| |
| // Attached Side Panel State. |
| attached_browser_ = nullptr; |
| glic_view_ = nullptr; |
| browser_close_subscription_.reset(); |
| } |
| |
| void GlicWindowControllerImpl::SaveWidgetPosition(bool user_modified) { |
| if (!IsDetached() || !GetGlicWidget()->IsVisible()) { |
| return; |
| } |
| if (window_config_.ShouldSetPostionOnDrag() && !user_modified && |
| !previous_position_.has_value()) { |
| profile_->GetPrefs()->ClearPref(prefs::kGlicPreviousPositionX); |
| profile_->GetPrefs()->ClearPref(prefs::kGlicPreviousPositionY); |
| return; |
| } |
| previous_position_ = GetGlicWidget()->GetClientAreaBoundsInScreen().origin(); |
| profile_->GetPrefs()->SetInteger(prefs::kGlicPreviousPositionX, |
| previous_position_->x()); |
| profile_->GetPrefs()->SetInteger(prefs::kGlicPreviousPositionY, |
| previous_position_->y()); |
| } |
| |
| void GlicWindowControllerImpl::ShowTitleBarContextMenuAt(gfx::Point event_loc) { |
| #if BUILDFLAG(IS_WIN) |
| views::View::ConvertPointToScreen(GetGlicView(), &event_loc); |
| event_loc = display::win::GetScreenWin()->DIPToScreenPoint(event_loc); |
| views::ShowSystemMenuAtScreenPixelLocation(views::HWNDForView(GetGlicView()), |
| event_loc); |
| #endif // BUILDFLAG(IS_WIN) |
| } |
| |
| mojom::PanelState GlicWindowControllerImpl::GetPanelState() { |
| return panel_state_; |
| } |
| |
| mojom::PanelState GlicWindowControllerImpl::GetGlobalPanelState() { |
| return panel_state_; |
| } |
| |
| void GlicWindowControllerImpl::OnDragComplete() { |
| if (AlwaysDetached()) { |
| // Do not handle attachment. |
| return; |
| } |
| BrowserWindowInterface* browser = FindBrowserForAttachment(); |
| // No browser within attachment range. |
| if (!browser) { |
| return; |
| } |
| // Attach to the found browser. |
| AttachToBrowser(*browser->GetBrowserForMigrationOnly(), |
| AttachChangeReason::kDrag); |
| } |
| |
| void GlicWindowControllerImpl::HandleGlicButtonIndicator() { |
| BrowserWindowInterface* browser = FindBrowserForAttachment(); |
| // No browser within attachment range so reset indicators |
| if (!browser) { |
| scoped_glic_button_indicator_.reset(); |
| return; |
| } |
| GlicButton* glic_button = GlicButton::FromBrowser(browser); |
| // If there isn't an existing scoped indicator for this button, create one. |
| if (!scoped_glic_button_indicator_ || |
| scoped_glic_button_indicator_->GetGlicButton() != glic_button) { |
| // Bring the browser to the front. |
| browser->GetBrowserForMigrationOnly() |
| ->GetBrowserView() |
| .GetWidget() |
| ->Activate(); |
| scoped_glic_button_indicator_ = |
| std::make_unique<ScopedGlicButtonIndicator>(glic_button); |
| } |
| } |
| |
| BrowserWindowInterface* GlicWindowControllerImpl::FindBrowserForAttachment() { |
| // The profile must have started off as Glic enabled since a Glic widget is |
| // open but it may have been disabled at runtime by policy. In this edge-case, |
| // prevent reattaching back to a window (as it no longer has a GlicButton). |
| if (!GlicEnabling::IsEnabledForProfile(profile_)) { |
| return nullptr; |
| } |
| if (!IsDetached()) { |
| return nullptr; |
| } |
| |
| gfx::Point glic_top_right = |
| GetGlicWidget()->GetWindowBoundsInScreen().top_right(); |
| // Loops through all browsers in activation order with the latest accessed |
| // browser first. |
| BrowserWindowInterface* browser_for_attachment = nullptr; |
| ForEachCurrentBrowserWindowInterfaceOrderedByActivation( |
| [&](BrowserWindowInterface* browser) { |
| if (!IsBrowserGlicAttachable(profile_, browser)) { |
| return true; // continue iterating |
| } |
| |
| auto* tab_strip_view = browser->GetBrowserForMigrationOnly() |
| ->GetBrowserView() |
| .tab_strip_view(); |
| CHECK(tab_strip_view); |
| |
| // If the profile is enabled, the Glic button must be available. |
| glic::GlicButton* glic_button = |
| BrowserElementsViews::From(browser)->GetViewAs<glic::GlicButton>( |
| kGlicButtonElementId); |
| CHECK(glic_button); |
| |
| // Define attachment zone as the right of the tab strip. It either is |
| // the width of the widget or 1/3 of the tab strip, whichever is |
| // smaller. |
| gfx::Rect attachment_zone = tab_strip_view->GetBoundsInScreen(); |
| int width = std::min(attachment_zone.width() / 3, |
| GlicWidget::GetInitialSize().width()); |
| attachment_zone.SetByBounds(attachment_zone.right() - width, |
| attachment_zone.y() - kAttachmentBuffer, |
| attachment_zone.right() + kAttachmentBuffer, |
| attachment_zone.bottom()); |
| |
| // If both the left center of the attachment zone and glic button right |
| // center are occluded, don't consider for attachment. |
| if (IsBrowserOccludedAtPoint(browser, attachment_zone.left_center()) && |
| IsBrowserOccludedAtPoint( |
| browser, glic_button->GetBoundsInScreen().right_center())) { |
| return true; // continue iterating |
| } |
| |
| if (attachment_zone.Contains(glic_top_right)) { |
| browser_for_attachment = browser; |
| return false; // stop iterating |
| } |
| |
| return true; // continue iterating |
| }); |
| |
| return browser_for_attachment; |
| } |
| |
| void GlicWindowControllerImpl::AddStateObserver(StateObserver* observer) { |
| state_observers_.AddObserver(observer); |
| } |
| |
| void GlicWindowControllerImpl::RemoveStateObserver(StateObserver* observer) { |
| state_observers_.RemoveObserver(observer); |
| } |
| |
| void GlicWindowControllerImpl::AddGlobalStateObserver( |
| PanelStateObserver* observer) { |
| AddStateObserver(observer); |
| } |
| |
| void GlicWindowControllerImpl::RemoveGlobalStateObserver( |
| PanelStateObserver* observer) { |
| RemoveStateObserver(observer); |
| } |
| |
| void GlicWindowControllerImpl::NotifyIfPanelStateChanged() { |
| auto new_state = ComputePanelState(); |
| if (new_state != panel_state_) { |
| panel_state_ = new_state; |
| state_observers_.Notify(&StateObserver::PanelStateChanged, panel_state_, |
| PanelStateContext{ |
| .attached_browser = attached_browser_, |
| .glic_widget = GetGlicWidget(), |
| }); |
| } |
| } |
| |
| mojom::PanelState GlicWindowControllerImpl::ComputePanelState() const { |
| return CreatePanelState(IsShowing(), attached_browser_); |
| } |
| |
| bool GlicWindowControllerImpl::IsActive() { |
| if (IsAttached()) { |
| auto* browser_view = |
| BrowserView::GetBrowserViewForBrowser(attached_browser_); |
| DCHECK(browser_view->contents_height_side_panel()); |
| return browser_view->contents_height_side_panel()->HasFocus(); |
| } |
| return IsDetached() && GetGlicWidget()->IsActive(); |
| } |
| |
| bool GlicWindowControllerImpl::HasFocus() { |
| return IsActive(); |
| } |
| |
| bool GlicWindowControllerImpl::IsShowing() const { |
| return !(state_ == State::kClosed); |
| } |
| |
| void GlicWindowControllerImpl::SwitchConversation( |
| glic::mojom::ConversationInfoPtr info, |
| mojom::WebClientHandler::SwitchConversationCallback callback) { |
| std::move(callback).Run(mojom::SwitchConversationErrorReason::kUnknown); |
| } |
| |
| void GlicWindowControllerImpl::CaptureScreenshot( |
| glic::mojom::WebClientHandler::CaptureScreenshotCallback callback) { |
| if (!GetGlicWidget()) { |
| std::move(callback).Run(mojom::CaptureScreenshotResult::NewErrorReason( |
| mojom::CaptureScreenshotErrorReason::kUnknown)); |
| return; |
| } |
| if (!screenshot_capturer_) { |
| screenshot_capturer_ = std::make_unique<GlicScreenshotCapturer>(); |
| } |
| screenshot_capturer_->CaptureScreenshot(GetGlicWidget()->GetNativeWindow(), |
| std::move(callback)); |
| } |
| |
| bool GlicWindowControllerImpl::IsAttached() const { |
| return IsShowing() && attached_browser_; |
| } |
| |
| bool GlicWindowControllerImpl::IsAttached() { |
| return const_cast<const GlicWindowControllerImpl*>(this)->IsAttached(); |
| } |
| |
| bool GlicWindowControllerImpl::IsDetached() const { |
| return IsShowing() && glic_widget_; |
| } |
| |
| base::CallbackListSubscription |
| GlicWindowControllerImpl::AddWindowActivationChangedCallback( |
| WindowActivationChangedCallback callback) { |
| return window_activation_callback_list_.Add(std::move(callback)); |
| } |
| |
| void GlicWindowControllerImpl::Preload() { |
| if (!host().contents_container()) { |
| host().CreateContents(/*initially_hidden=*/true); |
| host().webui_contents()->Resize(GetInitialBounds(nullptr)); |
| } |
| } |
| |
| void GlicWindowControllerImpl::Reload( |
| content::RenderFrameHost* render_frame_host) { |
| host().Reload(render_frame_host); |
| } |
| |
| bool GlicWindowControllerImpl::IsWarmed() const { |
| return const_cast<Host&>(host_).contents_container(); |
| } |
| |
| base::WeakPtr<GlicWindowControllerInterface> |
| GlicWindowControllerImpl::GetWeakPtr() { |
| return weak_ptr_factory_.GetWeakPtr(); |
| } |
| |
| void GlicWindowControllerImpl::Shutdown() { |
| // Hide first, then clean up (but do not animate). |
| Close(); |
| window_activation_callback_list_.Notify(false); |
| } |
| |
| bool GlicWindowControllerImpl::IsBrowserOccludedAtPoint( |
| BrowserWindowInterface* browser, |
| gfx::Point point) { |
| std::set<gfx::NativeWindow> exclude = { |
| GetGlicView()->GetWidget()->GetNativeWindow()}; |
| gfx::NativeWindow window = |
| window_finder_->GetLocalProcessWindowAtPoint(point, exclude); |
| if (browser->GetBrowserForMigrationOnly() |
| ->GetBrowserView() |
| .GetWidget() |
| ->GetNativeWindow() != window) { |
| return true; |
| } |
| return false; |
| } |
| |
| void GlicWindowControllerImpl::MaybeAdjustSizeForDisplay(bool animate) { |
| if (!IsDetached()) { |
| return; |
| } |
| const auto target_size = GlicWidget::ClampSize(glic_size_, GetGlicWidget()); |
| if (target_size != glic_window_animator_->GetCurrentTargetBounds().size()) { |
| glic_window_animator_->AnimateSize( |
| target_size, animate ? kAnimationDuration : base::Milliseconds(0), |
| base::DoNothing()); |
| } |
| } |
| |
| base::CallbackListSubscription GlicWindowControllerImpl::RegisterStateChange( |
| StateChangeCallback callback) { |
| return state_change_callback_list_.Add(std::move(callback)); |
| } |
| |
| base::CallbackListSubscription |
| GlicWindowControllerImpl::AddActiveInstanceChangedCallbackAndNotifyImmediately( |
| ActiveInstanceChangedCallback callback) { |
| NOTIMPLEMENTED(); |
| return base::CallbackListSubscription(); |
| } |
| |
| void GlicWindowControllerImpl::SetWindowState(State new_state) { |
| if (state_ == new_state) { |
| return; |
| } |
| state_ = new_state; |
| |
| glic_service_->SetContextAccessIndicator( |
| IsShowing() && host().IsContextAccessIndicatorEnabled()); |
| |
| if (auto* actor_keyed_service = actor::ActorKeyedService::Get(profile_)) { |
| // Show toast if floaty is closed. |
| BrowserWindowInterface* const last_active_bwi = |
| GetLastActiveBrowserWindowInterfaceWithAnyProfile(); |
| if (state_ == State::kClosed) { |
| actor_keyed_service->GetActorUiStateManager()->MaybeShowToast( |
| last_active_bwi); |
| } |
| } |
| |
| state_change_callback_list_.Notify(IsShowing(), |
| host_.GetPrimaryCurrentView()); |
| |
| if (IsWindowOpenAndReady()) { |
| glic_service_->metrics()->OnGlicWindowOpenAndReady(); |
| } |
| } |
| |
| bool GlicWindowControllerImpl::IsWindowOpenAndReady() { |
| return host().IsReady() && state_ == State::kOpen; |
| } |
| |
| GlicWindowController::State GlicWindowControllerImpl::state() const { |
| return state_; |
| } |
| |
| Profile* GlicWindowControllerImpl::profile() { |
| return profile_; |
| } |
| |
| GlicWindowAnimator* GlicWindowControllerImpl::GetWindowAnimatorForTesting() { |
| return glic_window_animator_.get(); |
| } |
| |
| // Return the Browser to which the panel is attached, or null if detached. |
| Browser* GlicWindowControllerImpl::attached_browser() { |
| return attached_browser_; |
| } |
| |
| web_modal::WebContentsModalDialogHost* |
| GlicWindowControllerImpl::GetWebContentsModalDialogHost( |
| content::WebContents* web_contents) { |
| return this; |
| } |
| |
| gfx::Size GlicWindowControllerImpl::GetMaximumDialogSize() { |
| if (IsDetached()) { |
| return GetGlicWidget()->GetClientAreaBoundsInScreen().size(); |
| } else if (IsAttached()) { |
| // TODO(crbug.com/439745838): Get side panel height |
| NOTIMPLEMENTED(); |
| } |
| return gfx::Size(); |
| } |
| |
| gfx::NativeView GlicWindowControllerImpl::GetHostView() const { |
| if (IsDetached()) { |
| return GetGlicWidget()->GetNativeView(); |
| } else if (IsAttached()) { |
| // TODO(crbug.com/439745838): Maybe get Native View for side panel if |
| // needed. |
| NOTIMPLEMENTED(); |
| } |
| return gfx::NativeView(); |
| } |
| |
| gfx::Point GlicWindowControllerImpl::GetDialogPosition( |
| const gfx::Size& dialog_size) { |
| if (IsDetached()) { |
| gfx::Rect client_area_bounds = |
| GetGlicWidget()->GetClientAreaBoundsInScreen(); |
| return gfx::Point((client_area_bounds.width() - dialog_size.width()) / 2, |
| 0); |
| } else if (IsAttached()) { |
| // TODO(crbug.com/439745838): Maybe implement for side panel if needed. |
| NOTIMPLEMENTED(); |
| } |
| return gfx::Point(); |
| } |
| |
| bool GlicWindowControllerImpl::ShouldConstrainDialogBoundsByHost() { |
| // Allows web modal dialogs to extend beyond the boundary of glic window. |
| // These web modals are usually larger than the glic window. |
| return false; |
| } |
| |
| void GlicWindowControllerImpl::AddObserver( |
| web_modal::ModalDialogHostObserver* observer) { |
| if (!IsDetached()) { |
| return; |
| } |
| modal_dialog_host_observers_.AddObserver(observer); |
| } |
| |
| void GlicWindowControllerImpl::RemoveObserver( |
| web_modal::ModalDialogHostObserver* observer) { |
| if (!IsDetached()) { |
| return; |
| } |
| modal_dialog_host_observers_.RemoveObserver(observer); |
| } |
| |
| void GlicWindowControllerImpl::MaybeSendConversationViewRequest() { |
| auto request = mojom::ViewChangeRequest::New( |
| mojom::ViewChangeRequestDetails::NewConversation( |
| mojom::ViewChangeRequestConversation::New())); |
| host().SendViewChangeRequest(std::move(request)); |
| } |
| |
| void GlicWindowControllerImpl::MaybeSendActuationViewRequest() { |
| auto request = mojom::ViewChangeRequest::New( |
| mojom::ViewChangeRequestDetails::NewActuation( |
| mojom::ViewChangeRequestActuation::New())); |
| host().SendViewChangeRequest(std::move(request)); |
| } |
| |
| bool GlicWindowControllerImpl::InvocationSourceMatchesCurrentView( |
| mojom::InvocationSource source) { |
| auto current_view = host().GetPrimaryCurrentView(); |
| return (source == mojom::InvocationSource::kActorTaskIcon && |
| current_view == mojom::CurrentView::kActuation) || |
| (source == mojom::InvocationSource::kTopChromeButton && |
| current_view == mojom::CurrentView::kConversation); |
| } |
| |
| } // namespace glic |