1 // Copyright 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/renderer/searchbox/searchbox.h"
9 #include "base/logging.h"
10 #include "base/strings/string_number_conversions.h"
11 #include "base/strings/string_split.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "base/time/time.h"
15 #include "chrome/common/chrome_switches.h"
16 #include "chrome/common/render_messages.h"
17 #include "chrome/common/url_constants.h"
18 #include "chrome/renderer/searchbox/searchbox_extension.h"
19 #include "components/favicon_base/fallback_icon_url_parser.h"
20 #include "components/favicon_base/favicon_types.h"
21 #include "components/favicon_base/favicon_url_parser.h"
22 #include "components/favicon_base/large_icon_url_parser.h"
23 #include "components/omnibox/common/omnibox_focus_state.h"
24 #include "content/public/renderer/render_frame.h"
25 #include "content/public/renderer/render_view.h"
26 #include "net/base/escape.h"
27 #include "third_party/WebKit/public/web/WebDocument.h"
28 #include "third_party/WebKit/public/web/WebFrame.h"
29 #include "third_party/WebKit/public/web/WebLocalFrame.h"
30 #include "third_party/WebKit/public/web/WebPerformance.h"
31 #include "third_party/WebKit/public/web/WebView.h"
36 // The size of the InstantMostVisitedItem cache.
37 const size_t kMaxInstantMostVisitedItemCacheSize
= 100;
39 // Returns true if items stored in |old_item_id_pairs| and |new_items| are
41 bool AreMostVisitedItemsEqual(
42 const std::vector
<InstantMostVisitedItemIDPair
>& old_item_id_pairs
,
43 const std::vector
<InstantMostVisitedItem
>& new_items
) {
44 if (old_item_id_pairs
.size() != new_items
.size())
47 for (size_t i
= 0; i
< new_items
.size(); ++i
) {
48 if (new_items
[i
].url
!= old_item_id_pairs
[i
].second
.url
||
49 new_items
[i
].title
!= old_item_id_pairs
[i
].second
.title
) {
56 const char* GetIconTypeUrlHost(SearchBox::ImageSourceType type
) {
58 case SearchBox::FAVICON
:
60 case SearchBox::LARGE_ICON
:
62 case SearchBox::FALLBACK_ICON
:
63 return "fallback-icon";
64 case SearchBox::THUMB
:
72 // Given |path| from an image URL, returns starting index of the page URL,
73 // depending on |type| of image URL. Returns -1 if parse fails.
74 int GetImagePathStartOfPageURL(SearchBox::ImageSourceType type
,
75 const std::string
& path
) {
76 // TODO(huangs): Refactor this: http://crbug.com/468320.
78 case SearchBox::FAVICON
: {
79 chrome::ParsedFaviconPath parsed
;
80 return chrome::ParseFaviconPath(
81 path
, favicon_base::FAVICON
, &parsed
) ? parsed
.path_index
: -1;
83 case SearchBox::LARGE_ICON
: {
84 LargeIconUrlParser parser
;
85 return parser
.Parse(path
) ? parser
.path_index() : -1;
87 case SearchBox::FALLBACK_ICON
: {
88 chrome::ParsedFallbackIconPath parser
;
89 return parser
.Parse(path
) ? parser
.path_index() : -1;
91 case SearchBox::THUMB
: {
102 // Helper for SearchBox::GenerateImageURLFromTransientURL().
103 class SearchBoxIconURLHelper
: public SearchBox::IconURLHelper
{
105 explicit SearchBoxIconURLHelper(const SearchBox
* search_box
);
106 ~SearchBoxIconURLHelper() override
;
107 int GetViewID() const override
;
108 std::string
GetURLStringFromRestrictedID(InstantRestrictedID rid
) const
112 const SearchBox
* search_box_
;
115 SearchBoxIconURLHelper::SearchBoxIconURLHelper(const SearchBox
* search_box
)
116 : search_box_(search_box
) {
119 SearchBoxIconURLHelper::~SearchBoxIconURLHelper() {
122 int SearchBoxIconURLHelper::GetViewID() const {
123 return search_box_
->render_view()->GetRoutingID();
126 std::string
SearchBoxIconURLHelper::GetURLStringFromRestrictedID(
127 InstantRestrictedID rid
) const {
128 InstantMostVisitedItem item
;
129 if (!search_box_
->GetMostVisitedItemWithID(rid
, &item
))
130 return std::string();
132 return item
.url
.spec();
137 namespace internal
{ // for testing
139 // Parses "<view_id>/<restricted_id>". If successful, assigns
140 // |*view_id| := "<view_id>", |*rid| := "<restricted_id>", and returns true.
141 bool ParseViewIdAndRestrictedId(const std::string
& id_part
,
143 InstantRestrictedID
* rid_out
) {
146 // Check that the path is of Most visited item ID form.
147 std::vector
<base::StringPiece
> tokens
= base::SplitStringPiece(
148 id_part
, "/", base::KEEP_WHITESPACE
, base::SPLIT_WANT_NONEMPTY
);
149 if (tokens
.size() != 2)
153 InstantRestrictedID rid
;
154 if (!base::StringToInt(tokens
[0], &view_id
) || view_id
< 0 ||
155 !base::StringToInt(tokens
[1], &rid
) || rid
< 0)
158 *view_id_out
= view_id
;
163 // Takes icon |url| of given |type|, e.g., FAVICON looking like
165 // chrome-search://favicon/<view_id>/<restricted_id>
166 // chrome-search://favicon/<parameters>/<view_id>/<restricted_id>
168 // If successful, assigns |*param_part| := "" or "<parameters>/" (note trailing
169 // slash), |*view_id| := "<view_id>", |*rid| := "rid", and returns true.
170 bool ParseIconRestrictedUrl(const GURL
& url
,
171 SearchBox::ImageSourceType type
,
172 std::string
* param_part
,
174 InstantRestrictedID
* rid
) {
178 // Strip leading slash.
179 std::string raw_path
= url
.path();
180 DCHECK_GT(raw_path
.length(), (size_t) 0);
181 DCHECK_EQ(raw_path
[0], '/');
182 raw_path
= raw_path
.substr(1);
184 int path_index
= GetImagePathStartOfPageURL(type
, raw_path
);
188 std::string id_part
= raw_path
.substr(path_index
);
189 if (!ParseViewIdAndRestrictedId(id_part
, view_id
, rid
))
192 *param_part
= raw_path
.substr(0, path_index
);
196 bool TranslateIconRestrictedUrl(const GURL
& transient_url
,
197 SearchBox::ImageSourceType type
,
198 const SearchBox::IconURLHelper
& helper
,
202 InstantRestrictedID rid
= -1;
204 if (!internal::ParseIconRestrictedUrl(
205 transient_url
, type
, ¶ms
, &view_id
, &rid
) ||
206 view_id
!= helper
.GetViewID()) {
207 if (type
== SearchBox::FAVICON
) {
208 *url
= GURL(base::StringPrintf("chrome-search://%s/",
209 GetIconTypeUrlHost(SearchBox::FAVICON
)));
215 std::string item_url
= helper
.GetURLStringFromRestrictedID(rid
);
216 *url
= GURL(base::StringPrintf("chrome-search://%s/%s%s",
217 GetIconTypeUrlHost(type
),
223 } // namespace internal
225 SearchBox::IconURLHelper::IconURLHelper() {
228 SearchBox::IconURLHelper::~IconURLHelper() {
231 SearchBox::SearchBox(content::RenderView
* render_view
)
232 : content::RenderViewObserver(render_view
),
233 content::RenderViewObserverTracker
<SearchBox
>(render_view
),
235 app_launcher_enabled_(false),
237 is_input_in_progress_(false),
238 is_key_capture_enabled_(false),
239 display_instant_results_(false),
240 most_visited_items_cache_(kMaxInstantMostVisitedItemCacheSize
),
245 SearchBox::~SearchBox() {
248 void SearchBox::LogEvent(NTPLoggingEventType event
) {
249 // The main frame for the current RenderView may be out-of-process, in which
250 // case it won't have performance(). Use the default delta of 0 in this
252 base::TimeDelta delta
;
253 if (render_view()->GetWebView()->mainFrame()->isWebLocalFrame()) {
254 // navigation_start in ms.
255 uint64 start
= 1000 * (render_view()->GetMainRenderFrame()->GetWebFrame()->
256 performance().navigationStart());
257 uint64 now
= (base::TimeTicks::Now() - base::TimeTicks::UnixEpoch())
259 DCHECK(now
>= start
);
260 delta
= base::TimeDelta::FromMilliseconds(now
- start
);
262 render_view()->Send(new ChromeViewHostMsg_LogEvent(
263 render_view()->GetRoutingID(), page_seq_no_
, event
, delta
));
266 void SearchBox::LogMostVisitedImpression(int position
,
267 const base::string16
& provider
) {
268 render_view()->Send(new ChromeViewHostMsg_LogMostVisitedImpression(
269 render_view()->GetRoutingID(), page_seq_no_
, position
, provider
));
272 void SearchBox::LogMostVisitedNavigation(int position
,
273 const base::string16
& provider
) {
274 render_view()->Send(new ChromeViewHostMsg_LogMostVisitedNavigation(
275 render_view()->GetRoutingID(), page_seq_no_
, position
, provider
));
278 void SearchBox::CheckIsUserSignedInToChromeAs(const base::string16
& identity
) {
279 render_view()->Send(new ChromeViewHostMsg_ChromeIdentityCheck(
280 render_view()->GetRoutingID(), page_seq_no_
, identity
));
283 void SearchBox::CheckIsUserSyncingHistory() {
284 render_view()->Send(new ChromeViewHostMsg_HistorySyncCheck(
285 render_view()->GetRoutingID(), page_seq_no_
));
288 void SearchBox::DeleteMostVisitedItem(
289 InstantRestrictedID most_visited_item_id
) {
290 render_view()->Send(new ChromeViewHostMsg_SearchBoxDeleteMostVisitedItem(
291 render_view()->GetRoutingID(),
293 GetURLForMostVisitedItem(most_visited_item_id
)));
296 bool SearchBox::GenerateImageURLFromTransientURL(const GURL
& transient_url
,
297 ImageSourceType type
,
299 SearchBoxIconURLHelper
helper(this);
300 return internal::TranslateIconRestrictedUrl(transient_url
, type
, helper
, url
);
303 void SearchBox::GetMostVisitedItems(
304 std::vector
<InstantMostVisitedItemIDPair
>* items
) const {
305 return most_visited_items_cache_
.GetCurrentItems(items
);
308 bool SearchBox::GetMostVisitedItemWithID(
309 InstantRestrictedID most_visited_item_id
,
310 InstantMostVisitedItem
* item
) const {
311 return most_visited_items_cache_
.GetItemWithRestrictedID(most_visited_item_id
,
315 const ThemeBackgroundInfo
& SearchBox::GetThemeBackgroundInfo() {
319 const EmbeddedSearchRequestParams
& SearchBox::GetEmbeddedSearchRequestParams() {
320 return embedded_search_request_params_
;
323 void SearchBox::Focus() {
324 render_view()->Send(new ChromeViewHostMsg_FocusOmnibox(
325 render_view()->GetRoutingID(), page_seq_no_
, OMNIBOX_FOCUS_VISIBLE
));
328 void SearchBox::NavigateToURL(const GURL
& url
,
329 WindowOpenDisposition disposition
,
330 bool is_most_visited_item_url
) {
331 render_view()->Send(new ChromeViewHostMsg_SearchBoxNavigate(
332 render_view()->GetRoutingID(), page_seq_no_
, url
,
333 disposition
, is_most_visited_item_url
));
336 void SearchBox::Paste(const base::string16
& text
) {
337 render_view()->Send(new ChromeViewHostMsg_PasteAndOpenDropdown(
338 render_view()->GetRoutingID(), page_seq_no_
, text
));
341 void SearchBox::SetVoiceSearchSupported(bool supported
) {
342 render_view()->Send(new ChromeViewHostMsg_SetVoiceSearchSupported(
343 render_view()->GetRoutingID(), page_seq_no_
, supported
));
346 void SearchBox::StartCapturingKeyStrokes() {
347 render_view()->Send(new ChromeViewHostMsg_FocusOmnibox(
348 render_view()->GetRoutingID(), page_seq_no_
, OMNIBOX_FOCUS_INVISIBLE
));
351 void SearchBox::StopCapturingKeyStrokes() {
352 render_view()->Send(new ChromeViewHostMsg_FocusOmnibox(
353 render_view()->GetRoutingID(), page_seq_no_
, OMNIBOX_FOCUS_NONE
));
356 void SearchBox::UndoAllMostVisitedDeletions() {
358 new ChromeViewHostMsg_SearchBoxUndoAllMostVisitedDeletions(
359 render_view()->GetRoutingID(), page_seq_no_
));
362 void SearchBox::UndoMostVisitedDeletion(
363 InstantRestrictedID most_visited_item_id
) {
364 render_view()->Send(new ChromeViewHostMsg_SearchBoxUndoMostVisitedDeletion(
365 render_view()->GetRoutingID(), page_seq_no_
,
366 GetURLForMostVisitedItem(most_visited_item_id
)));
369 bool SearchBox::OnMessageReceived(const IPC::Message
& message
) {
371 IPC_BEGIN_MESSAGE_MAP(SearchBox
, message
)
372 IPC_MESSAGE_HANDLER(ChromeViewMsg_SetPageSequenceNumber
,
373 OnSetPageSequenceNumber
)
374 IPC_MESSAGE_HANDLER(ChromeViewMsg_ChromeIdentityCheckResult
,
375 OnChromeIdentityCheckResult
)
376 IPC_MESSAGE_HANDLER(ChromeViewMsg_DetermineIfPageSupportsInstant
,
377 OnDetermineIfPageSupportsInstant
)
378 IPC_MESSAGE_HANDLER(ChromeViewMsg_HistorySyncCheckResult
,
379 OnHistorySyncCheckResult
)
380 IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxFocusChanged
, OnFocusChanged
)
381 IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxMarginChange
, OnMarginChange
)
382 IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxMostVisitedItemsChanged
,
383 OnMostVisitedChanged
)
384 IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxPromoInformation
,
385 OnPromoInformationReceived
)
386 IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxSetDisplayInstantResults
,
387 OnSetDisplayInstantResults
)
388 IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxSetInputInProgress
,
389 OnSetInputInProgress
)
390 IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxSetSuggestionToPrefetch
,
391 OnSetSuggestionToPrefetch
)
392 IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxSubmit
, OnSubmit
)
393 IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxThemeChanged
,
395 IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxToggleVoiceSearch
,
397 IPC_MESSAGE_UNHANDLED(handled
= false)
398 IPC_END_MESSAGE_MAP()
402 void SearchBox::OnSetPageSequenceNumber(int page_seq_no
) {
403 page_seq_no_
= page_seq_no
;
406 void SearchBox::OnChromeIdentityCheckResult(const base::string16
& identity
,
407 bool identity_match
) {
408 if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) {
409 extensions_v8::SearchBoxExtension::DispatchChromeIdentityCheckResult(
410 render_view()->GetWebView()->mainFrame(), identity
, identity_match
);
414 void SearchBox::OnDetermineIfPageSupportsInstant() {
415 if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) {
416 bool result
= extensions_v8::SearchBoxExtension::PageSupportsInstant(
417 render_view()->GetWebView()->mainFrame());
418 DVLOG(1) << render_view() << " PageSupportsInstant: " << result
;
419 render_view()->Send(new ChromeViewHostMsg_InstantSupportDetermined(
420 render_view()->GetRoutingID(), page_seq_no_
, result
));
424 void SearchBox::OnFocusChanged(OmniboxFocusState new_focus_state
,
425 OmniboxFocusChangeReason reason
) {
426 bool key_capture_enabled
= new_focus_state
== OMNIBOX_FOCUS_INVISIBLE
;
427 if (key_capture_enabled
!= is_key_capture_enabled_
) {
428 // Tell the page if the key capture mode changed unless the focus state
429 // changed because of TYPING. This is because in that case, the browser
430 // hasn't really stopped capturing key strokes.
432 // (More practically, if we don't do this check, the page would receive
433 // onkeycapturechange before the corresponding onchange, and the page would
434 // have no way of telling whether the keycapturechange happened because of
435 // some actual user action or just because they started typing.)
436 if (reason
!= OMNIBOX_FOCUS_CHANGE_TYPING
&&
437 render_view()->GetWebView() &&
438 render_view()->GetWebView()->mainFrame()) {
439 is_key_capture_enabled_
= key_capture_enabled
;
440 DVLOG(1) << render_view() << " OnKeyCaptureChange";
441 extensions_v8::SearchBoxExtension::DispatchKeyCaptureChange(
442 render_view()->GetWebView()->mainFrame());
445 bool is_focused
= new_focus_state
== OMNIBOX_FOCUS_VISIBLE
;
446 if (is_focused
!= is_focused_
) {
447 is_focused_
= is_focused
;
448 DVLOG(1) << render_view() << " OnFocusChange";
449 if (render_view()->GetWebView() &&
450 render_view()->GetWebView()->mainFrame()) {
451 extensions_v8::SearchBoxExtension::DispatchFocusChange(
452 render_view()->GetWebView()->mainFrame());
457 void SearchBox::OnHistorySyncCheckResult(bool sync_history
) {
458 if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) {
459 extensions_v8::SearchBoxExtension::DispatchHistorySyncCheckResult(
460 render_view()->GetWebView()->mainFrame(), sync_history
);
464 void SearchBox::OnMarginChange(int margin
) {
465 start_margin_
= margin
;
466 if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) {
467 extensions_v8::SearchBoxExtension::DispatchMarginChange(
468 render_view()->GetWebView()->mainFrame());
472 void SearchBox::OnMostVisitedChanged(
473 const std::vector
<InstantMostVisitedItem
>& items
) {
474 std::vector
<InstantMostVisitedItemIDPair
> last_known_items
;
475 GetMostVisitedItems(&last_known_items
);
477 if (AreMostVisitedItemsEqual(last_known_items
, items
))
478 return; // Do not send duplicate onmostvisitedchange events.
480 most_visited_items_cache_
.AddItems(items
);
481 if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) {
482 extensions_v8::SearchBoxExtension::DispatchMostVisitedChanged(
483 render_view()->GetWebView()->mainFrame());
487 void SearchBox::OnPromoInformationReceived(bool is_app_launcher_enabled
) {
488 app_launcher_enabled_
= is_app_launcher_enabled
;
491 void SearchBox::OnSetDisplayInstantResults(bool display_instant_results
) {
492 display_instant_results_
= display_instant_results
;
495 void SearchBox::OnSetInputInProgress(bool is_input_in_progress
) {
496 if (is_input_in_progress_
!= is_input_in_progress
) {
497 is_input_in_progress_
= is_input_in_progress
;
498 DVLOG(1) << render_view() << " OnSetInputInProgress";
499 if (render_view()->GetWebView() &&
500 render_view()->GetWebView()->mainFrame()) {
501 if (is_input_in_progress_
) {
502 extensions_v8::SearchBoxExtension::DispatchInputStart(
503 render_view()->GetWebView()->mainFrame());
505 extensions_v8::SearchBoxExtension::DispatchInputCancel(
506 render_view()->GetWebView()->mainFrame());
512 void SearchBox::OnSetSuggestionToPrefetch(const InstantSuggestion
& suggestion
) {
513 suggestion_
= suggestion
;
514 if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) {
515 DVLOG(1) << render_view() << " OnSetSuggestionToPrefetch";
516 extensions_v8::SearchBoxExtension::DispatchSuggestionChange(
517 render_view()->GetWebView()->mainFrame());
521 void SearchBox::OnSubmit(const base::string16
& query
,
522 const EmbeddedSearchRequestParams
& params
) {
524 embedded_search_request_params_
= params
;
525 if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) {
526 DVLOG(1) << render_view() << " OnSubmit";
527 extensions_v8::SearchBoxExtension::DispatchSubmit(
528 render_view()->GetWebView()->mainFrame());
534 void SearchBox::OnThemeChanged(const ThemeBackgroundInfo
& theme_info
) {
535 // Do not send duplicate notifications.
536 if (theme_info_
== theme_info
)
539 theme_info_
= theme_info
;
540 if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) {
541 extensions_v8::SearchBoxExtension::DispatchThemeChange(
542 render_view()->GetWebView()->mainFrame());
546 void SearchBox::OnToggleVoiceSearch() {
547 if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) {
548 extensions_v8::SearchBoxExtension::DispatchToggleVoiceSearch(
549 render_view()->GetWebView()->mainFrame());
553 GURL
SearchBox::GetURLForMostVisitedItem(InstantRestrictedID item_id
) const {
554 InstantMostVisitedItem item
;
555 return GetMostVisitedItemWithID(item_id
, &item
) ? item
.url
: GURL();
558 void SearchBox::Reset() {
560 embedded_search_request_params_
= EmbeddedSearchRequestParams();
561 suggestion_
= InstantSuggestion();
564 is_key_capture_enabled_
= false;
565 theme_info_
= ThemeBackgroundInfo();