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