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_tab_helper.h" 6 7#include <vector> 8 9#include "chrome/browser/chrome_notification_types.h" 10#include "chrome/browser/profiles/profile.h" 11#include "chrome/browser/ui/find_bar/find_bar_state.h" 12#include "chrome/browser/ui/find_bar/find_bar_state_factory.h" 13#include "content/public/browser/notification_service.h" 14#include "content/public/browser/render_view_host.h" 15#include "content/public/browser/web_contents.h" 16#include "content/public/common/stop_find_action.h" 17#include "third_party/WebKit/public/web/WebFindOptions.h" 18#include "ui/gfx/rect_f.h" 19 20using blink::WebFindOptions; 21using content::WebContents; 22 23DEFINE_WEB_CONTENTS_USER_DATA_KEY(FindTabHelper); 24 25// static 26int FindTabHelper::find_request_id_counter_ = -1; 27 28FindTabHelper::FindTabHelper(WebContents* web_contents) 29 : content::WebContentsObserver(web_contents), 30 find_ui_active_(false), 31 find_op_aborted_(false), 32 current_find_request_id_(find_request_id_counter_++), 33 last_search_case_sensitive_(false), 34 last_search_result_() { 35} 36 37FindTabHelper::~FindTabHelper() { 38} 39 40void FindTabHelper::StartFinding(base::string16 search_string, 41 bool forward_direction, 42 bool case_sensitive) { 43 // Remove the carriage return character, which generally isn't in web content. 44 const base::char16 kInvalidChars[] = { '\r', 0 }; 45 base::RemoveChars(search_string, kInvalidChars, &search_string); 46 47 // If search_string is empty, it means FindNext was pressed with a keyboard 48 // shortcut so unless we have something to search for we return early. 49 if (search_string.empty() && find_text_.empty()) { 50 Profile* profile = 51 Profile::FromBrowserContext(web_contents()->GetBrowserContext()); 52 base::string16 last_search_prepopulate_text = 53 FindBarStateFactory::GetLastPrepopulateText(profile); 54 55 // Try the last thing we searched for on this tab, then the last thing 56 // searched for on any tab. 57 if (!previous_find_text_.empty()) 58 search_string = previous_find_text_; 59 else if (!last_search_prepopulate_text.empty()) 60 search_string = last_search_prepopulate_text; 61 else 62 return; 63 } 64 65 // Keep track of the previous search. 66 previous_find_text_ = find_text_; 67 68 // This is a FindNext operation if we are searching for the same text again, 69 // or if the passed in search text is empty (FindNext keyboard shortcut). The 70 // exception to this is if the Find was aborted (then we don't want FindNext 71 // because the highlighting has been cleared and we need it to reappear). We 72 // therefore treat FindNext after an aborted Find operation as a full fledged 73 // Find. 74 bool find_next = (find_text_ == search_string || search_string.empty()) && 75 (last_search_case_sensitive_ == case_sensitive) && 76 !find_op_aborted_; 77 if (!find_next) 78 current_find_request_id_ = find_request_id_counter_++; 79 80 if (!search_string.empty()) 81 find_text_ = search_string; 82 last_search_case_sensitive_ = case_sensitive; 83 84 find_op_aborted_ = false; 85 86 // Keep track of what the last search was across the tabs. 87 Profile* profile = 88 Profile::FromBrowserContext(web_contents()->GetBrowserContext()); 89 FindBarState* find_bar_state = FindBarStateFactory::GetForProfile(profile); 90 find_bar_state->set_last_prepopulate_text(find_text_); 91 92 WebFindOptions options; 93 options.forward = forward_direction; 94 options.matchCase = case_sensitive; 95 options.findNext = find_next; 96 web_contents()->Find(current_find_request_id_, find_text_, options); 97} 98 99void FindTabHelper::StopFinding( 100 FindBarController::SelectionAction selection_action) { 101 if (selection_action == FindBarController::kClearSelectionOnPage) { 102 // kClearSelection means the find string has been cleared by the user, but 103 // the UI has not been dismissed. In that case we want to clear the 104 // previously remembered search (http://crbug.com/42639). 105 previous_find_text_ = base::string16(); 106 } else { 107 find_ui_active_ = false; 108 if (!find_text_.empty()) 109 previous_find_text_ = find_text_; 110 } 111 find_text_.clear(); 112 find_op_aborted_ = true; 113 last_search_result_ = FindNotificationDetails(); 114 115 content::StopFindAction action; 116 switch (selection_action) { 117 case FindBarController::kClearSelectionOnPage: 118 action = content::STOP_FIND_ACTION_CLEAR_SELECTION; 119 break; 120 case FindBarController::kKeepSelectionOnPage: 121 action = content::STOP_FIND_ACTION_KEEP_SELECTION; 122 break; 123 case FindBarController::kActivateSelectionOnPage: 124 action = content::STOP_FIND_ACTION_ACTIVATE_SELECTION; 125 break; 126 default: 127 NOTREACHED(); 128 action = content::STOP_FIND_ACTION_KEEP_SELECTION; 129 } 130 web_contents()->StopFinding(action); 131} 132 133#if defined(OS_ANDROID) 134void FindTabHelper::ActivateNearestFindResult(float x, float y) { 135 if (!find_op_aborted_ && !find_text_.empty()) { 136 web_contents()->GetRenderViewHost()->ActivateNearestFindResult( 137 current_find_request_id_, x, y); 138 } 139} 140 141void FindTabHelper::RequestFindMatchRects(int current_version) { 142 if (!find_op_aborted_ && !find_text_.empty()) 143 web_contents()->GetRenderViewHost()->RequestFindMatchRects(current_version); 144} 145#endif 146 147void FindTabHelper::HandleFindReply(int request_id, 148 int number_of_matches, 149 const gfx::Rect& selection_rect, 150 int active_match_ordinal, 151 bool final_update) { 152 // Ignore responses for requests that have been aborted. 153 // Ignore responses for requests other than the one we have most recently 154 // issued. That way we won't act on stale results when the user has 155 // already typed in another query. 156 if (!find_op_aborted_ && request_id == current_find_request_id_) { 157 if (number_of_matches == -1) 158 number_of_matches = last_search_result_.number_of_matches(); 159 if (active_match_ordinal == -1) 160 active_match_ordinal = last_search_result_.active_match_ordinal(); 161 162 gfx::Rect selection = selection_rect; 163 if (final_update && active_match_ordinal == 0) 164 selection = gfx::Rect(); 165 else if (selection_rect.IsEmpty()) 166 selection = last_search_result_.selection_rect(); 167 168 // Notify the UI, automation and any other observers that a find result was 169 // found. 170 last_search_result_ = FindNotificationDetails( 171 request_id, number_of_matches, selection, active_match_ordinal, 172 final_update); 173 content::NotificationService::current()->Notify( 174 chrome::NOTIFICATION_FIND_RESULT_AVAILABLE, 175 content::Source<WebContents>(web_contents()), 176 content::Details<FindNotificationDetails>(&last_search_result_)); 177 } 178} 179