Home | History | Annotate | Download | only in find_bar
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/ui/find_bar/find_bar_controller.h"
      6 
      7 #include <algorithm>
      8 
      9 #include "base/i18n/rtl.h"
     10 #include "base/logging.h"
     11 #include "build/build_config.h"
     12 #include "chrome/browser/chrome_notification_types.h"
     13 #include "chrome/browser/profiles/profile.h"
     14 #include "chrome/browser/ui/find_bar/find_bar.h"
     15 #include "chrome/browser/ui/find_bar/find_bar_state.h"
     16 #include "chrome/browser/ui/find_bar/find_bar_state_factory.h"
     17 #include "chrome/browser/ui/find_bar/find_tab_helper.h"
     18 #include "content/public/browser/navigation_details.h"
     19 #include "content/public/browser/navigation_entry.h"
     20 #include "content/public/browser/notification_details.h"
     21 #include "content/public/browser/notification_source.h"
     22 #include "content/public/browser/web_contents.h"
     23 #include "ui/gfx/rect.h"
     24 
     25 using content::NavigationController;
     26 using content::WebContents;
     27 
     28 // The minimum space between the FindInPage window and the search result.
     29 static const int kMinFindWndDistanceFromSelection = 5;
     30 
     31 FindBarController::FindBarController(FindBar* find_bar)
     32     : find_bar_(find_bar),
     33       web_contents_(NULL),
     34       last_reported_matchcount_(0) {
     35 }
     36 
     37 FindBarController::~FindBarController() {
     38   DCHECK(!web_contents_);
     39 }
     40 
     41 void FindBarController::Show() {
     42   FindTabHelper* find_tab_helper =
     43       FindTabHelper::FromWebContents(web_contents_);
     44 
     45   // Only show the animation if we're not already showing a find bar for the
     46   // selected WebContents.
     47   if (!find_tab_helper->find_ui_active()) {
     48     MaybeSetPrepopulateText();
     49 
     50     find_tab_helper->set_find_ui_active(true);
     51     find_bar_->Show(true);
     52   }
     53   find_bar_->SetFocusAndSelection();
     54 }
     55 
     56 void FindBarController::EndFindSession(SelectionAction selection_action,
     57                                        ResultAction result_action) {
     58   find_bar_->Hide(true);
     59 
     60   // |web_contents_| can be NULL for a number of reasons, for example when the
     61   // tab is closing. We must guard against that case. See issue 8030.
     62   if (web_contents_) {
     63     FindTabHelper* find_tab_helper =
     64         FindTabHelper::FromWebContents(web_contents_);
     65 
     66     // When we hide the window, we need to notify the renderer that we are done
     67     // for now, so that we can abort the scoping effort and clear all the
     68     // tickmarks and highlighting.
     69     find_tab_helper->StopFinding(selection_action);
     70 
     71     if (result_action == kClearResultsInFindBox)
     72       find_bar_->ClearResults(find_tab_helper->find_result());
     73 
     74     // When we get dismissed we restore the focus to where it belongs.
     75     find_bar_->RestoreSavedFocus();
     76   }
     77 }
     78 
     79 void FindBarController::ChangeWebContents(WebContents* contents) {
     80   if (web_contents_) {
     81     registrar_.RemoveAll();
     82     find_bar_->StopAnimation();
     83 
     84     FindTabHelper* find_tab_helper =
     85         FindTabHelper::FromWebContents(web_contents_);
     86     if (find_tab_helper)
     87       find_tab_helper->set_selected_range(find_bar_->GetSelectedRange());
     88   }
     89 
     90   web_contents_ = contents;
     91   FindTabHelper* find_tab_helper =
     92       web_contents_ ? FindTabHelper::FromWebContents(web_contents_)
     93                     : NULL;
     94 
     95   // Hide any visible find window from the previous tab if a NULL tab contents
     96   // is passed in or if the find UI is not active in the new tab.
     97   if (find_bar_->IsFindBarVisible() &&
     98       (!find_tab_helper || !find_tab_helper->find_ui_active())) {
     99     find_bar_->Hide(false);
    100   }
    101 
    102   if (!web_contents_)
    103     return;
    104 
    105   registrar_.Add(this,
    106                  chrome::NOTIFICATION_FIND_RESULT_AVAILABLE,
    107                  content::Source<WebContents>(web_contents_));
    108   registrar_.Add(
    109       this,
    110       content::NOTIFICATION_NAV_ENTRY_COMMITTED,
    111       content::Source<NavigationController>(&web_contents_->GetController()));
    112 
    113   MaybeSetPrepopulateText();
    114 
    115   if (find_tab_helper && find_tab_helper->find_ui_active()) {
    116     // A tab with a visible find bar just got selected and we need to show the
    117     // find bar but without animation since it was already animated into its
    118     // visible state. We also want to reset the window location so that
    119     // we don't surprise the user by popping up to the left for no apparent
    120     // reason.
    121     find_bar_->Show(false);
    122   }
    123 
    124   UpdateFindBarForCurrentResult();
    125   find_bar_->UpdateFindBarForChangedWebContents();
    126 }
    127 
    128 ////////////////////////////////////////////////////////////////////////////////
    129 // FindBarHost, content::NotificationObserver implementation:
    130 
    131 void FindBarController::Observe(int type,
    132                                 const content::NotificationSource& source,
    133                                 const content::NotificationDetails& details) {
    134   FindTabHelper* find_tab_helper =
    135       FindTabHelper::FromWebContents(web_contents_);
    136   if (type == chrome::NOTIFICATION_FIND_RESULT_AVAILABLE) {
    137     // Don't update for notifications from WebContentses other than the one we
    138     // are actively tracking.
    139     if (content::Source<WebContents>(source).ptr() == web_contents_) {
    140       UpdateFindBarForCurrentResult();
    141       if (find_tab_helper->find_result().final_update() &&
    142           find_tab_helper->find_result().number_of_matches() == 0) {
    143         const base::string16& last_search =
    144             find_tab_helper->previous_find_text();
    145         const base::string16& current_search = find_tab_helper->find_text();
    146         if (last_search.find(current_search) != 0)
    147           find_bar_->AudibleAlert();
    148       }
    149     }
    150   } else if (type == content::NOTIFICATION_NAV_ENTRY_COMMITTED) {
    151     NavigationController* source_controller =
    152         content::Source<NavigationController>(source).ptr();
    153     if (source_controller == &web_contents_->GetController()) {
    154       content::LoadCommittedDetails* commit_details =
    155           content::Details<content::LoadCommittedDetails>(details).ptr();
    156       content::PageTransition transition_type =
    157           commit_details->entry->GetTransitionType();
    158       // We hide the FindInPage window when the user navigates away, except on
    159       // reload (and when clicking on anchors within web pages).
    160       if (find_bar_->IsFindBarVisible()) {
    161         if (content::PageTransitionStripQualifier(transition_type) !=
    162             content::PAGE_TRANSITION_RELOAD) {
    163           // This is a new navigation (not reload), but we still don't want the
    164           // Find box to disappear if the navigation is just to a fragment
    165           // within the page.
    166           if (commit_details->is_navigation_to_different_page())
    167             EndFindSession(kKeepSelectionOnPage, kClearResultsInFindBox);
    168         } else {
    169           // On Reload we want to make sure FindNext is converted to a full Find
    170           // to make sure highlights for inactive matches are repainted.
    171           find_tab_helper->set_find_op_aborted(true);
    172         }
    173       }
    174     }
    175   }
    176 }
    177 
    178 // static
    179 gfx::Rect FindBarController::GetLocationForFindbarView(
    180     gfx::Rect view_location,
    181     const gfx::Rect& dialog_bounds,
    182     const gfx::Rect& avoid_overlapping_rect) {
    183   if (base::i18n::IsRTL()) {
    184     int boundary = dialog_bounds.width() - view_location.width();
    185     view_location.set_x(std::min(view_location.x(), boundary));
    186   } else {
    187     view_location.set_x(std::max(view_location.x(), dialog_bounds.x()));
    188   }
    189 
    190   gfx::Rect new_pos = view_location;
    191 
    192   // If the selection rectangle intersects the current position on screen then
    193   // we try to move our dialog to the left (right for RTL) of the selection
    194   // rectangle.
    195   if (!avoid_overlapping_rect.IsEmpty() &&
    196       avoid_overlapping_rect.Intersects(new_pos)) {
    197     if (base::i18n::IsRTL()) {
    198       new_pos.set_x(avoid_overlapping_rect.x() +
    199                     avoid_overlapping_rect.width() +
    200                     (2 * kMinFindWndDistanceFromSelection));
    201 
    202       // If we moved it off-screen to the right, we won't move it at all.
    203       if (new_pos.x() + new_pos.width() > dialog_bounds.width())
    204         new_pos = view_location;  // Reset.
    205     } else {
    206       new_pos.set_x(avoid_overlapping_rect.x() - new_pos.width() -
    207         kMinFindWndDistanceFromSelection);
    208 
    209       // If we moved it off-screen to the left, we won't move it at all.
    210       if (new_pos.x() < 0)
    211         new_pos = view_location;  // Reset.
    212     }
    213   }
    214 
    215   return new_pos;
    216 }
    217 
    218 void FindBarController::UpdateFindBarForCurrentResult() {
    219   FindTabHelper* find_tab_helper =
    220       FindTabHelper::FromWebContents(web_contents_);
    221   const FindNotificationDetails& find_result = find_tab_helper->find_result();
    222 
    223   // Avoid bug 894389: When a new search starts (and finds something) it reports
    224   // an interim match count result of 1 before the scoping effort starts. This
    225   // is to provide feedback as early as possible that we will find something.
    226   // As you add letters to the search term, this creates a flashing effect when
    227   // we briefly show "1 of 1" matches because there is a slight delay until
    228   // the scoping effort starts updating the match count. We avoid this flash by
    229   // ignoring interim results of 1 if we already have a positive number.
    230   if (find_result.number_of_matches() > -1) {
    231     if (last_reported_matchcount_ > 0 &&
    232         find_result.number_of_matches() == 1 &&
    233         !find_result.final_update())
    234       return;  // Don't let interim result override match count.
    235     last_reported_matchcount_ = find_result.number_of_matches();
    236   }
    237 
    238   find_bar_->UpdateUIForFindResult(find_result, find_tab_helper->find_text());
    239 }
    240 
    241 void FindBarController::MaybeSetPrepopulateText() {
    242   // Having a per-tab find_string is not compatible with a global find
    243   // pasteboard, so we always have the same find text in all find bars. This is
    244   // done through the find pasteboard mechanism, so don't set the text here.
    245   if (find_bar_->HasGlobalFindPasteboard())
    246     return;
    247 
    248   // Find out what we should show in the find text box. Usually, this will be
    249   // the last search in this tab, but if no search has been issued in this tab
    250   // we use the last search string (from any tab).
    251   FindTabHelper* find_tab_helper =
    252       FindTabHelper::FromWebContents(web_contents_);
    253   base::string16 find_string = find_tab_helper->find_text();
    254   if (find_string.empty())
    255     find_string = find_tab_helper->previous_find_text();
    256   if (find_string.empty()) {
    257     Profile* profile =
    258         Profile::FromBrowserContext(web_contents_->GetBrowserContext());
    259     find_string = FindBarStateFactory::GetLastPrepopulateText(profile);
    260   }
    261 
    262   // Update the find bar with existing results and search text, regardless of
    263   // whether or not the find bar is visible, so that if it's subsequently
    264   // shown it is showing the right state for this tab. We update the find text
    265   // _first_ since the FindBarView checks its emptiness to see if it should
    266   // clear the result count display when there's nothing in the box.
    267   find_bar_->SetFindTextAndSelectedRange(find_string,
    268                                          find_tab_helper->selected_range());
    269 }
    270