[email protected] | f0dc7523 | 2012-01-05 01:07:00 | [diff] [blame] | 1 | // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
[email protected] | fd2b9ce | 2010-08-11 04:03:57 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
[email protected] | 6b723f8 | 2010-10-05 20:14:27 | [diff] [blame] | 5 | #include "chrome/browser/instant/instant_controller.h" |
[email protected] | fd2b9ce | 2010-08-11 04:03:57 | [diff] [blame] | 6 | |
[email protected] | fd2b9ce | 2010-08-11 04:03:57 | [diff] [blame] | 7 | #include "base/command_line.h" |
[email protected] | c55e3b8 | 2012-08-09 15:27:05 | [diff] [blame] | 8 | #include "base/i18n/case_conversion.h" |
[email protected] | 7e03e81 | 2010-11-15 23:01:01 | [diff] [blame] | 9 | #include "base/metrics/histogram.h" |
[email protected] | 853e01b | 2012-09-21 20:14:11 | [diff] [blame] | 10 | #include "base/time.h" |
[email protected] | 941b260c | 2012-08-23 23:00:47 | [diff] [blame] | 11 | #include "base/utf_string_conversions.h" |
| 12 | #include "chrome/browser/autocomplete/autocomplete_provider.h" |
[email protected] | f3d2b31 | 2012-08-23 22:27:59 | [diff] [blame] | 13 | #include "chrome/browser/favicon/favicon_service_factory.h" |
[email protected] | c55e3b8 | 2012-08-09 15:27:05 | [diff] [blame] | 14 | #include "chrome/browser/history/history.h" |
| 15 | #include "chrome/browser/history/history_service_factory.h" |
| 16 | #include "chrome/browser/history/history_tab_helper.h" |
[email protected] | 37c917b | 2012-06-28 15:53:59 | [diff] [blame] | 17 | #include "chrome/browser/instant/instant_controller_delegate.h" |
[email protected] | 6b723f8 | 2010-10-05 20:14:27 | [diff] [blame] | 18 | #include "chrome/browser/instant/instant_loader.h" |
[email protected] | ba6680f | 2010-11-01 20:35:08 | [diff] [blame] | 19 | #include "chrome/browser/platform_util.h" |
[email protected] | 018cbb2 | 2010-10-11 22:32:09 | [diff] [blame] | 20 | #include "chrome/browser/prefs/pref_service.h" |
[email protected] | 8e5c89a | 2011-06-07 18:13:33 | [diff] [blame] | 21 | #include "chrome/browser/search_engines/template_url_service.h" |
| 22 | #include "chrome/browser/search_engines/template_url_service_factory.h" |
[email protected] | b67d0a4 | 2012-09-04 20:57:35 | [diff] [blame] | 23 | #include "chrome/browser/ui/search/search.h" |
[email protected] | a7eef8f | 2012-06-08 22:19:39 | [diff] [blame] | 24 | #include "chrome/browser/ui/tab_contents/tab_contents.h" |
[email protected] | 43211582 | 2011-07-10 15:52:27 | [diff] [blame] | 25 | #include "chrome/common/chrome_notification_types.h" |
[email protected] | fd2b9ce | 2010-08-11 04:03:57 | [diff] [blame] | 26 | #include "chrome/common/chrome_switches.h" |
[email protected] | 018cbb2 | 2010-10-11 22:32:09 | [diff] [blame] | 27 | #include "chrome/common/pref_names.h" |
[email protected] | c55e3b8 | 2012-08-09 15:27:05 | [diff] [blame] | 28 | #include "content/public/browser/favicon_status.h" |
| 29 | #include "content/public/browser/navigation_entry.h" |
[email protected] | ad50def5 | 2011-10-19 23:17:07 | [diff] [blame] | 30 | #include "content/public/browser/notification_service.h" |
[email protected] | 5626b089 | 2012-02-20 14:46:58 | [diff] [blame] | 31 | #include "content/public/browser/render_widget_host_view.h" |
[email protected] | ef9572e | 2012-01-04 22:14:12 | [diff] [blame] | 32 | #include "content/public/browser/web_contents.h" |
[email protected] | fd2b9ce | 2010-08-11 04:03:57 | [diff] [blame] | 33 | |
[email protected] | 6eb8ea9 | 2011-08-22 21:01:41 | [diff] [blame] | 34 | #if defined(TOOLKIT_VIEWS) |
[email protected] | c13be0d | 2011-11-22 02:09:58 | [diff] [blame] | 35 | #include "ui/views/widget/widget.h" |
[email protected] | 6eb8ea9 | 2011-08-22 21:01:41 | [diff] [blame] | 36 | #endif |
| 37 | |
[email protected] | c55e3b8 | 2012-08-09 15:27:05 | [diff] [blame] | 38 | namespace { |
| 39 | |
| 40 | enum PreviewUsageType { |
| 41 | PREVIEW_CREATED = 0, |
| 42 | PREVIEW_DELETED, |
| 43 | PREVIEW_LOADED, |
| 44 | PREVIEW_SHOWED, |
| 45 | PREVIEW_COMMITTED, |
| 46 | PREVIEW_NUM_TYPES, |
| 47 | }; |
| 48 | |
| 49 | // An artificial delay (in milliseconds) we introduce before telling the Instant |
| 50 | // page about the new omnibox bounds, in cases where the bounds shrink. This is |
| 51 | // to avoid the page jumping up/down very fast in response to bounds changes. |
| 52 | const int kUpdateBoundsDelayMS = 1000; |
| 53 | |
| 54 | // The maximum number of times we'll load a non-Instant-supporting search engine |
| 55 | // before we give up and blacklist it for the rest of the browsing session. |
| 56 | const int kMaxInstantSupportFailures = 10; |
| 57 | |
[email protected] | 08a9f97 | 2012-08-27 23:23:15 | [diff] [blame] | 58 | // If an Instant page has not been used in these many milliseconds, it is |
| 59 | // reloaded so that the page does not become stale. |
| 60 | const int kStaleLoaderTimeoutMS = 3 * 3600 * 1000; |
| 61 | |
[email protected] | c55e3b8 | 2012-08-09 15:27:05 | [diff] [blame] | 62 | std::string ModeToString(InstantController::Mode mode) { |
| 63 | switch (mode) { |
[email protected] | b67d0a4 | 2012-09-04 20:57:35 | [diff] [blame] | 64 | case InstantController::EXTENDED: return "_Extended"; |
[email protected] | c55e3b8 | 2012-08-09 15:27:05 | [diff] [blame] | 65 | case InstantController::INSTANT: return "_Instant"; |
| 66 | case InstantController::SUGGEST: return "_Suggest"; |
| 67 | case InstantController::HIDDEN: return "_Hidden"; |
| 68 | case InstantController::SILENT: return "_Silent"; |
[email protected] | b67d0a4 | 2012-09-04 20:57:35 | [diff] [blame] | 69 | case InstantController::DISABLED: return "_Disabled"; |
[email protected] | c55e3b8 | 2012-08-09 15:27:05 | [diff] [blame] | 70 | } |
| 71 | |
| 72 | NOTREACHED(); |
| 73 | return std::string(); |
| 74 | } |
| 75 | |
| 76 | void AddPreviewUsageForHistogram(InstantController::Mode mode, |
| 77 | PreviewUsageType usage) { |
| 78 | DCHECK(0 <= usage && usage < PREVIEW_NUM_TYPES) << usage; |
| 79 | base::Histogram* histogram = base::LinearHistogram::FactoryGet( |
| 80 | "Instant.Previews" + ModeToString(mode), 1, PREVIEW_NUM_TYPES, |
| 81 | PREVIEW_NUM_TYPES + 1, base::Histogram::kUmaTargetedHistogramFlag); |
| 82 | histogram->Add(usage); |
| 83 | } |
| 84 | |
| 85 | void AddSessionStorageHistogram(InstantController::Mode mode, |
| 86 | const TabContents* tab1, |
| 87 | const TabContents* tab2) { |
| 88 | base::Histogram* histogram = base::BooleanHistogram::FactoryGet( |
| 89 | "Instant.SessionStorageNamespace" + ModeToString(mode), |
| 90 | base::Histogram::kUmaTargetedHistogramFlag); |
[email protected] | d1198fd | 2012-08-13 22:50:19 | [diff] [blame] | 91 | const content::SessionStorageNamespaceMap& session_storage_map1 = |
| 92 | tab1->web_contents()->GetController().GetSessionStorageNamespaceMap(); |
| 93 | const content::SessionStorageNamespaceMap& session_storage_map2 = |
| 94 | tab2->web_contents()->GetController().GetSessionStorageNamespaceMap(); |
| 95 | bool is_session_storage_the_same = |
| 96 | session_storage_map1.size() == session_storage_map2.size(); |
| 97 | if (is_session_storage_the_same) { |
| 98 | // The size is the same, so let's check that all entries match. |
| 99 | for (content::SessionStorageNamespaceMap::const_iterator |
| 100 | it1 = session_storage_map1.begin(), |
| 101 | it2 = session_storage_map2.begin(); |
| 102 | it1 != session_storage_map1.end() && |
| 103 | it2 != session_storage_map2.end(); |
| 104 | ++it1, ++it2) { |
| 105 | if (it1->first != it2->first || it1->second != it2->second) { |
| 106 | is_session_storage_the_same = false; |
| 107 | break; |
| 108 | } |
| 109 | } |
| 110 | } |
| 111 | histogram->AddBoolean(is_session_storage_the_same); |
[email protected] | c55e3b8 | 2012-08-09 15:27:05 | [diff] [blame] | 112 | } |
| 113 | |
[email protected] | b67d0a4 | 2012-09-04 20:57:35 | [diff] [blame] | 114 | InstantController::Mode GetModeForProfile(Profile* profile) { |
| 115 | if (!profile || profile->IsOffTheRecord() || !profile->GetPrefs() || |
| 116 | !profile->GetPrefs()->GetBoolean(prefs::kInstantEnabled)) |
| 117 | return InstantController::DISABLED; |
[email protected] | c55e3b8 | 2012-08-09 15:27:05 | [diff] [blame] | 118 | |
[email protected] | b67d0a4 | 2012-09-04 20:57:35 | [diff] [blame] | 119 | return chrome::search::IsInstantExtendedAPIEnabled(profile) ? |
| 120 | InstantController::EXTENDED : InstantController::INSTANT; |
[email protected] | 03bb953d | 2010-09-14 21:38:30 | [diff] [blame] | 121 | } |
| 122 | |
[email protected] | b67d0a4 | 2012-09-04 20:57:35 | [diff] [blame] | 123 | } // namespace |
| 124 | |
[email protected] | 6b723f8 | 2010-10-05 20:14:27 | [diff] [blame] | 125 | InstantController::~InstantController() { |
[email protected] | c55e3b8 | 2012-08-09 15:27:05 | [diff] [blame] | 126 | if (GetPreviewContents()) |
| 127 | AddPreviewUsageForHistogram(mode_, PREVIEW_DELETED); |
[email protected] | 03bb953d | 2010-09-14 21:38:30 | [diff] [blame] | 128 | } |
| 129 | |
[email protected] | 7e03e81 | 2010-11-15 23:01:01 | [diff] [blame] | 130 | // static |
[email protected] | b67d0a4 | 2012-09-04 20:57:35 | [diff] [blame] | 131 | InstantController* InstantController::CreateInstant( |
| 132 | Profile* profile, |
| 133 | InstantControllerDelegate* delegate) { |
| 134 | const Mode mode = GetModeForProfile(profile); |
| 135 | return mode == DISABLED ? NULL : new InstantController(delegate, mode); |
| 136 | } |
| 137 | |
| 138 | // static |
| 139 | bool InstantController::IsExtendedAPIEnabled(Profile* profile) { |
| 140 | return GetModeForProfile(profile) == EXTENDED; |
| 141 | } |
| 142 | |
| 143 | // static |
| 144 | bool InstantController::IsInstantEnabled(Profile* profile) { |
| 145 | const Mode mode = GetModeForProfile(profile); |
| 146 | return mode == EXTENDED || mode == INSTANT; |
| 147 | } |
| 148 | |
| 149 | // static |
| 150 | bool InstantController::IsSuggestEnabled(Profile* profile) { |
| 151 | const Mode mode = GetModeForProfile(profile); |
| 152 | return mode == EXTENDED || mode == INSTANT || mode == SUGGEST; |
| 153 | } |
| 154 | |
| 155 | // static |
[email protected] | 7e03e81 | 2010-11-15 23:01:01 | [diff] [blame] | 156 | void InstantController::RegisterUserPrefs(PrefService* prefs) { |
[email protected] | c55e3b8 | 2012-08-09 15:27:05 | [diff] [blame] | 157 | prefs->RegisterBooleanPref(prefs::kInstantConfirmDialogShown, false, |
[email protected] | 18c2ffac | 2011-09-16 21:07:29 | [diff] [blame] | 158 | PrefService::SYNCABLE_PREF); |
[email protected] | c55e3b8 | 2012-08-09 15:27:05 | [diff] [blame] | 159 | prefs->RegisterBooleanPref(prefs::kInstantEnabled, false, |
[email protected] | 18c2ffac | 2011-09-16 21:07:29 | [diff] [blame] | 160 | PrefService::SYNCABLE_PREF); |
[email protected] | 7ab402e6 | 2012-06-20 19:49:18 | [diff] [blame] | 161 | |
| 162 | // TODO(jamescook): Move this to search controller. |
[email protected] | b67d0a4 | 2012-09-04 20:57:35 | [diff] [blame] | 163 | prefs->RegisterDoublePref(prefs::kInstantAnimationScaleFactor, 1.0, |
[email protected] | 7ab402e6 | 2012-06-20 19:49:18 | [diff] [blame] | 164 | PrefService::UNSYNCABLE_PREF); |
[email protected] | 7e03e81 | 2010-11-15 23:01:01 | [diff] [blame] | 165 | } |
| 166 | |
[email protected] | 05cf2fa | 2012-05-29 20:36:06 | [diff] [blame] | 167 | bool InstantController::Update(const AutocompleteMatch& match, |
[email protected] | 6b723f8 | 2010-10-05 20:14:27 | [diff] [blame] | 168 | const string16& user_text, |
[email protected] | cd22332 | 2012-08-26 19:54:42 | [diff] [blame] | 169 | const string16& full_text, |
| 170 | bool verbatim) { |
[email protected] | c55e3b8 | 2012-08-09 15:27:05 | [diff] [blame] | 171 | const TabContents* active_tab = delegate_->GetActiveTabContents(); |
[email protected] | bdf1d86 | 2010-11-24 02:46:11 | [diff] [blame] | 172 | |
[email protected] | c55e3b8 | 2012-08-09 15:27:05 | [diff] [blame] | 173 | // We could get here with no active tab if the Browser is closing. |
| 174 | if (!active_tab) { |
| 175 | Hide(); |
| 176 | return false; |
| 177 | } |
| 178 | |
| 179 | std::string instant_url; |
| 180 | Profile* profile = active_tab->profile(); |
| 181 | |
[email protected] | de59556 | 2012-09-07 18:31:04 | [diff] [blame] | 182 | // If the match's TemplateURL is valid, it's a search query; use it. If it's |
| 183 | // not valid, it's likely a URL; in EXTENDED mode, try using the default |
| 184 | // search engine's TemplateURL instead. |
[email protected] | 2e4164d | 2012-09-18 18:22:15 | [diff] [blame] | 185 | const GURL& tab_url = active_tab->web_contents()->GetURL(); |
| 186 | if (GetInstantURL(match.GetTemplateURL(profile), tab_url, &instant_url)) { |
[email protected] | de59556 | 2012-09-07 18:31:04 | [diff] [blame] | 187 | ResetLoader(instant_url, active_tab); |
| 188 | } else if (mode_ != EXTENDED || !CreateDefaultLoader()) { |
[email protected] | c55e3b8 | 2012-08-09 15:27:05 | [diff] [blame] | 189 | Hide(); |
| 190 | return false; |
| 191 | } |
| 192 | |
[email protected] | c55e3b8 | 2012-08-09 15:27:05 | [diff] [blame] | 193 | if (full_text.empty()) { |
| 194 | Hide(); |
| 195 | return false; |
| 196 | } |
| 197 | |
[email protected] | c55e3b8 | 2012-08-09 15:27:05 | [diff] [blame] | 198 | // Track the non-Instant search URL for this query. |
| 199 | url_for_history_ = match.destination_url; |
[email protected] | 97b6c4f | 2010-09-27 19:31:26 | [diff] [blame] | 200 | last_transition_type_ = match.transition; |
[email protected] | de59556 | 2012-09-07 18:31:04 | [diff] [blame] | 201 | last_active_tab_ = active_tab; |
| 202 | last_match_was_search_ = AutocompleteMatch::IsSearchType(match.type); |
[email protected] | c55e3b8 | 2012-08-09 15:27:05 | [diff] [blame] | 203 | |
[email protected] | cd22332 | 2012-08-26 19:54:42 | [diff] [blame] | 204 | // In EXTENDED mode, we send only |user_text| as the query text. In all other |
| 205 | // modes, we use the entire |full_text|. |
| 206 | const string16& query_text = mode_ == EXTENDED ? user_text : full_text; |
[email protected] | b67d0a4 | 2012-09-04 20:57:35 | [diff] [blame] | 207 | string16 last_query_text = mode_ == EXTENDED ? |
| 208 | last_user_text_ : last_full_text_; |
[email protected] | 64f09b8 | 2011-10-13 16:17:20 | [diff] [blame] | 209 | last_user_text_ = user_text; |
[email protected] | cd22332 | 2012-08-26 19:54:42 | [diff] [blame] | 210 | last_full_text_ = full_text; |
[email protected] | 64f09b8 | 2011-10-13 16:17:20 | [diff] [blame] | 211 | |
[email protected] | c55e3b8 | 2012-08-09 15:27:05 | [diff] [blame] | 212 | // Don't send an update to the loader if the query text hasn't changed. |
[email protected] | cd22332 | 2012-08-26 19:54:42 | [diff] [blame] | 213 | if (query_text == last_query_text && verbatim == last_verbatim_) { |
| 214 | // Reuse the last suggestion, as it's still valid. |
| 215 | delegate_->SetSuggestedText(last_suggestion_.text, |
| 216 | last_suggestion_.behavior); |
[email protected] | fd2b9ce | 2010-08-11 04:03:57 | [diff] [blame] | 217 | |
[email protected] | c55e3b8 | 2012-08-09 15:27:05 | [diff] [blame] | 218 | // We need to call Show() here because of this: |
| 219 | // 1. User has typed a query (say Q). Instant overlay is showing results. |
| 220 | // 2. User arrows-down to a URL entry or erases all omnibox text. Both of |
| 221 | // these cause the overlay to Hide(). |
| 222 | // 3. User arrows-up to Q or types Q again. The last text we processed is |
| 223 | // still Q, so we don't Update() the loader, but we do need to Show(). |
[email protected] | 941b260c | 2012-08-23 23:00:47 | [diff] [blame] | 224 | if (loader_processed_last_update_ && |
[email protected] | b67d0a4 | 2012-09-04 20:57:35 | [diff] [blame] | 225 | (mode_ == INSTANT || mode_ == EXTENDED)) |
[email protected] | b0628ad | 2012-09-20 00:48:47 | [diff] [blame] | 226 | Show(100, INSTANT_SIZE_PERCENT); |
[email protected] | 64f09b8 | 2011-10-13 16:17:20 | [diff] [blame] | 227 | return true; |
| 228 | } |
[email protected] | 36d5e559 | 2010-11-15 20:45:59 | [diff] [blame] | 229 | |
[email protected] | c55e3b8 | 2012-08-09 15:27:05 | [diff] [blame] | 230 | last_verbatim_ = verbatim; |
| 231 | loader_processed_last_update_ = false; |
[email protected] | 0e7cb68 | 2012-08-15 04:04:38 | [
|