// Copyright 2014 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/autofill/popup/popup_base_view.h"

#include <memory>
#include <string_view>
#include <utility>

#include "base/dcheck_is_on.h"
#include "base/feature_list.h"
#include "base/i18n/rtl.h"
#include "chrome/browser/platform_util.h"
#include "chrome/browser/themes/theme_service.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/browser_window/public/global_browser_collection.h"
#include "chrome/browser/ui/views/autofill/popup/custom_cursor_suppressor.h"
#include "chrome/browser/ui/views/autofill/popup/popup_view_utils.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "components/autofill/core/common/autofill_features.h"
#include "components/strings/grit/components_strings.h"
#include "content/public/browser/web_contents.h"
#include "third_party/skia/include/core/SkPath.h"
#include "third_party/skia/include/core/SkRRect.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/color/color_id.h"
#include "ui/display/screen.h"
#include "ui/events/base_event_utils.h"
#include "ui/gfx/geometry/skia_conversions.h"
#include "ui/gfx/scrollbar_size.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/border.h"
#include "ui/views/bubble/bubble_border.h"
#include "ui/views/focus/focus_manager.h"
#include "ui/views/layout/layout_provider.h"
#include "ui/views/view_tracker.h"
#include "ui/views/widget/widget.h"

#if DCHECK_IS_ON()
#include "base/containers/fixed_flat_set.h"
#endif

namespace autofill {

namespace {

// The maximum size (in DIPs) of custom cursors that are permitted while the
// popup is shown. The size is limited to avoid custom cursors that cover most
// of the popup.
constexpr int kMaximumAllowedCustomCursorDimension = 24;

// The maximum number of pixels the suggestions dialog is shifted towards the
// center the focused field.
constexpr int kMaximumPixelsToMoveSuggestionToCenter = 120;

// The maximum width percentage the suggestion dialog is shifted towards the
// center of the focused field.
constexpr int kMaximumWidthPercentageToMoveTheSuggestionToCenter = 50;

// The max number of pixels the popup is allowed to be rendered above the top
// of the `WebContents`. Limiting overflow prevents the popup content from
// covering important browser elements (e.g., the address bar).
constexpr int kMaxPopupWebContentsTopYOverflow = 8;

// Creates a border for a popup.
std::unique_ptr<views::Border> CreateBorder() {
  auto border = std::make_unique<views::BubbleBorder>(
      views::BubbleBorder::NONE, views::BubbleBorder::STANDARD_SHADOW);
  border->SetColor(ui::kColorDropdownBackground);
  border->set_rounded_corners(
      gfx::RoundedCornersF(PopupBaseView::GetCornerRadius()));
  border->set_md_shadow_elevation(
      ChromeLayoutProvider::Get()->GetShadowElevationMetric(
          views::Emphasis::kMedium));
  return border;
}

}  // namespace

// static
int PopupBaseView::GetCornerRadius() {
  return ChromeLayoutProvider::Get()->GetCornerRadiusMetric(
      views::Emphasis::kHigh);
}

// static
int PopupBaseView::ArrowHorizontalMargin() {
  // The horizontal margin should match the offset of the bubble arrow (if
  // that arrow happens to be shown on the top).
  return views::BubbleBorder::kVisibleArrowBuffer;
}

// The widget that the PopupBaseView will be attached to.
class PopupBaseView::Widget : public views::Widget {
 public:
  // Takes ownership of `autofill_popup_base_view` and uses it as the delegate
  // of a new Widget. `parent_native_view` is the intended parent view of the
  // new Widget.
  explicit Widget(PopupBaseView* autofill_popup_base_view,
                  gfx::NativeView parent_native_view,
                  views::Widget::InitParams::Activatable activatable) {
    views::Widget::InitParams params(
        views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
        views::Widget::InitParams::TYPE_POPUP);
    params.delegate = autofill_popup_base_view;
    params.parent = parent_native_view;
    // Ensure the popup border is not painted on an opaque background.
    params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
    params.shadow_type = views::Widget::InitParams::ShadowType::kNone;
    params.activatable = activatable;

    // `kSecuritySurface` makes the popup to display on top of all other windows
    // (including system ones, but the support among different OS, versions and
    // setups is not consistent). This is not required for regular autofill
    // popup use, but it makes certain attacks (those based on the popup being
    // obscured) less practical.
    params.z_order = ui::ZOrderLevel::kSecuritySurface;

    Init(std::move(params));
    AddObserver(popup_base_view());

    // No animation for popup appearance (too distracting).
    SetVisibilityAnimationTransition(views::Widget::ANIMATE_HIDE);
  }

  PopupBaseView* popup_base_view() const {
    // This cast is always safe since we pass the base view as a delegate.
    return static_cast<PopupBaseView*>(widget_delegate());
  }

  // views::Widget:
  const ui::ThemeProvider* GetThemeProvider() const override {
    if (!popup_base_view() || popup_base_view()->GetBrowser()) {
      return nullptr;
    }

    return &ThemeService::GetThemeProviderForProfile(
        popup_base_view()->GetBrowser()->GetProfile());
  }

  views::Widget* GetPrimaryWindowWidget() override {
    if (!popup_base_view() || !popup_base_view()->GetBrowser()) {
      return nullptr;
    }

    BrowserView* browser_view =
        BrowserView::GetBrowserViewForBrowser(popup_base_view()->GetBrowser());
    if (!browser_view) {
      return nullptr;
    }

    return browser_view->GetWidget()->GetPrimaryWindowWidget();
  }

  void OnMouseEvent(ui::MouseEvent* event) override {
    views::View* parent_content_view =
        parent() ? parent()->GetContentsView() : nullptr;

    if (!parent_content_view) {
      views::Widget::OnMouseEvent(event);
      return;
    }

    // Suppress the exit event on MacOS and Windows generated when the sub-popup
    // initially opens. We assume that it is the sub-popup that hovers
    // the parent by its semi-transparent shadow part. But in theory it could be
    // another window, which is not a problem because the popup closes on focus
    // loss anyway. The exit event will be synthesized by the sub-popup later
    // (find the trick that does this below).
    if (event->type() == ui::EventType::kMouseExited &&
        GetContentsView()->IsMouseHovered()) {
      return;
    }

    // Retrigger mouse moves on the parent to make selection/highlighting work
    // properly and thus provide more intuitive UX when the child's transparent
    // parts (e.g. shadow) overlap the parent (assuming that the child contents
    // view is not overlapped).
    if (event->type() == ui::EventType::kMouseMoved &&
        !GetContentsView()->IsMouseHovered() &&
        parent_content_view->IsMouseHovered()) {
      parent()->SynthesizeMouseMoveEvent();
      // Save the synthesized event position to use it for the exit event
      // later.
      last_synthesized_parent_mouse_move_position_ =
          display::Screen::Get()->GetCursorScreenPoint();
    } else if (!parent_content_view->IsMouseHovered() &&
               last_synthesized_parent_mouse_move_position_.has_value()) {
      // Generate the exit event after a set of move events as there is no one
      // handling this case (when the mouse gets outside of the parent
      // widget), which is important for the selection/highlighting state
      // consistency.
      const gfx::Point location = View::ConvertPointFromScreen(
          parent()->GetRootView(),
          last_synthesized_parent_mouse_move_position_.value());
      ui::MouseEvent mouse_event(ui::EventType::kMouseExited, location,
                                 location, ui::EventTimeForNow(),
                                 ui::EF_IS_SYNTHESIZED,
                                 /*changed_button_flags=*/0);
      parent()->OnMouseEvent(&mouse_event);
      last_synthesized_parent_mouse_move_position_.reset();
    }

    views::Widget::OnMouseEvent(event);
  }

 private:
  std::optional<gfx::Point> last_synthesized_parent_mouse_move_position_;
};

PopupBaseView::PopupBaseView(
    base::WeakPtr<AutofillPopupViewDelegate> delegate,
    views::Widget* parent_widget,
    views::Widget::InitParams::Activatable new_widget_activatable,
    bool show_arrow_pointer)
    : delegate_(delegate),
      parent_widget_(parent_widget),
      new_widget_activatable_(new_widget_activatable),
      show_arrow_pointer_(show_arrow_pointer) {
  // TODO(aleventhal) The correct role spec-wise to use here is kMenu, however
  // as of NVDA 2018.2.1, firing a menu event with kMenu breaks left/right
  // arrow editing feedback in text field. If NVDA addresses this we should
  // consider returning to using kMenu, so that users are notified that a
  // menu popup has been shown.
  GetViewAccessibility().SetRole(ax::mojom::Role::kPane);
  GetViewAccessibility().SetName(
      l10n_util::GetStringUTF16(IDS_AUTOFILL_POPUP_ACCESSIBLE_NODE_DATA));
}

PopupBaseView::~PopupBaseView() {
  if (delegate_) {
    delegate_->ViewDestroyed();
  }
  RemoveWidgetObservers();

  CHECK(!IsInObserverList());
}

BrowserWindowInterface* PopupBaseView::GetBrowser() {
  // When the browser is destroyed during test teardown (e.g.,
  // PopupViewViewsBrowsertest.SearchBarViewProvided), the browser's owned
  // popup widget is closed via Widget::ForEachOwnedWidget → CloseNow.
  // Destroying the popup's HWND triggers a WM_WINDOWPOSCHANGED message,
  // which calls GetPrimaryWindowWidget() → GetBrowser(). At that point,
  // FindBrowserWithTab() would access a partially torn-down Browser and
  // hang. Returning nullptr here is safe because callers (GetThemeProvider,
  // GetPrimaryWindowWidget) already handle a null return gracefully.
  if (GetWidget() && GetWidget()->IsClosed()) {
    return nullptr;
  }
  content::WebContents* web_contents = GetWebContents();
  if (!web_contents) {
    return nullptr;
  }
  return GlobalBrowserCollection::GetInstance()->FindBrowserWithTab(
      web_contents);
}

bool PopupBaseView::DoShow() {
  const bool initialize_widget = !GetWidget();
  if (initialize_widget) {
    // On Mac Cocoa browser, |parent_widget_| is null (the parent is not a
    // views::Widget).
    // TODO(crbug.com/41379554): Remove |parent_widget_|.
    if (parent_widget_) {
      parent_widget_->AddObserver(this);
    }

    // The widget is destroyed by the corresponding NativeWidget, so we don't
    // have to worry about deletion.
    new PopupBaseView::Widget(this, /*parent_native_view=*/
                              parent_widget_ ? parent_widget_->GetNativeView()
                                             : delegate_->container_view(),
                              new_widget_activatable_);
  }

  GetWidget()->GetRootView()->SetBorder(CreateBorder());
  bool enough_height = DoUpdateBoundsAndRedrawPopup();
  // If there is insufficient height, DoUpdateBoundsAndRedrawPopup() hides and
  // thus deletes |this|. Hence, there is nothing else to do.
  if (!enough_height) {
    return false;
  }

  if (GetWebContents()) {
    custom_cursor_suppressor_.Start(
        /*max_dimension_dips=*/kMaximumAllowedCustomCursorDimension + 1);
  } else {
    // `delegate_` is already gone and `WebContents` is destroying itself.
    return false;
  }

  GetWidget()->Show();

  // Showing the widget can change native focus (which would result in an
  // immediate hiding of the popup). Only start observing after shown.
  if (initialize_widget) {
    CHECK(!focus_observation_.IsObserving());
    focus_observation_.Observe(views::NativeViewFocusManager::GetInstance());
  }

  return true;
}

void PopupBaseView::DoHide() {
  if (is_ax_menu_start_event_fired_) {
    // Fire menu end event.
    // The menu start event is delayed until the user
    // navigates into the menu, otherwise some screen readers will ignore
    // any focus events outside of the menu, including a focus event on
    // the form control itself.
    if (!TrackAndRun(
            this,
            [this]() {
              NotifyAccessibilityEventDeprecated(
                  ax::mojom::Event::kMenuPopupEnd, true);
            },
            [this]() {
              NotifyAccessibilityEventDeprecated(ax::mojom::Event::kMenuEnd,
                                                 true);
            },
            [this]() { GetViewAccessibility().EndPopupFocusOverride(); },
            [this]() {
              if (parent_widget_) {
                if (views::FocusManager* focus_manager =
                        parent_widget_->GetFocusManager()) {
                  if (View* focused_view = focus_manager->GetFocusedView()) {
                    focused_view->GetViewAccessibility()
                        .FireFocusAfterMenuClose();
                  }
                }
              }
            })) {
      return;
    }
  }

  // The controller is no longer valid after it hides us.
  delegate_ = nullptr;

  RemoveWidgetObservers();

  if (GetWidget()) {
    // Don't call CloseNow() because some of the functions higher up the stack
    // assume the the widget is still valid after this point.
    // http://crbug.com/40312043
    // NOTE: This deletes |this|.
    GetWidget()->Close();
  } else {
    delete this;
  }
}

void PopupBaseView::NotifyAXSelection(views::View& selected_view) {
  views::ViewTracker selected_view_tracker(&selected_view);
  if (!is_ax_menu_start_event_fired_) {
    // Fire the menu start event once, right before the first item is selected.
    // By firing these and the matching kMenuEnd events, we are telling screen
    // readers that the focus is only changing temporarily, and the screen
    // reader will restore the focus back to the appropriate textfield when the
    // menu closes.
    if (!TrackAndRun(
            this,
            [this]() {
              NotifyAccessibilityEventDeprecated(ax::mojom::Event::kMenuStart,
                                                 true);
            },
            [this]() {
              NotifyAccessibilityEventDeprecated(
                  ax::mojom::Event::kMenuPopupStart, true);
            })) {
      return;
    }

    is_ax_menu_start_event_fired_ = true;
  }

  // Ensure the selected view was not destroyed, as e.g. firing native
  // accessibility events on Windows can synchronously re-enter the browser
  // thread and destroy the view.
  // TODO(crbug.com/514228954): Consider removing accessibility event calls.
  if (!selected_view_tracker.view()) {
    return;
  }

  selected_view.GetViewAccessibility().SetPopupFocusOverride();
#if DCHECK_IS_ON()
  constexpr auto kDerivedClasses = base::MakeFixedFlatSet<std::string_view>(
      {"PopupSuggestionView", "PopupPasswordSuggestionView", "PopupFooterView",
       "PopupSeparatorView", "PopupWarningView", "PopupBaseView",
       "PasswordGenerationPopupViewViews::GeneratedPasswordBox", "PopupRowView",
       "PopupRowWithButtonView", "PopupRowContentView", "MdTextButton",
       "PopupBnplFootnoteView", "PopupAtMemoryAiDisclosureView",
       "PopupNoticeView"});
  DCHECK(kDerivedClasses.contains(selected_view.GetClassName()))
      << "If you add a new derived class from AutofillPopupRowView, add it "
         "here and to onSelection(evt) in "
         "chrome/browser/resources/chromeos/accessibility/chromevox/mv2/"
         "background/"
         "event/desktop_automation_handler.js to ensure that ChromeVox "
         "announces the item when selected. Missing class: "
      << selected_view.GetClassName();
#endif
}

void PopupBaseView::OnWidgetBoundsChanged(views::Widget* widget,
                                          const gfx::Rect& new_bounds) {
  CHECK(widget == parent_widget_ || widget == GetWidget());
  if (widget != parent_widget_) {
    return;
  }

  HideController(SuggestionHidingReason::kWidgetChanged);
}

void PopupBaseView::OnWidgetDestroying(views::Widget* widget) {
  // On Windows, widgets can be destroyed in any order. Regardless of which
  // widget is destroyed first, remove all observers and hide the popup.
  CHECK(widget == parent_widget_ || widget == GetWidget());

  // Normally this happens at destruct-time or hide-time, but because it depends
  // on |parent_widget_| (which is about to go away), it needs to happen sooner
  // in this case.
  RemoveWidgetObservers();

  // Because the parent widget is about to be destroyed, we null out the weak
  // reference to it and protect against possibly accessing it during
  // destruction (e.g., by attempting to remove observers).
  parent_widget_ = nullptr;

  HideController(SuggestionHidingReason::kWidgetChanged);
}

void PopupBaseView::RemoveWidgetObservers() {
  if (parent_widget_) {
    parent_widget_->RemoveObserver(this);
  }
  if (views::Widget* widget = GetWidget()) {
    widget->RemoveObserver(this);
  }
  focus_observation_.Reset();
}

void PopupBaseView::UpdateClipPath() {
  SkRect local_bounds = gfx::RectToSkRect(GetLocalBounds());
  SkScalar radius = SkIntToScalar(GetCornerRadius());
  SetClipPath(SkPath::RRect(SkRRect::MakeRectXY(local_bounds, radius, radius)));
}

gfx::Rect PopupBaseView::GetContentAreaBounds() const {
  content::WebContents* web_contents = GetWebContents();
  if (web_contents) {
    return web_contents->GetContainerBounds();
  }

  // If the |web_contents| is null, simply return an empty rect. The most common
  // reason to end up here is that the |web_contents| has been destroyed
  // externally, which can happen at any time. This happens fairly commonly on
  // Windows (e.g., at shutdown) in particular.
  return gfx::Rect();
}

gfx::Rect PopupBaseView::GetTopWindowBounds() const {
  views::Widget* widget = views::Widget::GetTopLevelWidgetForNativeView(
      delegate_->container_view());
  // Find root in window tree.
  while (widget && widget->parent()) {
    widget = widget->parent();
  }
  if (widget) {
    return widget->GetWindowBoundsInScreen();
  }

  // If the widget is null, simply return an empty rect. The most common reason
  // to end up here is that the NativeView has been destroyed externally, which
  // can happen at any time. This happens fairly commonly on Windows (e.g., at
  // shutdown) in particular.
  return gfx::Rect();
}

gfx::Rect PopupBaseView::GetOptimalPositionAndPlaceArrowOnPopup(
    const gfx::Rect& element_bounds,
    const gfx::Rect& visible_content_area_bounds,
    const gfx::Size& preferred_size,
    base::span<const views::BubbleArrowSide> preferred_popup_sides) {
  views::BubbleBorder* border = static_cast<views::BubbleBorder*>(
      GetWidget()->GetRootView()->GetBorder());
  DCHECK(border);

  gfx::Rect popup_bounds;

  // Deduce the arrow and the position.
  views::BubbleBorder::Arrow arrow = GetOptimalPopupPlacement(
      /*visible_content_area_bounds=*/visible_content_area_bounds,
      /*element_bounds=*/element_bounds,
      /*popup_preferred_size=*/preferred_size,
      /*right_to_left=*/delegate_->GetElementTextDirection() ==
          base::i18n::TextDirection::RIGHT_TO_LEFT,
      /*scrollbar_width=*/gfx::scrollbar_size(),
      /*maximum_pixel_offset_to_center=*/
      kMaximumPixelsToMoveSuggestionToCenter,
      /*maximum_width_percentage_to_center=*/
      kMaximumWidthPercentageToMoveTheSuggestionToCenter,
      /*popup_bounds=*/popup_bounds, preferred_popup_sides,
      /*anchor_type=*/delegate_->anchor_type());

  // Those values are not supported for adding an arrow.
  // Currently, they can not be returned by GetOptimalPopupPlacement().
  DCHECK(arrow != views::BubbleBorder::Arrow::NONE);
  DCHECK(arrow != views::BubbleBorder::Arrow::FLOAT);

  if (show_arrow_pointer_) {
    // Set the arrow position to the border.
    border->set_arrow(arrow);
    border->AddArrowToBubbleCornerAndPointTowardsAnchor(
        element_bounds, popup_bounds,
        visible_content_area_bounds.y() - kMaxPopupWebContentsTopYOverflow);
  }

  return popup_bounds;
}

bool PopupBaseView::DoUpdateBoundsAndRedrawPopup() {
  gfx::Size preferred_size = GetPreferredSize();
  const gfx::Rect content_area_bounds = GetContentAreaBounds();
  // TODO(crbug.com/40799454) Once popups can render outside the main window on
  // Linux, use the screen bounds.
  const gfx::Rect top_window_bounds = GetTopWindowBounds();
  const gfx::Rect& max_bounds_for_popup =
      PopupMayExceedContentAreaBounds(GetWebContents()) ? top_window_bounds
                                                        : content_area_bounds;

  // Intersect with the current monitor's work area to avoid showing popups
  // outside the screen.
  gfx::Rect visible_content_area_bounds =
      IntersectWithDisplayBounds(GetWebContents(), max_bounds_for_popup);

  gfx::Rect element_bounds = gfx::ToEnclosingRect(delegate_->element_bounds());

  // An element is never outside the content area if it is contained by the
  // `visible_content_area_bounds`. This also applies if the element is empty,
  // which means that either the height or the width is 0. An element can be
  // empty in case the popup is anchored to a caret, which has a 0 width.
  if (!visible_content_area_bounds.Contains(element_bounds)) {
    // If the element exceeds the visible content area, ensure that the popup
    // is still visually attached to the input element.
    element_bounds.Intersect(visible_content_area_bounds);
    if (element_bounds.IsEmpty()) {
      HideController(SuggestionHidingReason::kElementOutsideOfContentArea);
      return false;
    }
  }
  // Consider the element is |kElementBorderPadding| pixels larger at the top
  // and at the bottom in order to reposition the dropdown, so that it doesn't
  // look too close to the element.
  element_bounds.Inset(
      gfx::Insets::VH(/*vertical=*/-kElementBorderPadding, /*horizontal=*/0));

  // At least one row of the popup should be shown in the bounds of the
  // visible content area so that the user notices the presence of the popup.
  int item_height =
      children().size() > 0 ? children()[0]->GetPreferredSize().height() : 0;
  // That function checks whether the popup element has enough place to render
  // either to the top of the focused element or to the bottom of the focused
  // element.
  if (!CanShowDropdownHere(item_height, visible_content_area_bounds,
                           element_bounds)) {
    HideController(SuggestionHidingReason::kInsufficientSpace);
    return false;
  }

  gfx::Rect popup_bounds = GetOptimalPositionAndPlaceArrowOnPopup(
      element_bounds, visible_content_area_bounds, preferred_size,
      kDefaultPreferredPopupSides);

  if (OverlapsWithAnotherPrompt(popup_bounds)) {
    HideController(SuggestionHidingReason::kOverlappingWithAnotherPrompt);
    return false;
  }

  if (BoundsOverlapWithPictureInPictureWindow(popup_bounds)) {
    HideController(
        SuggestionHidingReason::kOverlappingWithPictureInPictureWindow);
    return false;
  }

  // Account for the scroll view's border so that the content has enough space.
  popup_bounds.Inset(-GetWidget()->GetRootView()->GetInsets());
  GetWidget()->SetBounds(popup_bounds);

  DeprecatedLayoutImmediately();
  UpdateClipPath();
  SchedulePaint();
  return true;
}

bool PopupBaseView::OverlapsWithAnotherPrompt(
    const gfx::Rect& popup_bounds) const {
  content::WebContents* web_contents = GetWebContents();
  if (!web_contents) {
    return false;
  }

  if (BoundsOverlapWithAnyOpenPrompt(popup_bounds, web_contents)) {
    return true;
  }
  // On Windows, due to platform-specific implementation details, the previous
  // check isn't reliable, and fails to detect open prompts. Since the most
  // critical bubble is the permission bubble, we check for that specifically.
  if (BoundsOverlapWithOpenPermissionsPrompt(popup_bounds, web_contents)) {
    return true;
  }

  if (base::FeatureList::IsEnabled(
          features::kAutofillPopupCheckHtmlFormPopupOverlap)) {
    if (BoundsOverlapWithHtmlFormPopup(popup_bounds, web_contents)) {
      return true;
    }
  }

  return false;
}

void PopupBaseView::OnNativeFocusChanged(gfx::NativeView focused_now) {
  // TODO(crbug.com/330303918): The focus change is triggered sometimes
  // (reproduced on a Linux release build, on a debug one - no) with
  // `focused_now` == `nullptr` during activatable popup opening, no other
  // widget gets focus then and this widget remains active.
  // The `!GetWidget()->IsActive()` piece handles this case and prevents
  // immediate popup closing.
  // Investigate the reason and either fix it on the appropriate side or make
  // this TODO a regular comment if it works as intended.
  if (GetWidget() && GetWidget()->GetNativeView() != focused_now &&
      !GetWidget()->IsActive()) {
    HideController(SuggestionHidingReason::kFocusChanged);
  }
}

void PopupBaseView::HideController(SuggestionHidingReason reason) {
  if (delegate_) {
    delegate_->Hide(reason);
  }
  // This will eventually result in the deletion of |this|, as the delegate
  // will hide |this|. See |DoHide| above for an explanation on why the precise
  // timing of that deletion is tricky.
}

content::WebContents* PopupBaseView::GetWebContents() const {
  if (!delegate_) {
    return nullptr;
  }

  return delegate_->GetWebContents();
}

BEGIN_METADATA(PopupBaseView)
ADD_READONLY_PROPERTY_METADATA(gfx::Rect, ContentAreaBounds)
END_METADATA

}  // namespace autofill
