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.
5 #include "chrome/browser/ui/find_bar/find_bar_controller.h"
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/geometry/rect.h"
25 using content::NavigationController
;
26 using content::WebContents
;
28 // The minimum space between the FindInPage window and the search result.
29 static const int kMinFindWndDistanceFromSelection
= 5;
31 FindBarController::FindBarController(FindBar
* find_bar
)
32 : find_bar_(find_bar
),
34 last_reported_matchcount_(0) {
37 FindBarController::~FindBarController() {
38 DCHECK(!web_contents_
);
41 void FindBarController::Show() {
42 FindTabHelper
* find_tab_helper
=
43 FindTabHelper::FromWebContents(web_contents_
);
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();
50 find_tab_helper
->set_find_ui_active(true);
51 find_bar_
->Show(true);
53 find_bar_
->SetFocusAndSelection();
56 void FindBarController::EndFindSession(SelectionAction selection_action
,
57 ResultAction result_action
) {
58 find_bar_
->Hide(true);
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.
63 FindTabHelper
* find_tab_helper
=
64 FindTabHelper::FromWebContents(web_contents_
);
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
);
71 if (result_action
== kClearResultsInFindBox
)
72 find_bar_
->ClearResults(find_tab_helper
->find_result());
74 // When we get dismissed we restore the focus to where it belongs.
75 find_bar_
->RestoreSavedFocus();
79 void FindBarController::ChangeWebContents(WebContents
* contents
) {
81 registrar_
.RemoveAll();
82 find_bar_
->StopAnimation();
84 FindTabHelper
* find_tab_helper
=
85 FindTabHelper::FromWebContents(web_contents_
);
87 find_tab_helper
->set_selected_range(find_bar_
->GetSelectedRange());
90 web_contents_
= contents
;
91 FindTabHelper
* find_tab_helper
=
92 web_contents_
? FindTabHelper::FromWebContents(web_contents_
)
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);
106 chrome::NOTIFICATION_FIND_RESULT_AVAILABLE
,
107 content::Source
<WebContents
>(web_contents_
));
110 content::NOTIFICATION_NAV_ENTRY_COMMITTED
,
111 content::Source
<NavigationController
>(&web_contents_
->GetController()));
113 MaybeSetPrepopulateText();
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
121 find_bar_
->Show(false);
124 UpdateFindBarForCurrentResult();
125 find_bar_
->UpdateFindBarForChangedWebContents();
128 ////////////////////////////////////////////////////////////////////////////////
129 // FindBarHost, content::NotificationObserver implementation:
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();
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 ui::PageTransition transition_type
=
157 commit_details
->entry
->GetTransitionType();
158 // Hide the find bar on reload or navigation.
159 if (find_bar_
->IsFindBarVisible() &&
160 (ui::PageTransitionStripQualifier(transition_type
) ==
161 ui::PAGE_TRANSITION_RELOAD
||
162 commit_details
->is_navigation_to_different_page()))
163 EndFindSession(kKeepSelectionOnPage
, kClearResultsInFindBox
);
169 gfx::Rect
FindBarController::GetLocationForFindbarView(
170 gfx::Rect view_location
,
171 const gfx::Rect
& dialog_bounds
,
172 const gfx::Rect
& avoid_overlapping_rect
) {
173 if (base::i18n::IsRTL()) {
174 int boundary
= dialog_bounds
.width() - view_location
.width();
175 view_location
.set_x(std::min(view_location
.x(), boundary
));
177 view_location
.set_x(std::max(view_location
.x(), dialog_bounds
.x()));
180 gfx::Rect new_pos
= view_location
;
182 // If the selection rectangle intersects the current position on screen then
183 // we try to move our dialog to the left (right for RTL) of the selection
185 if (!avoid_overlapping_rect
.IsEmpty() &&
186 avoid_overlapping_rect
.Intersects(new_pos
)) {
187 if (base::i18n::IsRTL()) {
188 new_pos
.set_x(avoid_overlapping_rect
.x() +
189 avoid_overlapping_rect
.width() +
190 (2 * kMinFindWndDistanceFromSelection
));
192 // If we moved it off-screen to the right, we won't move it at all.
193 if (new_pos
.x() + new_pos
.width() > dialog_bounds
.width())
194 new_pos
= view_location
; // Reset.
196 new_pos
.set_x(avoid_overlapping_rect
.x() - new_pos
.width() -
197 kMinFindWndDistanceFromSelection
);
199 // If we moved it off-screen to the left, we won't move it at all.
201 new_pos
= view_location
; // Reset.
208 void FindBarController::UpdateFindBarForCurrentResult() {
209 FindTabHelper
* find_tab_helper
=
210 FindTabHelper::FromWebContents(web_contents_
);
211 const FindNotificationDetails
& find_result
= find_tab_helper
->find_result();
213 // Avoid bug 894389: When a new search starts (and finds something) it reports
214 // an interim match count result of 1 before the scoping effort starts. This
215 // is to provide feedback as early as possible that we will find something.
216 // As you add letters to the search term, this creates a flashing effect when
217 // we briefly show "1 of 1" matches because there is a slight delay until
218 // the scoping effort starts updating the match count. We avoid this flash by
219 // ignoring interim results of 1 if we already have a positive number.
220 if (find_result
.number_of_matches() > -1) {
221 if (last_reported_matchcount_
> 0 &&
222 find_result
.number_of_matches() == 1 &&
223 !find_result
.final_update())
224 return; // Don't let interim result override match count.
225 last_reported_matchcount_
= find_result
.number_of_matches();
228 find_bar_
->UpdateUIForFindResult(find_result
, find_tab_helper
->find_text());
231 void FindBarController::MaybeSetPrepopulateText() {
232 // Having a per-tab find_string is not compatible with a global find
233 // pasteboard, so we always have the same find text in all find bars. This is
234 // done through the find pasteboard mechanism, so don't set the text here.
235 if (find_bar_
->HasGlobalFindPasteboard())
238 // Find out what we should show in the find text box. Usually, this will be
239 // the last search in this tab, but if no search has been issued in this tab
240 // we use the last search string (from any tab).
241 FindTabHelper
* find_tab_helper
=
242 FindTabHelper::FromWebContents(web_contents_
);
243 base::string16 find_string
= find_tab_helper
->find_text();
244 if (find_string
.empty())
245 find_string
= find_tab_helper
->previous_find_text();
246 if (find_string
.empty()) {
248 Profile::FromBrowserContext(web_contents_
->GetBrowserContext());
249 find_string
= FindBarStateFactory::GetLastPrepopulateText(profile
);
252 // Update the find bar with existing results and search text, regardless of
253 // whether or not the find bar is visible, so that if it's subsequently
254 // shown it is showing the right state for this tab. We update the find text
255 // _first_ since the FindBarView checks its emptiness to see if it should
256 // clear the result count display when there's nothing in the box.
257 find_bar_
->SetFindTextAndSelectedRange(find_string
,
258 find_tab_helper
->selected_range());