// Copyright 2019 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/ui/views/frame/immersive_mode_controller_mac.h"

#include <AppKit/AppKit.h>

#include <algorithm>
#include <vector>

#include "base/apple/foundation_util.h"
#include "base/auto_reset.h"
#include "base/check.h"
#include "base/feature_list.h"
#include "chrome/browser/glic/widget/glic_widget.h"
#include "chrome/browser/ui/browser_window/public/browser_window_features.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/find_bar/find_bar.h"
#include "chrome/browser/ui/find_bar/find_bar_controller.h"
#include "chrome/browser/ui/fullscreen_util_mac.h"
#include "chrome/browser/ui/views/frame/browser_frame_view_mac.h"
#include "chrome/browser/ui/views/frame/tab_strip_region_view.h"
#include "chrome/browser/ui/views/frame/top_container_view.h"
#include "chrome/browser/ui/views/infobars/infobar_container_view.h"
#include "chrome/browser/ui/views/omnibox/omnibox_popup_presenter_base.h"
#include "components/constrained_window/constrained_window_views.h"
#include "components/remote_cocoa/app_shim/features.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/views/border.h"
#include "ui/views/bubble/bubble_dialog_delegate_view.h"
#include "ui/views/cocoa/native_widget_mac_ns_window_host.h"
#include "ui/views/focus/focus_search.h"
#include "ui/views/widget/native_widget.h"

namespace {

// This FocusSearch connects BrowserView, the overlay widget and the tab
// overlay widget to form a complete focus traversal cycle. It finds the
// next focusable view from another widget when the FocusManager cannot
// find the next focusable view in the currently focused widget.
class ImmersiveModeFocusSearchMac : public views::FocusSearch {
 public:
  explicit ImmersiveModeFocusSearchMac(BrowserView* browser_view);
  ImmersiveModeFocusSearchMac(const ImmersiveModeFocusSearchMac&) = delete;
  ImmersiveModeFocusSearchMac& operator=(const ImmersiveModeFocusSearchMac&) =
      delete;
  ~ImmersiveModeFocusSearchMac() override;

  // views::FocusSearch:
  views::View* FindNextFocusableView(
      views::View* starting_view,
      SearchDirection search_direction,
      TraversalDirection traversal_direction,
      StartingViewPolicy check_starting_view,
      AnchoredDialogPolicy can_go_into_anchored_dialog,
      views::FocusTraversable** focus_traversable,
      views::View** focus_traversable_view) override;

 private:
  bool finding_next_focusable_view_ = false;
  raw_ptr<BrowserView> browser_view_;
};

}  // namespace

ImmersiveModeControllerMac::RevealedLock::RevealedLock(
    base::WeakPtr<ImmersiveModeControllerMac> controller)
    : controller_(std::move(controller)) {}

ImmersiveModeControllerMac::RevealedLock::~RevealedLock() {
  if (auto* controller = controller_.get()) {
    controller->LockDestroyed();
  }
}

ImmersiveModeControllerMac::ImmersiveModeControllerMac(
    ui::UnownedUserDataHost& host,
    bool separate_tab_strip)
    : ImmersiveModeController(host), separate_tab_strip_(separate_tab_strip) {}

ImmersiveModeControllerMac::~ImmersiveModeControllerMac() {
  CHECK(!views::WidgetObserver::IsInObserverList());
}

void ImmersiveModeControllerMac::Init(BrowserView* browser_view) {
  browser_view_ = browser_view;
  focus_search_ = std::make_unique<ImmersiveModeFocusSearchMac>(browser_view);
  browser_close_subscription_ =
      browser_view_->browser()->RegisterBrowserDidClose(
          base::BindRepeating(&ImmersiveModeControllerMac::BrowserDidClose,
                              base::Unretained(this)));
}

void ImmersiveModeControllerMac::SetEnabled(bool enabled) {
  if (enabled_ == enabled) {
    return;
  }
  enabled_ = enabled;
  if (enabled) {
    // Vertical tab strip should stay visible and will not be reparented to the
    // tab overlay.
    if (separate_tab_strip_ && !browser_view_->ShouldDrawVerticalTabStrip()) {
      tab_widget_height_ = browser_view_->tab_strip_view()->height();
      tab_widget_height_ += static_cast<BrowserFrameViewMac*>(
                                browser_view_->browser_widget()->GetFrameView())
                                ->GetTopInset(false);

      browser_view_->tab_overlay_widget()->SetSize(gfx::Size(
          browser_view_->top_container()->size().width(), tab_widget_height_));
      browser_view_->tab_overlay_widget()->Show();

      // Move the tab strip to the `tab_overlay_widget`, the host of the
      // `tab_overlay_view`.
      browser_view_->tab_overlay_view()->AddChildViewRaw(
          static_cast<views::View*>(browser_view_->tab_strip_view()));

      browser_view_->tab_strip_view()->SetBorder(
          views::CreateEmptyBorder(GetTabStripRegionViewInsets()));

      views::NativeWidgetMacNSWindowHost* tab_overlay_host =
          views::NativeWidgetMacNSWindowHost::GetFromNativeWindow(
              browser_view_->tab_overlay_widget()->GetNativeWindow());
      SetTabNativeWidgetID(tab_overlay_host->bridged_native_widget_id());
    }
    top_container_observation_.Observe(browser_view_->top_container());
    overlay_widget_observation_.Observe(browser_view_->overlay_widget());

    // Capture the overlay content view before enablement. Once enabled the view
    // is moved to an AppKit window leaving us otherwise without a reference.
    NSView* content_view = browser_view_->overlay_widget()
                               ->GetNativeWindow()
                               .GetNativeNSWindow()
                               .contentView;
    browser_view_->overlay_widget()->SetNativeWindowProperty(
        views::NativeWidgetMacNSWindowHost::kMovedContentNSView,
        (__bridge void*)content_view);

    views::NativeWidgetMacNSWindowHost::GetFromNativeWindow(
        browser_view_->GetWidget()->GetNativeWindow())
        ->set_immersive_mode_reveal_client(weak_ptr_factory_.GetWeakPtr());

    // Move the appropriate children from the browser widget to the overlay
    // widget, unless we are entering content fullscreen. Make sure to call
    // `Show()` on the overlay widget before enabling immersive fullscreen. The
    // call to `Show()` actually performs the underlying window reparenting.
    if (!fullscreen_utils::IsInContentFullscreen(browser_view_->browser())) {
      MoveChildren(browser_view_->GetWidget(), browser_view_->overlay_widget());
    }

    // `Show()` is needed because the overlay widget's compositor is still being
    // used, even though its content view has been moved to the AppKit
    // controlled fullscreen NSWindow.
    browser_view_->overlay_widget()->Show();

    // Set revealed to be true when entering immersive fullscreen so the toolbar
    // and bookmarks bar heights are accounted for during the fullscreen
    // transition.
    OnImmersiveModeToolbarRevealChanged(true);

    for (Observer& observer : observers_) {
      observer.OnImmersiveFullscreenEntered();
    }

    views::NativeWidgetMacNSWindowHost* overlay_host =
        views::NativeWidgetMacNSWindowHost::GetFromNativeWindow(
            browser_view_->overlay_widget()->GetNativeWindow());
    if (auto* window = GetNSWindowMojo()) {
      window->EnableImmersiveFullscreen(
          overlay_host->bridged_native_widget_id(), tab_native_widget_id_);
    }
    browser_view_->GetWidget()->GetFocusManager()->AddFocusChangeListener(this);
    // Set up a root FocusTraversable that handles focus cycles between overlay
    // widgets and the browser widget.
    browser_view_->GetWidget()->SetFocusTraversableParent(this);
    browser_view_->GetWidget()->SetFocusTraversableParentView(browser_view_);
    browser_view_->overlay_widget()->SetFocusTraversableParent(this);
    browser_view_->overlay_widget()->SetFocusTraversableParentView(
        browser_view_->overlay_view());
    if (browser_view_->tab_overlay_widget()) {
      browser_view_->tab_overlay_widget()->SetFocusTraversableParent(this);
      browser_view_->tab_overlay_widget()->SetFocusTraversableParentView(
          browser_view_->tab_overlay_view());
    }

    // If the window is maximized OnViewBoundsChanged will not be called
    // when transitioning to full screen. Call it now.
    OnViewBoundsChanged(browser_view_->top_container());
  } else {
    if (separate_tab_strip_) {
      browser_view_->tab_overlay_widget()->Hide();
      browser_view_->tab_strip_view()->SetBorder(nullptr);
    }
    // Reset tab widget state so that re-entering fullscreen with a different
    // tab layout (e.g. vertical tabs) does not carry over stale values from a
    // previous horizontal-tab fullscreen session.
    tab_native_widget_id_ = 0;
    tab_widget_height_ = 0;
    top_container_observation_.Reset();
    overlay_widget_observation_.Reset();

    // Notify BrowserView about the fullscreen exit so that the top container
    // can be reparented, otherwise it might be destroyed along with the
    // overlay widget.
    for (Observer& observer : observers_) {
      observer.OnImmersiveFullscreenExited();
    }

    // Rollback the view shuffling from enablement.
    MoveChildren(browser_view_->overlay_widget(), browser_view_->GetWidget());
    browser_view_->overlay_widget()->Hide();
    if (auto* window = GetNSWindowMojo()) {
      window->DisableImmersiveFullscreen();
    }
    browser_view_->overlay_widget()->SetNativeWindowProperty(
        views::NativeWidgetMacNSWindowHost::kMovedContentNSView, nullptr);

    browser_view_->GetWidget()->GetFocusManager()->RemoveFocusChangeListener(
        this);
    focus_lock_.reset();
    // Remove the root FocusTraversable.
    browser_view_->GetWidget()->SetFocusTraversableParent(nullptr);
    browser_view_->GetWidget()->SetFocusTraversableParentView(nullptr);
    browser_view_->overlay_widget()->SetFocusTraversableParent(nullptr);
    browser_view_->overlay_widget()->SetFocusTraversableParentView(nullptr);
    if (browser_view_->tab_overlay_widget()) {
      browser_view_->tab_overlay_widget()->SetFocusTraversableParent(nullptr);
      browser_view_->tab_overlay_widget()->SetFocusTraversableParentView(
          nullptr);
    }
  }
}

// LINT.IfChange(MacTabStripInsets)
gfx::Insets ImmersiveModeControllerMac::GetTabStripRegionViewInsets() {
  // TODO(crbug.com/40892148): Get this dynamically. Unfortunately the
  // values in BrowserFrameViewMac::GetCaptionButtonInsets don't
  // account for a window with an NSToolbar. (This is now done for
  // the Mac browser frame view, but not yet here.)
  int right_left_inset = 0;
  if (@available(macOS 26, *)) {
    right_left_inset = 74;
  } else {
    right_left_inset = 66;
  }

  // Without this +1 top inset the tabs sit 1px too high. I assume this is
  // because in fullscreen there is no resize handle.
  return browser_view_->browser_widget()
                 ->GetFrameView()
                 ->CaptionButtonsOnLeadingEdge()
             ? gfx::Insets::TLBR(1, right_left_inset, 0, 0)
             : gfx::Insets::TLBR(1, 0, 0, right_left_inset);
}
// LINT.ThenChange(//chrome/browser/ui/views/frame/browser_frame_view_mac.mm:MacTabStripInsets)

void ImmersiveModeControllerMac::BrowserDidClose(
    BrowserWindowInterface* browser) {
  SetEnabled(false);
}

bool ImmersiveModeControllerMac::IsEnabled() const {
  return enabled_;
}

bool ImmersiveModeControllerMac::IsRevealed() const {
  return enabled_ && is_revealed_;
}

int ImmersiveModeControllerMac::GetTopContainerVerticalOffset(
    const gfx::Size& top_container_size) const {
  return 0;
}

std::unique_ptr<ImmersiveRevealedLock>
ImmersiveModeControllerMac::GetRevealedLock(AnimateReveal animate_reveal) {
  if (auto* window = GetNSWindowMojo()) {
    window->ImmersiveFullscreenRevealLock();
  }
  return std::make_unique<RevealedLock>(weak_ptr_factory_.GetWeakPtr());
}

void ImmersiveModeControllerMac::OnFindBarVisibleBoundsChanged(
    const gfx::Rect& new_visible_bounds_in_screen) {
  bool was_visible =
      std::exchange(find_bar_visible_, !new_visible_bounds_in_screen.IsEmpty());
  if (enabled_ && was_visible != find_bar_visible_) {
    // Ensure web content is fully visible if find bar is showing.
    browser_view_->InvalidateLayout();
  }
}

bool ImmersiveModeControllerMac::ShouldStayImmersiveAfterExitingFullscreen() {
  return false;
}

int ImmersiveModeControllerMac::GetMinimumContentOffset() const {
  if ((find_bar_visible_ || HasVisibleBubbleInOverlay()) &&
      !fullscreen_utils::IsAlwaysShowToolbarEnabled(browser_view_->browser()) &&
      !fullscreen_utils::IsInContentFullscreen(browser_view_->browser())) {
    return overlay_height_;
  }
  return 0;
}

int ImmersiveModeControllerMac::GetExtraInfobarOffset() const {
  if (fullscreen_utils::IsInContentFullscreen(browser_view_->browser())) {
    return 0;
  }
  if (fullscreen_utils::IsAlwaysShowToolbarEnabled(browser_view_->browser())) {
    return reveal_amount_ * menu_bar_height_;
  }
  return reveal_amount_ * (menu_bar_height_ + overlay_height_);
}

void ImmersiveModeControllerMac::OnContentFullscreenChanged(
    bool is_content_fullscreen) {
  // Ignore this call if we are not in browser fullscreen.
  if (!IsEnabled()) {
    return;
  }

  if (is_content_fullscreen) {
    // When in content fullscreen the overlay widget is not displayed. Move all
    // the child widgets from the overlay widget to the browser widget. This is
    // particularly important for sticky children like the find bar or
    // permission dialogs.
    MoveChildren(browser_view_->overlay_widget(), browser_view_->GetWidget());
  } else {
    // Put the children back when transitioning from content fullscreen back to
    // browser fullscreen.
    MoveChildren(browser_view_->GetWidget(), browser_view_->overlay_widget());
  }
}

void ImmersiveModeControllerMac::OnDidChangeFocus(views::View* focused_before,
                                                  views::View* focused_now) {
  if (browser_view_->top_container()->Contains(focused_now) ||
      (browser_view_->tab_overlay_view() &&
       browser_view_->tab_overlay_view()->Contains(focused_now))) {
    if (!focus_lock_) {
      focus_lock_ = GetRevealedLock(ANIMATE_REVEAL_NO);
    }
  } else {
    focus_lock_.reset();
  }
}

void ImmersiveModeControllerMac::OnViewBoundsChanged(
    views::View* observed_view) {
  gfx::Rect bounds = observed_view->bounds();
  if (bounds.IsEmpty()) {
    return;
  }
  overlay_height_ = bounds.height();
  // TODO(b/475222200): Currently tab strips are duplicated when switching to
  // vertical tabs while in immersive leaves behind the horizontal tab, and
  // OnViewBoundsChanged does not get called.
  // tab_overlay_widget size should only be set if we aren't drawing the
  // vertical tabstrip.
  if (separate_tab_strip_ && !browser_view_->ShouldDrawVerticalTabStrip()) {
    gfx::Size new_size(bounds.width(), tab_widget_height_);
    browser_view_->tab_overlay_widget()->SetSize(new_size);
    browser_view_->tab_overlay_view()->SetSize(new_size);
    browser_view_->tab_strip_view()->SetSize(
        gfx::Size(new_size.width(), browser_view_->tab_strip_view()->height()));
    overlay_height_ += tab_widget_height_;
  }
  browser_view_->overlay_widget()->SetSize(bounds.size());
  if (auto* window = GetNSWindowMojo()) {
    window->OnTopContainerViewBoundsChanged(bounds);
  }
}

void ImmersiveModeControllerMac::LockDestroyed() {
  if (auto* window = GetNSWindowMojo()) {
    window->ImmersiveFullscreenRevealUnlock();
  }
}

void ImmersiveModeControllerMac::SetTabNativeWidgetID(uint64_t widget_id) {
  tab_native_widget_id_ = widget_id;
}

void ImmersiveModeControllerMac::MoveChildren(views::Widget* from_widget,
                                              views::Widget* to_widget) {
  CHECK(from_widget && to_widget);

  // If the browser window is closing the native view is removed. Don't attempt
  // to move children.
  if (!from_widget->GetNativeView() || !to_widget->GetNativeView()) {
    return;
  }

  views::Widget::Widgets widgets =
      views::Widget::GetAllChildWidgets(from_widget->GetNativeView());
  for (views::Widget* widget : widgets) {
    if (ShouldMoveChild(widget)) {
      views::Widget::ReparentNativeView(widget->GetNativeView(),
                                        to_widget->GetNativeView());
    }
  }
}

bool ImmersiveModeControllerMac::HasVisibleBubbleInOverlay() const {
  if (!browser_view_->overlay_widget()) {
    return false;
  }
  for (views::Widget* widget : views::Widget::GetAllChildWidgets(
           browser_view_->overlay_widget()->GetNativeView())) {
    if (widget->IsVisible() && widget->widget_delegate() &&
        widget->widget_delegate()->AsBubbleDialogDelegate()) {
      return true;
    }
  }
  return false;
}

bool ImmersiveModeControllerMac::ShouldMoveChild(views::Widget* child) {
  // Filter out widgets that should not be reparented.
  // The browser, overlay and tab overlay widgets all stay put.
  if (child == browser_view_->GetWidget() ||
      child == browser_view_->overlay_widget() ||
      child == browser_view_->tab_overlay_widget()) {
    return false;
  }

  // The find bar should be reparented if it exists.
  if (browser_view_->browser()->GetFeatures().HasFindBarController()) {
    FindBarController* find_bar_controller =
        browser_view_->browser()->GetFeatures().GetFindBarController();
    if (child == find_bar_controller->find_bar()->GetHostWidget()) {
      return true;
    }
  }

  const void* widget_identifier =
      child->GetNativeWindowProperty(views::kWidgetIdentifierKey);
  if (widget_identifier ==
          constrained_window::kConstrainedWindowWidgetIdentifier
      || widget_identifier == glic::kGlicWidgetIdentifier
  ) {
    return true;
  }

  // Reparent the Omnibox popup. Popup is not a BubbleDialogDelegate, so it
  // fails the check below.
  if (widget_identifier == omnibox::kOmniboxWebUIPopupWidgetId) {
    return true;
  }

  // Widgets that have an anchor view contained within top chrome should be
  // reparented.
  views::WidgetDelegate* widget_delegate = child->widget_delegate();
  if (!widget_delegate) {
    return false;
  }
  views::BubbleDialogDelegate* bubble_dialog =
      widget_delegate->AsBubbleDialogDelegate();
  if (!bubble_dialog) {
    return false;
  }
  // Both `top_container` and `tab_strip_view` are checked individually
  // because `tab_strip_view` is pulled out of `top_container` to be
  // displayed in the titlebar.
  views::View* anchor_view = bubble_dialog->GetAnchorView();
  if (anchor_view && (browser_view_->top_container()->Contains(anchor_view) ||
                      browser_view_->tab_strip_view()->Contains(anchor_view))) {
    return true;
  }

  // All other widgets will stay put.
  return false;
}

void ImmersiveModeControllerMac::OnImmersiveModeToolbarRevealChanged(
    bool is_revealed) {
  if (is_revealed_ == is_revealed) {
    return;
  }
  is_revealed_ = is_revealed;

  // Notify observers that immersive reveal has started.
  for (Observer& observer : observers_) {
    if (is_revealed) {
      observer.OnImmersiveRevealStarted();
    } else {
      observer.OnImmersiveRevealEnded();
    }
  }
}

void ImmersiveModeControllerMac::OnImmersiveModeMenuBarRevealChanged(
    double reveal_amount) {
  reveal_amount_ = reveal_amount;
  if (!browser_view_->infobar_container()->IsEmpty()) {
    browser_view_->InvalidateLayout();
  }
}

void ImmersiveModeControllerMac::OnAutohidingMenuBarHeightChanged(
    int menu_bar_height) {
  menu_bar_height_ = menu_bar_height;
  if (!browser_view_->infobar_container()->IsEmpty()) {
    browser_view_->InvalidateLayout();
  }
}

views::FocusSearch* ImmersiveModeControllerMac::GetFocusSearch() {
  return focus_search_.get();
}

views::FocusTraversable*
ImmersiveModeControllerMac::GetFocusTraversableParent() {
  return nullptr;
}

views::View* ImmersiveModeControllerMac::GetFocusTraversableParentView() {
  return nullptr;
}

remote_cocoa::mojom::NativeWidgetNSWindow*
ImmersiveModeControllerMac::GetNSWindowMojo() {
  return views::NativeWidgetMacNSWindowHost::GetFromNativeWindow(
             browser_view_->GetWidget()->GetNativeWindow())
      ->GetNSWindowMojo();
}

ImmersiveModeFocusSearchMac::ImmersiveModeFocusSearchMac(
    BrowserView* browser_view)
    : views::FocusSearch(browser_view, true, true),
      browser_view_(browser_view) {}

ImmersiveModeFocusSearchMac::~ImmersiveModeFocusSearchMac() = default;

views::View* ImmersiveModeFocusSearchMac::FindNextFocusableView(
    views::View* starting_view,
    SearchDirection search_direction,
    TraversalDirection traversal_direction,
    StartingViewPolicy check_starting_view,
    AnchoredDialogPolicy can_go_into_anchored_dialog,
    views::FocusTraversable** focus_traversable,
    views::View** focus_traversable_view) {
  // Re-entering ImmersiveModeFocusSearchMac. This means that the last focus
  // search fails to find a focusable view in the widget. Early exit to
  // prevent infinite focus search loop.
  if (finding_next_focusable_view_) {
    return nullptr;
  }
  base::AutoReset<bool> auto_reset(&finding_next_focusable_view_, true);

  // Search in the `starting_view` traversable tree.
  views::FocusTraversable* starting_focus_traversable =
      starting_view->GetFocusTraversable();
  if (!starting_focus_traversable) {
    starting_focus_traversable =
        starting_view->GetWidget()->GetFocusTraversable();
  }

  views::View* v =
      starting_focus_traversable->GetFocusSearch()->FindNextFocusableView(
          starting_view, search_direction, traversal_direction,
          check_starting_view, can_go_into_anchored_dialog, focus_traversable,
          focus_traversable_view);

  if (v) {
    return v;
  }

  // If no next focusable view in the `starting_view` traversable tree,
  // jumps to the next widget.
  views::FocusManager* focus_manager =
      browser_view_->GetWidget()->GetFocusManager();

  // The focus cycles between overlay widget(s) and the browser widget.
  std::vector<views::Widget*> traverse_order = {browser_view_->overlay_widget(),
                                                browser_view_->GetWidget()};
  if (browser_view_->tab_overlay_widget()) {
    traverse_order.push_back(browser_view_->tab_overlay_widget());
  }

  auto current_widget_it = std::ranges::find_if(
      traverse_order, [starting_view](const views::Widget* widget) {
        return widget->GetRootView()->Contains(starting_view);
      });
  CHECK(current_widget_it != traverse_order.end());
  int current_widget_ind = current_widget_it - traverse_order.begin();

  bool reverse = search_direction == SearchDirection::kBackwards;
  int next_widget_ind =
      (current_widget_ind + (reverse ? -1 : 1) + traverse_order.size()) %
      traverse_order.size();
  return focus_manager->GetNextFocusableView(
      nullptr, traverse_order[next_widget_ind], reverse, true);
}

ImmersiveModeOverlayWidgetObserver::ImmersiveModeOverlayWidgetObserver(
    ImmersiveModeControllerMac* controller)
    : controller_(controller) {}

ImmersiveModeOverlayWidgetObserver::~ImmersiveModeOverlayWidgetObserver() =
    default;

void ImmersiveModeOverlayWidgetObserver::OnWidgetBoundsChanged(
    views::Widget* widget,
    const gfx::Rect& new_bounds) {
  // Update web dialog position when the overlay widget moves by invalidating
  // the browser view layout.
  controller_->browser_view()->InvalidateLayout();

  // The overlay widget moving can affect the position of its child widgets
  // (e.g. bubbles) relative to their anchors (which might be in the browser
  // widget). We need to re-anchor them.
  for (views::Widget* child :
       views::Widget::GetAllChildWidgets(widget->GetNativeView())) {
    if (views::WidgetDelegate* widget_delegate = child->widget_delegate()) {
      if (views::BubbleDialogDelegate* bubble =
              widget_delegate->AsBubbleDialogDelegate()) {
        bubble->OnAnchorBoundsChanged();
      }
    }
  }
}

void ImmersiveModeOverlayWidgetObserver::OnWidgetDestroying(
    views::Widget* widget) {
  controller_->SetEnabled(false);
}
