|  | // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "chrome/browser/ui/find_bar/find_tab_helper.h" | 
|  |  | 
|  | #include <vector> | 
|  |  | 
|  | #include "base/strings/string_util.h" | 
|  | #include "build/build_config.h" | 
|  | #include "chrome/browser/chrome_notification_types.h" | 
|  | #include "chrome/browser/profiles/profile.h" | 
|  | #include "chrome/browser/ui/find_bar/find_bar_state.h" | 
|  | #include "chrome/browser/ui/find_bar/find_bar_state_factory.h" | 
|  | #include "content/public/browser/notification_service.h" | 
|  | #include "content/public/browser/render_frame_host.h" | 
|  | #include "content/public/browser/render_view_host.h" | 
|  | #include "content/public/browser/web_contents.h" | 
|  | #include "content/public/common/stop_find_action.h" | 
|  | #include "third_party/WebKit/public/web/WebFindOptions.h" | 
|  | #include "ui/gfx/geometry/rect_f.h" | 
|  |  | 
|  | using blink::WebFindOptions; | 
|  | using content::WebContents; | 
|  |  | 
|  | DEFINE_WEB_CONTENTS_USER_DATA_KEY(FindTabHelper); | 
|  |  | 
|  | // static | 
|  | int FindTabHelper::find_request_id_counter_ = -1; | 
|  |  | 
|  | FindTabHelper::FindTabHelper(WebContents* web_contents) | 
|  | : content::WebContentsObserver(web_contents), | 
|  | find_ui_active_(false), | 
|  | find_op_aborted_(false), | 
|  | current_find_request_id_(find_request_id_counter_++), | 
|  | last_search_case_sensitive_(false), | 
|  | last_search_result_() { | 
|  | } | 
|  |  | 
|  | FindTabHelper::~FindTabHelper() { | 
|  | } | 
|  |  | 
|  | void FindTabHelper::StartFinding(base::string16 search_string, | 
|  | bool forward_direction, | 
|  | bool case_sensitive) { | 
|  | // Remove the carriage return character, which generally isn't in web content. | 
|  | const base::char16 kInvalidChars[] = { '\r', 0 }; | 
|  | base::RemoveChars(search_string, kInvalidChars, &search_string); | 
|  |  | 
|  | // If search_string is empty, it means FindNext was pressed with a keyboard | 
|  | // shortcut so unless we have something to search for we return early. | 
|  | if (search_string.empty() && find_text_.empty()) { | 
|  | Profile* profile = | 
|  | Profile::FromBrowserContext(web_contents()->GetBrowserContext()); | 
|  | base::string16 last_search_prepopulate_text = | 
|  | FindBarStateFactory::GetLastPrepopulateText(profile); | 
|  |  | 
|  | // Try the last thing we searched for on this tab, then the last thing | 
|  | // searched for on any tab. | 
|  | if (!previous_find_text_.empty()) | 
|  | search_string = previous_find_text_; | 
|  | else if (!last_search_prepopulate_text.empty()) | 
|  | search_string = last_search_prepopulate_text; | 
|  | else | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Keep track of the previous search. | 
|  | previous_find_text_ = find_text_; | 
|  |  | 
|  | // This is a FindNext operation if we are searching for the same text again, | 
|  | // or if the passed in search text is empty (FindNext keyboard shortcut). The | 
|  | // exception to this is if the Find was aborted (then we don't want FindNext | 
|  | // because the highlighting has been cleared and we need it to reappear). We | 
|  | // therefore treat FindNext after an aborted Find operation as a full fledged | 
|  | // Find. | 
|  | bool find_next = (find_text_ == search_string || search_string.empty()) && | 
|  | (last_search_case_sensitive_ == case_sensitive) && | 
|  | !find_op_aborted_; | 
|  | if (!find_next) | 
|  | current_find_request_id_ = find_request_id_counter_++; | 
|  |  | 
|  | if (!search_string.empty()) | 
|  | find_text_ = search_string; | 
|  | last_search_case_sensitive_ = case_sensitive; | 
|  |  | 
|  | find_op_aborted_ = false; | 
|  |  | 
|  | // Keep track of what the last search was across the tabs. | 
|  | Profile* profile = | 
|  | Profile::FromBrowserContext(web_contents()->GetBrowserContext()); | 
|  | FindBarState* find_bar_state = FindBarStateFactory::GetForProfile(profile); | 
|  | find_bar_state->set_last_prepopulate_text(find_text_); | 
|  |  | 
|  | WebFindOptions options; | 
|  | options.forward = forward_direction; | 
|  | options.matchCase = case_sensitive; | 
|  | options.findNext = find_next; | 
|  | web_contents()->Find(current_find_request_id_, find_text_, options); | 
|  | } | 
|  |  | 
|  | void FindTabHelper::StopFinding( | 
|  | FindBarController::SelectionAction selection_action) { | 
|  | if (selection_action == FindBarController::kClearSelectionOnPage) { | 
|  | // kClearSelection means the find string has been cleared by the user, but | 
|  | // the UI has not been dismissed. In that case we want to clear the | 
|  | // previously remembered search (http://crbug.com/42639). | 
|  | previous_find_text_ = base::string16(); | 
|  | } else { | 
|  | find_ui_active_ = false; | 
|  | if (!find_text_.empty()) | 
|  | previous_find_text_ = find_text_; | 
|  | } | 
|  | find_text_.clear(); | 
|  | find_op_aborted_ = true; | 
|  | last_search_result_ = FindNotificationDetails(); | 
|  |  | 
|  | content::StopFindAction action; | 
|  | switch (selection_action) { | 
|  | case FindBarController::kClearSelectionOnPage: | 
|  | action = content::STOP_FIND_ACTION_CLEAR_SELECTION; | 
|  | break; | 
|  | case FindBarController::kKeepSelectionOnPage: | 
|  | action = content::STOP_FIND_ACTION_KEEP_SELECTION; | 
|  | break; | 
|  | case FindBarController::kActivateSelectionOnPage: | 
|  | action = content::STOP_FIND_ACTION_ACTIVATE_SELECTION; | 
|  | break; | 
|  | default: | 
|  | NOTREACHED(); | 
|  | action = content::STOP_FIND_ACTION_KEEP_SELECTION; | 
|  | } | 
|  | web_contents()->StopFinding(action); | 
|  | } | 
|  |  | 
|  | void FindTabHelper::ActivateFindInPageResultForAccessibility() { | 
|  | web_contents()->GetMainFrame()->ActivateFindInPageResultForAccessibility( | 
|  | current_find_request_id_); | 
|  | } | 
|  |  | 
|  | #if defined(OS_ANDROID) | 
|  | void FindTabHelper::ActivateNearestFindResult(float x, float y) { | 
|  | if (!find_op_aborted_ && !find_text_.empty()) { | 
|  | web_contents()->GetMainFrame()->ActivateNearestFindResult( | 
|  | current_find_request_id_, x, y); | 
|  | } | 
|  | } | 
|  |  | 
|  | void FindTabHelper::RequestFindMatchRects(int current_version) { | 
|  | if (!find_op_aborted_ && !find_text_.empty()) | 
|  | web_contents()->GetMainFrame()->RequestFindMatchRects(current_version); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | void FindTabHelper::HandleFindReply(int request_id, | 
|  | int number_of_matches, | 
|  | const gfx::Rect& selection_rect, | 
|  | int active_match_ordinal, | 
|  | bool final_update) { | 
|  | // Ignore responses for requests that have been aborted. | 
|  | // Ignore responses for requests other than the one we have most recently | 
|  | // issued. That way we won't act on stale results when the user has | 
|  | // already typed in another query. | 
|  | if (!find_op_aborted_ && request_id == current_find_request_id_) { | 
|  | if (number_of_matches == -1) | 
|  | number_of_matches = last_search_result_.number_of_matches(); | 
|  | if (active_match_ordinal == -1) | 
|  | active_match_ordinal = last_search_result_.active_match_ordinal(); | 
|  |  | 
|  | gfx::Rect selection = selection_rect; | 
|  | if (final_update && active_match_ordinal == 0) | 
|  | selection = gfx::Rect(); | 
|  | else if (selection_rect.IsEmpty()) | 
|  | selection = last_search_result_.selection_rect(); | 
|  |  | 
|  | // Notify the UI, automation and any other observers that a find result was | 
|  | // found. | 
|  | last_search_result_ = FindNotificationDetails( | 
|  | request_id, number_of_matches, selection, active_match_ordinal, | 
|  | final_update); | 
|  | content::NotificationService::current()->Notify( | 
|  | chrome::NOTIFICATION_FIND_RESULT_AVAILABLE, | 
|  | content::Source<WebContents>(web_contents()), | 
|  | content::Details<FindNotificationDetails>(&last_search_result_)); | 
|  | } | 
|  | } |