| // 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. |
| |
| #include "chrome/browser/glic/widget/browser_conditions.h" |
| |
| #include "base/memory/raw_ptr.h" |
| #include "base/scoped_multi_source_observation.h" |
| #include "base/scoped_observation.h" |
| #include "base/timer/timer.h" |
| #include "chrome/browser/glic/public/glic_enabling.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_list.h" |
| #include "chrome/browser/ui/browser_list_observer.h" |
| #include "chrome/browser/ui/browser_window/public/desktop_browser_window_capabilities.h" |
| #include "chrome/browser/ui/views/frame/browser_view.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/gfx/win/hwnd_util.h" |
| #endif // BUILDFLAG(IS_WIN) |
| |
| namespace glic { |
| |
| namespace { |
| #if BUILDFLAG(IS_WIN) |
| |
| struct IsBrowserTopmostWindowState { |
| HWND browser_hwnd = nullptr; |
| bool browser_is_topmost_window = false; |
| }; |
| |
| // Window enumerator used to determine if the top most visible window, other |
| // than the system tray, is the browser window it is looking for. This is called |
| // in z-order, i.e., topmost window first. |
| // Window enumerator, so returning FALSE stops enumerating. |
| // `lParam` is a pointer to IsBrowserTopmostWindowState, which contains the |
| // browser HWND it is looking for. When finished enumerating, it sets |
| // topmost_visible_non_opaque_hwnd to the topmost non system tray HWND it |
| // finds. |
| BOOL CALLBACK IsBrowserWindowTopmostWindowEnumerator(HWND hwnd, LPARAM lParam) { |
| struct IsBrowserTopmostWindowState* state = |
| reinterpret_cast<struct IsBrowserTopmostWindowState*>(lParam); |
| if (hwnd == state->browser_hwnd) { |
| state->browser_is_topmost_window = true; |
| return FALSE; |
| } else if (gfx::GetClassName(hwnd) != L"Shell_TrayWnd" && |
| gfx::IsWindowVisibleAndFullyOpaque(hwnd, nullptr)) { |
| state->browser_is_topmost_window = false; |
| return FALSE; |
| } |
| return TRUE; |
| } |
| |
| bool IsBrowserWindowTopmostWindow(Browser* browser) { |
| HWND browser_hwnd = |
| browser->window()->GetNativeWindow()->GetHost()->GetAcceleratedWidget(); |
| |
| struct IsBrowserTopmostWindowState state{browser_hwnd, false}; |
| EnumWindows(&IsBrowserWindowTopmostWindowEnumerator, |
| reinterpret_cast<LPARAM>(&state)); |
| return state.browser_is_topmost_window; |
| } |
| |
| #endif // BUILDFLAG(IS_WIN) |
| |
| bool IsBrowserGlicCompatible(Profile* profile, Browser* browser) { |
| // A browser is not compatible if it: |
| // - is not a TYPE_NORMAL browser |
| // - is from a glic-disabled profile |
| // - uses a different Profile from glic |
| // WARNING: updating these conditions will require updating |
| // BrowserAttachObservation. |
| return GlicEnabling::IsEnabledForProfile(browser->profile()) && |
| browser->is_type_normal() && browser->profile() == profile; |
| } |
| |
| } // namespace |
| |
| bool IsBrowserGlicAttachable(Profile* profile, Browser* browser) { |
| return IsBrowserGlicCompatible(profile, browser) && |
| browser->window()->IsVisible() && !browser->window()->IsMinimized(); |
| } |
| |
| Browser* FindBrowserForAttachment(Profile* profile) { |
| // TODO (crbug.com/390472495) Determine which browser to attach to. Currently |
| // attaches to the last focused glic-compatible browser. |
| for (Browser* browser : BrowserList::GetInstance()->OrderedByActivation()) { |
| if (IsBrowserGlicAttachable(profile, browser)) { |
| return browser; |
| } |
| } |
| return nullptr; |
| } |
| |
| bool IsBrowserInForeground(Browser* browser) { |
| if (browser->IsActive()) { |
| return true; |
| } |
| #if BUILDFLAG(IS_WIN) |
| // On Windows, clicking the status bar icon makes an active browser window |
| // inactive, but it will still be the last active browser. Attach to the |
| // last active browser if it's the foremost visible window, other than the |
| // system tray. |
| return IsBrowserWindowTopmostWindow(browser); |
| #else |
| return false; |
| #endif // BUILDFLAG(IS_WIN) |
| } |
| |
| bool IsBrowserVisible(Browser* browser) { |
| return browser && browser->window() && |
| browser->GetBrowserView().GetWidget() && |
| browser->window()->IsVisible() && !browser->window()->IsMinimized() && |
| browser->capabilities()->IsVisibleOnScreen(); |
| } |
| |
| class BrowserAttachObservationImpl : public BrowserAttachObservation, |
| public BrowserListObserver, |
| public views::WidgetObserver { |
| public: |
| BrowserAttachObservationImpl(Profile* profile, |
| BrowserAttachObserver* observer) |
| : profile_(profile), |
| observer_(observer), |
| browser_list_observation_(this), |
| browser_widget_observations_(this) { |
| for (auto browser : *BrowserList::GetInstance()) { |
| OnBrowserAdded(browser); |
| } |
| browser_list_observation_.Observe(BrowserList::GetInstance()); |
| current_value_ = FindBrowserForAttachment(profile_); |
| } |
| |
| bool CanAttachToBrowser() const override { return current_value_ != nullptr; } |
| |
| // BrowserListObserver implementation. |
| void OnBrowserSetLastActive(Browser* browser) override { |
| // BrowserList updates the active browser list before this call, so |
| // `CheckForChange` will find the correct browser. |
| CheckForChange(); |
| } |
| void OnBrowserAdded(Browser* browser) override { |
| if (IsBrowserGlicCompatible(profile_, browser)) { |
| browser_widget_observations_.AddObservation( |
| browser->GetBrowserView().GetWidget()); |
| } |
| } |
| void OnBrowserRemoved(Browser* browser) override { |
| if (current_value_ == browser) { |
| // BrowserList updates the active browser list before this call, so |
| // `CheckForChange` will find the correct browser. |
| CheckForChange(); |
| } |
| } |
| |
| // views::WidgetObserver implementation. |
| void OnWidgetVisibilityChanged(views::Widget* widget, bool visible) override { |
| // Note: visibility change takes effect after this call, so PostMessage is |
| // critical here. |
| check_for_change_timer_.Start( |
| FROM_HERE, base::TimeDelta(), |
| base::BindOnce(&BrowserAttachObservationImpl::CheckForChange, |
| base::Unretained(this))); |
| } |
| void OnWidgetShowStateChanged(views::Widget* widget) override { |
| CheckForChange(); |
| } |
| void OnWidgetDestroyed(views::Widget* widget) override { |
| // Note: widget observer removal has to be done at widget destruction time |
| // because when OnBrowserRemoved is called, the widget has already |
| // been destroyed. |
| if (browser_widget_observations_.IsObservingSource(widget)) { |
| browser_widget_observations_.RemoveObservation(widget); |
| } |
| } |
| |
| private: |
| void CheckForChange() { |
| SetBrowserForAttachment(FindBrowserForAttachment(profile_)); |
| } |
| |
| void SetBrowserForAttachment(Browser* browser) { |
| if (current_value_ == browser) { |
| return; |
| } |
| bool could_attach = current_value_ != nullptr; |
| bool can_attach = browser != nullptr; |
| current_value_ = browser; |
| observer_->BrowserForAttachmentChanged(browser); |
| if (could_attach != can_attach) { |
| observer_->CanAttachToBrowserChanged(can_attach); |
| } |
| } |
| |
| raw_ptr<Profile> profile_; |
| raw_ptr<Browser> current_value_; |
| raw_ptr<BrowserAttachObserver> observer_; |
| base::OneShotTimer check_for_change_timer_; |
| base::ScopedObservation<BrowserList, BrowserListObserver> |
| browser_list_observation_; |
| base::ScopedMultiSourceObservation<views::Widget, views::WidgetObserver> |
| browser_widget_observations_; |
| }; |
| |
| std::unique_ptr<BrowserAttachObservation> ObserveBrowserForAttachment( |
| Profile* profile, |
| BrowserAttachObserver* observer) { |
| return std::make_unique<BrowserAttachObservationImpl>(profile, observer); |
| } |
| |
| } // namespace glic |