// Copyright 2012 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/find_bar_host.h"

#include <algorithm>

#include "base/check_is_test.h"
#include "base/i18n/rtl.h"
#include "build/build_config.h"
#include "chrome/browser/enterprise/data_protection/data_protection_clipboard_utils.h"
#include "chrome/browser/ui/find_bar/find_bar_controller.h"
#include "chrome/browser/ui/view_ids.h"
#include "chrome/browser/ui/views/find_bar_view.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/theme_copying_widget.h"
#include "components/find_in_page/find_tab_helper.h"
#include "components/find_in_page/find_types.h"
#include "components/strings/grit/components_strings.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_user_data.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/compositor/layer.h"
#include "ui/events/event.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/gfx/animation/slide_animation.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/views/border.h"
#include "ui/views/focus/external_focus_tracker.h"
#include "ui/views/widget/root_view.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"

#if BUILDFLAG(IS_WIN)
#include <windows.h>
#endif

using input::NativeWebKeyboardEvent;

// During testing we can disable animations by setting this flag to true,
// so that opening and closing the dropdown bar is shown instantly, instead of
// having to poll it while it animates to open/closed status.
static bool g_disable_animation_for_test_ = false;

namespace {

class FindBarHostHelper
    : public content::WebContentsUserData<FindBarHostHelper> {
 public:
  static FindBarHostHelper* CreateOrGetFromWebContents(
      content::WebContents* web_contents) {
    CreateForWebContents(web_contents);
    return FromWebContents(web_contents);
  }

  void SetExternalFocusTracker(
      std::unique_ptr<views::ExternalFocusTracker> external_focus_tracker) {
    external_focus_tracker_ = std::move(external_focus_tracker);
  }

  std::unique_ptr<views::ExternalFocusTracker> TakeExternalFocusTracker() {
    return std::move(external_focus_tracker_);
  }

  views::ExternalFocusTracker* focus_tracker() {
    return external_focus_tracker_.get();
  }

 private:
  friend class content::WebContentsUserData<FindBarHostHelper>;

  explicit FindBarHostHelper(content::WebContents* web_contents)
      : content::WebContentsUserData<FindBarHostHelper>(*web_contents) {}

  std::unique_ptr<views::ExternalFocusTracker> external_focus_tracker_;

  WEB_CONTENTS_USER_DATA_KEY_DECL();
};

WEB_CONTENTS_USER_DATA_KEY_IMPL(FindBarHostHelper);

gfx::Rect GetLocationForFindBarView(gfx::Rect view_location,
                                    const gfx::Rect& clipping_box,
                                    const gfx::Rect& avoid_overlapping_rect) {
  // Clamp to the `clipping_box`.
  view_location.set_width(
      std::min(view_location.width(), clipping_box.width()));
  if (base::i18n::IsRTL()) {
    int boundary = clipping_box.width() - view_location.width();
    view_location.set_x(std::min(view_location.x(), boundary));
  } else {
    view_location.set_x(std::max(view_location.x(), clipping_box.x()));
  }
  view_location.set_y(std::max(view_location.y(), clipping_box.y()));

  gfx::Rect new_pos = view_location;

  // The minimum space between the FindInPage window and the search result.
  constexpr int kMinFindWndDistanceFromSelection = 5;

  // If the selection rectangle intersects the current position on screen then
  // we try to move our dialog to the left (right for RTL) of the selection
  // rectangle.
  if (!avoid_overlapping_rect.IsEmpty() &&
      avoid_overlapping_rect.Intersects(new_pos)) {
    if (base::i18n::IsRTL()) {
      new_pos.set_x(avoid_overlapping_rect.x() +
                    avoid_overlapping_rect.width() +
                    (2 * kMinFindWndDistanceFromSelection));

      // If we moved it to be clipped on the right, we won't move it at all.
      if (new_pos.x() + new_pos.width() > clipping_box.width()) {
        new_pos = view_location;  // Reset.
      }
    } else {
      new_pos.set_x(avoid_overlapping_rect.x() - new_pos.width() -
                    kMinFindWndDistanceFromSelection);

      // If we moved it off-screen to the left, we won't move it at all.
      if (new_pos.x() < 0) {
        new_pos = view_location;  // Reset.
      }
    }
  }

  return new_pos;
}

}  // namespace

////////////////////////////////////////////////////////////////////////////////
// FindBarHost, public:

FindBarHost::FindBarHost(FindBarOwner* find_bar_owner)
    : AnimationDelegateViews(find_bar_owner->GetOwnerWidget()),
      find_bar_owner_(find_bar_owner) {
  auto find_bar_view = std::make_unique<FindBarView>(this);
  // The |clip_view| exists to paint to a layer so that it can clip descendent
  // Views which also paint to a Layer. See http://crbug.com/41240976
  auto clip_view = std::make_unique<views::View>();
  clip_view->SetPaintToLayer();
  clip_view->layer()->SetFillsBoundsOpaquely(false);
  clip_view->layer()->SetMasksToBounds(true);
  view_ = clip_view->AddChildView(std::move(find_bar_view));

  // Initialize the host.
  host_ =
      std::make_unique<ThemeCopyingWidget>(find_bar_owner_->GetOwnerWidget());
  views::Widget::InitParams params(
      views::Widget::InitParams::CLIENT_OWNS_WIDGET,
      views::Widget::InitParams::TYPE_CONTROL);
  params.delegate = this;
  params.name = "FindBarHost";
  params.parent = find_bar_owner_->GetWidgetForAnchoring()->GetNativeView();
  params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
  // The FindBarHost is intentionally not activatable.
  //
  // Don't use Activatable::kYes. It caused a series of other issues on macOS:
  // 1. Virtual Desktop Switching: When the find bar closes, macOS attempts to
  //    restore focus to the previously active window. This heuristic can
  //    incorrectly switch to a different space (Virtual Desktop) where another
  //    chrome window resides (crbug.com/40205173, crbug.com/40147557).
  // 2. Broken Browser Shortcuts: Since the find bar steals the "key window"
  //    status, the browser window stops receiving shortcuts like Cmd+D
  //    (crbug.com/40694525).
  // 3. Focus Loss: Handing focus back and forth can fail, leading to lost
  //    focus (crbug.com/422444253).
  // 4. Mouse Interaction Issues: Clicking on web content requires two clicks
  //    (one to re-activate the browser window) (crbug.com/442293378).
  //
  // Instead, we keep it as Activatable::kNo.
  //
  // With Activatable::kNo, the browser window remains the OS-level key window
  // and receives keyboard events. The FocusManager tracks focus; if the find
  // bar has focus, the FocusManager routes events from the browser window to
  // the find bar view, even though they are in different widgets. We manually
  // activate the browser window when clicking on the find bar textfield (see
  // ActivateOwnerWidgetIfNecessary) to ensure the browser window receives
  // these events.
  params.activatable = views::Widget::InitParams::Activatable::kNo;
  host_->Init(std::move(params));
  host_->SetContentsView(std::move(clip_view));

  // Start listening to focus changes, so we can register and unregister our
  // own handler for Escape.
  focus_manager_ = host_->GetFocusManager();
  focus_manager_observation_.Observe(focus_manager_.get());

  animation_ = std::make_unique<gfx::SlideAnimation>(this);
  if (!gfx::Animation::ShouldRenderRichAnimation()) {
    animation_->SetSlideDuration(base::TimeDelta());
  }

  // Update the widget and |view_| bounds to the hidden state.
  AnimationProgressed(animation_.get());
  SetAccessibleWindowRole(ax::mojom::Role::kDialog);
}

FindBarHost::~FindBarHost() {
  focus_tracker_.reset();
}

bool FindBarHost::MaybeForwardKeyEventToWebpage(const ui::KeyEvent& key_event) {
  switch (key_event.key_code()) {
    case ui::VKEY_DOWN:
    case ui::VKEY_UP:
    case ui::VKEY_PRIOR:
    case ui::VKEY_NEXT:
      break;
    case ui::VKEY_HOME:
    case ui::VKEY_END:
      if (key_event.IsControlDown()) {
        break;
      }
      [[fallthrough]];
    default:
      return false;
  }

  if (!web_contents()) {
    return false;
  }

  // Make sure we don't have a text field element interfering with keyboard
  // input. Otherwise Up and Down arrow key strokes get eaten. "Nom Nom Nom".
  web_contents()->ClearFocusedElement();
  NativeWebKeyboardEvent event(key_event);
  web_contents()
      ->GetPrimaryMainFrame()
      ->GetRenderViewHost()
      ->GetWidget()
      ->ForwardKeyboardEventWithLatencyInfo(event, *key_event.latency());
  return true;
}

void FindBarHost::ActivateOwnerWidgetIfNecessary() {
  // See crbug.com/40616214.
  views::Widget* widget = find_bar_owner()->GetOwnerWidget();
  if (widget && !widget->IsActive()) {
    widget->Activate();
  }
}

bool FindBarHost::IsVisible() const {
  return is_visible_;
}

views::Widget* FindBarHost::GetHostWidget() {
  return host_.get();
}

FindBarController* FindBarHost::GetFindBarController() const {
  return find_bar_controller_;
}

bool FindBarHost::HasFocus() const {
  return view_->ContainsFocus();
}

void FindBarHost::CloseOverlappingBubbles() {
  find_bar_owner_->CloseOverlappingBubbles();
}

void FindBarHost::SetFindBarController(FindBarController* find_bar_controller) {
  find_bar_controller_ = find_bar_controller;

  if (GetWidget()) {
    GetWidget()->UpdateAccessibleNameForRootView();
  }
}

void FindBarHost::Show(bool animate, bool focus) {
  RestoreOrCreateFocusTracker();
  DCHECK(host_);

  SetDialogPosition(GetDialogPosition(gfx::Rect()));

  // If we're in the middle of a close animation, stop it and skip to the end.
  // This ensures that the state is consistent and prepared to show the drop-
  // down bar.
  if (animation_->IsClosing()) {
    animation_->End();
  }

  if (focus) {
    host_->Show();
  } else {
    host_->ShowInactive();
  }

  bool was_visible = is_visible_;
  is_visible_ = true;
  if (!animate || g_disable_animation_for_test_) {
    animation_->Reset(1);
    AnimationProgressed(animation_.get());
  } else if (!was_visible) {
    // Don't re-start the animation.
    animation_->Reset();
    animation_->Show();
  }

  if (!was_visible) {
    OnVisibilityChanged();
  }
}

void FindBarHost::Hide(bool animate) {
  // Restore/Save is non-symmetric as hiding the FindBarHost could change
  // the focus state of the external view. Saving the focus tracker before the
  // hide preserves the appropriate view in the event the FindBarHost visibility
  // is restored as part of a tab change.
  SaveFocusTracker();
  if (!is_visible_) {
    return;
  }

  if (animate && !g_disable_animation_for_test_ && !animation_->IsClosing()) {
    animation_->Hide();
  } else {
    if (animation_->IsClosing()) {
      // If we're in the middle of a close animation, skip immediately to the
      // end of the animation.
      animation_->End();
    } else {
      // Otherwise we need to set both the animation state to ended and the
      // DropdownBarHost state to ended/hidden, otherwise the next time we try
      // to show the bar, it might refuse to do so. Note that we call
      // AnimationEnded ourselves as Reset does not call it if we are not
      // animating here.
      animation_->Reset();
      AnimationEnded(animation_.get());
    }
  }
}

void FindBarHost::SetFocusAndSelection() {
  view_->FocusAndSelectAll();
  SetFindBarIsFocusedOnCurrentTab(true);
}

void FindBarHost::ClearResults(
    const find_in_page::FindNotificationDetails& results) {
  view_->UpdateForResult(results, std::u16string());
}

void FindBarHost::StopAnimation() {
  animation_->End();
}

void FindBarHost::MoveWindowIfNecessary() {
  MoveWindowIfNecessaryWithRect(gfx::Rect());
}

void FindBarHost::SetFindTextAndSelectedRange(
    const std::u16string& find_text,
    const gfx::Range& selected_range) {
  view_->SetFindTextAndSelectedRange(find_text, selected_range);
}

std::u16string_view FindBarHost::GetFindText() const {
  return view_->GetFindText();
}

gfx::Range FindBarHost::GetSelectedRange() const {
  return view_->GetSelectedRange();
}

void FindBarHost::UpdateUIForFindResult(
    const find_in_page::FindNotificationDetails& result,
    const std::u16string& find_text) {
  if (!find_text.empty()) {
    view_->UpdateForResult(result, find_text);
  } else {
    view_->ClearMatchCount();
  }

  // We now need to check if the window is obscuring the search results.
  MoveWindowIfNecessaryWithRect(result.selection_rect());

  // Once we find a match we no longer want to keep track of what had
  // focus. EndFindSession will then set the focus to the page content.
  if (result.number_of_matches() > 0) {
    focus_tracker_.reset();
  }
}

void FindBarHost::AudibleAlert() {
  ++audible_alerts_;
#if BUILDFLAG(IS_WIN)
  MessageBeep(MB_OK);
#endif
}

bool FindBarHost::IsFindBarVisible() const {
  return is_visible_;
}

void FindBarHost::RestoreSavedFocus() {
  SetFindBarIsFocusedOnCurrentTab(false);

  std::unique_ptr<views::ExternalFocusTracker> focus_tracker_from_web_contents;
  views::ExternalFocusTracker* tracker = focus_tracker_.get();
  if (!tracker && web_contents()) {
    auto* helper = FindBarHostHelper::FromWebContents(web_contents());
    if (helper) {
      focus_tracker_from_web_contents = helper->TakeExternalFocusTracker();
      tracker = focus_tracker_from_web_contents.get();
    }
  }

  if (tracker) {
    tracker->FocusLastFocusedExternalView();
    focus_tracker_.reset();
  } else {
    // TODO(brettw): Focus() should be on WebContentsView.
    web_contents()->Focus();
  }
}

bool FindBarHost::HasGlobalFindPasteboard() const {
#if BUILDFLAG(IS_MAC)
  return true;
#else
  return false;
#endif
}

void FindBarHost::UpdateFindBarForChangedWebContents() {
  if (GetWidget()) {
    GetWidget()->UpdateAccessibleNameForRootView();
  }
}

bool FindBarHost::CanPopulateFromSelectedText() {
  return !web_contents() ||
         enterprise_data_protection::CanPopulateFindBarFromSelection(
             web_contents());
}

const FindBarTesting* FindBarHost::GetFindBarTesting() const {
  return this;
}

////////////////////////////////////////////////////////////////////////////////
// FindBarWin, ui::AcceleratorTarget implementation:

bool FindBarHost::AcceleratorPressed(const ui::Accelerator& accelerator) {
  ui::KeyboardCode key = accelerator.key_code();
  if (key == ui::VKEY_RETURN && accelerator.IsCtrlDown()) {
    // Ctrl+Enter closes the Find session and navigates any link that is active.
    find_bar_controller_->EndFindSession(
        find_in_page::SelectionAction::kActivate,
        find_in_page::ResultAction::kClear);
    return true;
  }

  CHECK_EQ(key, ui::VKEY_ESCAPE);
  // This will end the Find session and hide the window, causing it to loose
  // focus and in the process unregister us as the handler for the Escape
  // accelerator through the OnWillChangeFocus event.
  find_bar_controller_->EndFindSession(find_in_page::SelectionAction::kKeep,
                                       find_in_page::ResultAction::kKeep);
  return true;
}

bool FindBarHost::CanHandleAccelerators() const {
  return true;
}

////////////////////////////////////////////////////////////////////////////////
// FindBarTesting implementation:

bool FindBarHost::GetFindBarWindowInfo(gfx::Point* position,
                                       bool* fully_visible) const {
  if (!find_bar_controller_) {
    if (position) {
      *position = gfx::Point();
    }
    if (fully_visible) {
      *fully_visible = false;
    }
    return false;
  }

  gfx::Rect window_rect = host_->GetWindowBoundsInScreen();
  if (position) {
    *position = window_rect.origin();
  }
  if (fully_visible) {
    *fully_visible = is_visible_ && !animation_->is_animating();
  }
  return true;
}

std::u16string_view FindBarHost::GetFindSelectedText() const {
  return view_->GetFindSelectedText();
}

std::u16string_view FindBarHost::GetMatchCountText() const {
  return view_->GetMatchCountText();
}

int FindBarHost::GetContentsWidth() const {
  return view_->GetContentsBounds().width();
}

size_t FindBarHost::GetAudibleAlertCount() const {
  return audible_alerts_;
}

std::u16string FindBarHost::GetAccessibleWindowTitle() const {
  // This can be called in tests by AccessibilityChecker before the controller
  // is registered with this object. So to handle that case, we need to bail out
  // if there is no controller.
  const FindBarController* const controller = GetFindBarController();
  if (!controller) {
    return std::u16string();
  }
  return find_bar_owner_->GetFindBarAccessibleWindowTitle();
}

FindBarView* FindBarHost::GetFindBarViewForTesting() {
  CHECK_IS_TEST();
  return view_;
}

base::AutoReset<bool> FindBarHost::SetEnableAnimationsForTesting(
    bool enable_animation) {
  CHECK_IS_TEST();
  return base::AutoReset<bool>(&g_disable_animation_for_test_,
                               !enable_animation);
}
////////////////////////////////////////////////////////////////////////////////
// private:

void FindBarHost::GetWidgetPositionNative(gfx::Rect* avoid_overlapping_rect) {
  gfx::Rect frame_rect = host_->GetTopLevelWidget()->GetWindowBoundsInScreen();
  gfx::Rect webcontents_rect = web_contents()->GetViewBounds();
  avoid_overlapping_rect->Offset(0, webcontents_rect.y() - frame_rect.y());
}

void FindBarHost::MoveWindowIfNecessaryWithRect(
    const gfx::Rect& selection_rect) {
  // We only move the window if one is active for the current WebContents. If we
  // don't check this, then SetDialogPosition below will end up making the Find
  // Bar visible.
  if (!web_contents()) {
    return;
  }

  find_in_page::FindTabHelper* find_tab_helper =
      find_in_page::FindTabHelper::FromWebContents(web_contents());
  if (!find_tab_helper || !find_tab_helper->find_ui_active()) {
    return;
  }

  gfx::Rect new_pos = GetDialogPosition(selection_rect);
  SetDialogPosition(new_pos);
}

void FindBarHost::SaveFocusTracker() {
  if (!web_contents()) {
    return;
  }

  if (focus_tracker_) {
    focus_tracker_->SetFocusManager(nullptr);
    FindBarHostHelper::CreateOrGetFromWebContents(web_contents())
        ->SetExternalFocusTracker(std::move(focus_tracker_));
  }
}

void FindBarHost::RestoreOrCreateFocusTracker() {
  if (!web_contents()) {
    return;
  }

  std::unique_ptr<views::ExternalFocusTracker> focus_tracker =
      FindBarHostHelper::CreateOrGetFromWebContents(web_contents())
          ->TakeExternalFocusTracker();
  if (focus_tracker) {
    focus_tracker_ = std::move(focus_tracker);
    focus_tracker_->SetFocusManager(host_->GetFocusManager());
  } else {
    focus_tracker_ =
        std::make_unique<views::ExternalFocusTracker>(view_, focus_manager_);
  }
}

void FindBarHost::SetFindBarIsFocusedOnCurrentTab(bool focus) {
  if (web_contents()) {
    find_in_page::FindTabHelper::FromWebContents(web_contents())
        ->set_find_ui_focused(focus);
  }
}

void FindBarHost::OnVisibilityChanged() {
  // Tell the immersive mode controller about the find bar's bounds. The
  // immersive mode controller uses the bounds to keep the top-of-window views
  // revealed when the mouse is hovered over the find bar.
  gfx::Rect visible_bounds;
  if (is_visible_) {
    visible_bounds = host_->GetWindowBoundsInScreen();
  }
  find_bar_owner_->OnFindBarVisibilityChanged(visible_bounds);
}

void FindBarHost::RegisterAccelerators() {
  DCHECK(!esc_accel_target_registered_);
  ui::Accelerator escape(ui::VKEY_ESCAPE, ui::EF_NONE);
  focus_manager_->RegisterAccelerator(
      escape, ui::AcceleratorManager::kNormalPriority, this);
  esc_accel_target_registered_ = true;
  // Register for Ctrl+Return.
  ui::Accelerator ctrl_ret(ui::VKEY_RETURN, ui::EF_CONTROL_DOWN);
  focus_manager_->RegisterAccelerator(
      ctrl_ret, ui::AcceleratorManager::kNormalPriority, this);
}

void FindBarHost::UnregisterAccelerators() {
  // Unregister Ctrl+Return.
  ui::Accelerator ctrl_ret(ui::VKEY_RETURN, ui::EF_CONTROL_DOWN);
  focus_manager_->UnregisterAccelerator(ctrl_ret, this);

  DCHECK(esc_accel_target_registered_);
  ui::Accelerator escape(ui::VKEY_ESCAPE, ui::EF_NONE);
  focus_manager_->UnregisterAccelerator(escape, this);
  esc_accel_target_registered_ = false;
}

gfx::Rect FindBarHost::GetDialogPosition(gfx::Rect avoid_overlapping_rect) {
  // Find the area we have to work with (after accounting for scrollbars, etc).
  // The owner does layout for the components that we care about
  // positioning relative to, so we ask it to tell us where we should go.
  gfx::Rect find_bar_bounds = find_bar_owner_->GetFindBarBoundingBox();
  if (find_bar_bounds.IsEmpty()) {
    return gfx::Rect();
  }

  // Ask the view how large an area it needs to draw on.
  gfx::Size prefsize = view_->GetPreferredSize();

  // Don't show the find bar if |widget_bounds| is not tall enough to fit.
  gfx::Insets insets = view_->GetInsets();
  if (find_bar_bounds.height() < prefsize.height() - insets.height()) {
    return gfx::Rect();
  }

  // Place the view in the top right corner of the widget boundaries (top left
  // for RTL languages). Adjust for the view insets to ensure the border lines
  // up with the location bar.
  int x = find_bar_bounds.x() - insets.left();
  if (!base::i18n::IsRTL()) {
    x += find_bar_bounds.width() - prefsize.width() + insets.width();
  }
  int y = find_bar_bounds.y() - insets.top();
  const gfx::Rect view_location(x, y, prefsize.width(), prefsize.height());

  // When we get Find results back, we specify a selection rect, which we
  // should strive to avoid overlapping. But first, we need to offset the
  // selection rect (if one was provided).
  if (!avoid_overlapping_rect.IsEmpty()) {
    // For comparison (with the Intersects function below) we need to account
    // for the fact that we draw the Find widget relative to the Chrome frame,
    // whereas the selection rect is relative to the page.
    GetWidgetPositionNative(&avoid_overlapping_rect);
  }

  gfx::Rect clipping_box = find_bar_owner_->GetFindBarClippingBox();

  return GetLocationForFindBarView(view_location, clipping_box,
                                   avoid_overlapping_rect);
}

void FindBarHost::SetDialogPosition(const gfx::Rect& new_pos) {
  view_->SetSize(new_pos.size());

  if (new_pos.IsEmpty()) {
    return;
  }

  host_->SetBounds(new_pos);

  find_bar_owner_->OnFindBarVisibilityChanged(host_->GetWindowBoundsInScreen());
}

void FindBarHost::OnWillChangeFocus(views::View* focused_before,
                                    views::View* focused_now) {
  // First we need to determine if one or both of the views passed in are child
  // views of our view.
  bool our_view_before = focused_before && view_->Contains(focused_before);
  bool our_view_now = focused_now && view_->Contains(focused_now);

  // When both our_view_before and our_view_now are false, it means focus is
  // changing hands elsewhere in the application (and we shouldn't do anything).
  // Similarly, when both are true, focus is changing hands within the dropdown
  // widget (and again, we should not do anything). We therefore only need to
  // look at when we gain initial focus and when we loose it.
  if (!our_view_before && our_view_now) {
    // We are gaining focus from outside the dropdown widget so we must register
    // a handler for Escape.
    RegisterAccelerators();
    SetFindBarIsFocusedOnCurrentTab(true);
  } else if (our_view_before && !our_view_now) {
    // We are losing focus to something outside our widget so we restore the
    // original handler for Escape.
    UnregisterAccelerators();
  }

  if (!our_view_now) {
    SetFindBarIsFocusedOnCurrentTab(false);
  }
}

void FindBarHost::AnimationProgressed(const gfx::Animation* animation) {
  // First, we calculate how many pixels to slide the widget.
  gfx::Size pref_size = view_->GetPreferredSize();
  int view_offset = static_cast<int>((animation_->GetCurrentValue() - 1.0) *
                                     pref_size.height());

  // This call makes sure |view_| appears in the right location, the size and
  // shape is correct and that it slides in the right direction.
  view_->SetPosition(gfx::Point(0, view_offset));
}

void FindBarHost::AnimationEnded(const gfx::Animation* animation) {
  // Ensure the position gets a final update.  This is important when ending the
  // animation early (e.g. closing a tab with an open find bar), since otherwise
  // the position will be out of date at the start of the next animation.
  AnimationProgressed(animation);

  if (!animation_->IsShowing()) {
    // Animation has finished closing.
    DCHECK(host_);
    host_->Hide();
    is_visible_ = false;
    OnVisibilityChanged();
  }
}
