| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/instant/instant_controller.h" | 5 #include "chrome/browser/instant/instant_controller.h" |
| 6 | 6 |
| 7 #include "base/bind.h" | |
| 8 #include "base/command_line.h" | 7 #include "base/command_line.h" |
| 9 #include "base/message_loop.h" | 8 #include "base/i18n/case_conversion.h" |
| 10 #include "base/metrics/histogram.h" | 9 #include "base/metrics/histogram.h" |
| 11 #include "build/build_config.h" | |
| 12 #include "chrome/browser/autocomplete/autocomplete_match.h" | 10 #include "chrome/browser/autocomplete/autocomplete_match.h" |
| 11 #include "chrome/browser/history/history.h" |
| 12 #include "chrome/browser/history/history_service_factory.h" |
| 13 #include "chrome/browser/history/history_tab_helper.h" |
| 13 #include "chrome/browser/instant/instant_controller_delegate.h" | 14 #include "chrome/browser/instant/instant_controller_delegate.h" |
| 14 #include "chrome/browser/instant/instant_loader.h" | 15 #include "chrome/browser/instant/instant_loader.h" |
| 15 #include "chrome/browser/platform_util.h" | 16 #include "chrome/browser/platform_util.h" |
| 16 #include "chrome/browser/prefs/pref_service.h" | 17 #include "chrome/browser/prefs/pref_service.h" |
| 17 #include "chrome/browser/profiles/profile.h" | |
| 18 #include "chrome/browser/search_engines/template_url.h" | |
| 19 #include "chrome/browser/search_engines/template_url_service.h" | 18 #include "chrome/browser/search_engines/template_url_service.h" |
| 20 #include "chrome/browser/search_engines/template_url_service_factory.h" | 19 #include "chrome/browser/search_engines/template_url_service_factory.h" |
| 21 #include "chrome/browser/ui/blocked_content/blocked_content_tab_helper.h" | |
| 22 #include "chrome/browser/ui/tab_contents/tab_contents.h" | 20 #include "chrome/browser/ui/tab_contents/tab_contents.h" |
| 23 #include "chrome/common/chrome_notification_types.h" | 21 #include "chrome/common/chrome_notification_types.h" |
| 24 #include "chrome/common/chrome_switches.h" | 22 #include "chrome/common/chrome_switches.h" |
| 25 #include "chrome/common/pref_names.h" | 23 #include "chrome/common/pref_names.h" |
| 24 #include "content/public/browser/favicon_status.h" |
| 25 #include "content/public/browser/navigation_entry.h" |
| 26 #include "content/public/browser/notification_service.h" | 26 #include "content/public/browser/notification_service.h" |
| 27 #include "content/public/browser/render_widget_host_view.h" | 27 #include "content/public/browser/render_widget_host_view.h" |
| 28 #include "content/public/browser/web_contents.h" | 28 #include "content/public/browser/web_contents.h" |
| 29 #include "ui/gfx/codec/png_codec.h" |
| 29 | 30 |
| 30 #if defined(TOOLKIT_VIEWS) | 31 #if defined(TOOLKIT_VIEWS) |
| 31 #include "ui/views/focus/focus_manager.h" | |
| 32 #include "ui/views/view.h" | |
| 33 #include "ui/views/widget/widget.h" | 32 #include "ui/views/widget/widget.h" |
| 34 #endif | 33 #endif |
| 35 | 34 |
| 35 namespace { |
| 36 |
| 37 enum PreviewUsageType { |
| 38 PREVIEW_CREATED = 0, |
| 39 PREVIEW_DELETED, |
| 40 PREVIEW_LOADED, |
| 41 PREVIEW_SHOWED, |
| 42 PREVIEW_COMMITTED, |
| 43 PREVIEW_NUM_TYPES, |
| 44 }; |
| 45 |
| 46 // An artificial delay (in milliseconds) we introduce before telling the Instant |
| 47 // page about the new omnibox bounds, in cases where the bounds shrink. This is |
| 48 // to avoid the page jumping up/down very fast in response to bounds changes. |
| 49 const int kUpdateBoundsDelayMS = 1000; |
| 50 |
| 51 // The maximum number of times we'll load a non-Instant-supporting search engine |
| 52 // before we give up and blacklist it for the rest of the browsing session. |
| 53 const int kMaxInstantSupportFailures = 10; |
| 54 |
| 55 std::string ModeToString(InstantController::Mode mode) { |
| 56 switch (mode) { |
| 57 case InstantController::INSTANT: return "_Instant"; |
| 58 case InstantController::SUGGEST: return "_Suggest"; |
| 59 case InstantController::HIDDEN: return "_Hidden"; |
| 60 case InstantController::SILENT: return "_Silent"; |
| 61 } |
| 62 |
| 63 NOTREACHED(); |
| 64 return std::string(); |
| 65 } |
| 66 |
| 67 void AddPreviewUsageForHistogram(InstantController::Mode mode, |
| 68 PreviewUsageType usage) { |
| 69 DCHECK(0 <= usage && usage < PREVIEW_NUM_TYPES) << usage; |
| 70 base::Histogram* histogram = base::LinearHistogram::FactoryGet( |
| 71 "Instant.Previews" + ModeToString(mode), 1, PREVIEW_NUM_TYPES, |
| 72 PREVIEW_NUM_TYPES + 1, base::Histogram::kUmaTargetedHistogramFlag); |
| 73 histogram->Add(usage); |
| 74 } |
| 75 |
| 76 void AddSessionStorageHistogram(InstantController::Mode mode, |
| 77 const TabContents* tab1, |
| 78 const TabContents* tab2) { |
| 79 base::Histogram* histogram = base::BooleanHistogram::FactoryGet( |
| 80 "Instant.SessionStorageNamespace" + ModeToString(mode), |
| 81 base::Histogram::kUmaTargetedHistogramFlag); |
| 82 histogram->AddBoolean( |
| 83 tab1->web_contents()->GetController().GetSessionStorageNamespace() == |
| 84 tab2->web_contents()->GetController().GetSessionStorageNamespace()); |
| 85 } |
| 86 |
| 87 } // namespace |
| 88 |
| 36 InstantController::InstantController(InstantControllerDelegate* delegate, | 89 InstantController::InstantController(InstantControllerDelegate* delegate, |
| 37 Mode mode) | 90 Mode mode) |
| 38 : delegate_(delegate), | 91 : delegate_(delegate), |
| 39 is_displayable_(false), | 92 mode_(mode), |
| 40 is_out_of_date_(true), | 93 last_active_tab_(NULL), |
| 41 commit_on_pointer_release_(false), | 94 last_verbatim_(false), |
| 95 last_complete_behavior_(INSTANT_COMPLETE_NOW), |
| 42 last_transition_type_(content::PAGE_TRANSITION_LINK), | 96 last_transition_type_(content::PAGE_TRANSITION_LINK), |
| 43 ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)), | 97 is_showing_(false), |
| 44 mode_(mode) { | 98 loader_processed_last_update_(false) { |
| 45 DCHECK(mode_ == INSTANT || mode_ == SUGGEST || mode_ == HIDDEN || | |
| 46 mode_ == SILENT); | |
| 47 } | 99 } |
| 48 | 100 |
| 49 InstantController::~InstantController() { | 101 InstantController::~InstantController() { |
| 102 if (GetPreviewContents()) |
| 103 AddPreviewUsageForHistogram(mode_, PREVIEW_DELETED); |
| 50 } | 104 } |
| 51 | 105 |
| 52 // static | 106 // static |
| 53 void InstantController::RegisterUserPrefs(PrefService* prefs) { | 107 void InstantController::RegisterUserPrefs(PrefService* prefs) { |
| 54 prefs->RegisterBooleanPref(prefs::kInstantConfirmDialogShown, | 108 prefs->RegisterBooleanPref(prefs::kInstantConfirmDialogShown, false, |
| 55 false, | |
| 56 PrefService::SYNCABLE_PREF); | 109 PrefService::SYNCABLE_PREF); |
| 57 prefs->RegisterBooleanPref(prefs::kInstantEnabled, | 110 prefs->RegisterBooleanPref(prefs::kInstantEnabled, false, |
| 58 false, | |
| 59 PrefService::SYNCABLE_PREF); | 111 PrefService::SYNCABLE_PREF); |
| 60 | 112 |
| 61 // TODO(jamescook): Move this to search controller. | 113 // TODO(jamescook): Move this to search controller. |
| 62 prefs->RegisterDoublePref(prefs::kInstantAnimationScaleFactor, | 114 prefs->RegisterDoublePref(prefs::kInstantAnimationScaleFactor, |
| 63 1.0, | 115 1.0, |
| 64 PrefService::UNSYNCABLE_PREF); | 116 PrefService::UNSYNCABLE_PREF); |
| 65 } | 117 } |
| 66 | 118 |
| 67 // static | 119 // static |
| 68 void InstantController::RecordMetrics(Profile* profile) { | |
| 69 UMA_HISTOGRAM_ENUMERATION("Instant.Status", IsEnabled(profile), 2); | |
| 70 } | |
| 71 | |
| 72 // static | |
| 73 bool InstantController::IsEnabled(Profile* profile) { | 120 bool InstantController::IsEnabled(Profile* profile) { |
| 74 const PrefService* prefs = profile->GetPrefs(); | 121 const PrefService* prefs = profile ? profile->GetPrefs() : NULL; |
| 75 return prefs && prefs->GetBoolean(prefs::kInstantEnabled); | 122 return prefs && prefs->GetBoolean(prefs::kInstantEnabled); |
| 76 } | 123 } |
| 77 | 124 |
| 78 // static | |
| 79 void InstantController::Enable(Profile* profile) { | |
| 80 PrefService* prefs = profile->GetPrefs(); | |
| 81 if (!prefs) | |
| 82 return; | |
| 83 | |
| 84 prefs->SetBoolean(prefs::kInstantEnabled, true); | |
| 85 prefs->SetBoolean(prefs::kInstantConfirmDialogShown, true); | |
| 86 UMA_HISTOGRAM_ENUMERATION("Instant.Preference", 1, 2); | |
| 87 } | |
| 88 | |
| 89 // static | |
| 90 void InstantController::Disable(Profile* profile) { | |
| 91 PrefService* prefs = profile->GetPrefs(); | |
| 92 if (!prefs) | |
| 93 return; | |
| 94 | |
| 95 prefs->SetBoolean(prefs::kInstantEnabled, false); | |
| 96 UMA_HISTOGRAM_ENUMERATION("Instant.Preference", 0, 2); | |
| 97 } | |
| 98 | |
| 99 bool InstantController::Update(const AutocompleteMatch& match, | 125 bool InstantController::Update(const AutocompleteMatch& match, |
| 100 const string16& user_text, | 126 const string16& user_text, |
| 101 bool verbatim, | 127 bool verbatim, |
| 102 string16* suggested_text) { | 128 string16* suggested_text, |
| 129 InstantCompleteBehavior* complete_behavior) { |
| 130 const TabContents* active_tab = delegate_->GetActiveTabContents(); |
| 131 |
| 132 // We could get here with no active tab if the Browser is closing. |
| 133 if (!active_tab) { |
| 134 Hide(); |
| 135 return false; |
| 136 } |
| 137 |
| 138 std::string instant_url; |
| 139 Profile* profile = active_tab->profile(); |
| 140 |
| 141 // If the match's TemplateURL isn't valid, it is likely not a query. |
| 142 if (!GetInstantURL(match.GetTemplateURL(profile), &instant_url)) { |
| 143 Hide(); |
| 144 return false; |
| 145 } |
| 146 |
| 147 string16 full_text = user_text + *suggested_text; |
| 148 |
| 149 if (full_text.empty()) { |
| 150 Hide(); |
| 151 return false; |
| 152 } |
| 153 |
| 154 // The presence of any suggested_text implies verbatim. |
| 155 DCHECK(suggested_text->empty() || verbatim) |
| 156 << user_text << "|" << *suggested_text; |
| 157 |
| 158 ResetLoader(instant_url, active_tab); |
| 159 last_active_tab_ = active_tab; |
| 160 |
| 161 // Track the non-Instant search URL for this query. |
| 162 url_for_history_ = match.destination_url; |
| 163 last_transition_type_ = match.transition; |
| 164 |
| 165 last_user_text_ = user_text; |
| 166 |
| 167 // Don't send an update to the loader if the query text hasn't changed. |
| 168 if (full_text == last_full_text_ && verbatim == last_verbatim_) { |
| 169 // Since we are updating |suggested_text|, shouldn't we also update |
| 170 // |last_full_text_|? No. There's no guarantee that our suggestion will |
| 171 // actually be inline autocompleted. For example, it may get trumped by |
| 172 // a history suggestion. If our suggestion does make it, the omnibox will |
| 173 // call Update() again, at which time we'll update |last_full_text_|. |
| 174 *suggested_text = last_suggestion_; |
| 175 *complete_behavior = last_complete_behavior_; |
| 176 |
| 177 // We need to call Show() here because of this: |
| 178 // 1. User has typed a query (say Q). Instant overlay is showing results. |
| 179 // 2. User arrows-down to a URL entry or erases all omnibox text. Both of |
| 180 // these cause the overlay to Hide(). |
| 181 // 3. User arrows-up to Q or types Q again. The last text we processed is |
| 182 // still Q, so we don't Update() the loader, but we do need to Show(). |
| 183 if (loader_processed_last_update_ && mode_ == INSTANT) |
| 184 Show(); |
| 185 return true; |
| 186 } |
| 187 |
| 188 last_full_text_ = full_text; |
| 189 last_verbatim_ = verbatim; |
| 190 loader_processed_last_update_ = false; |
| 191 |
| 192 // Reset the last suggestion, as it's no longer valid. |
| 103 suggested_text->clear(); | 193 suggested_text->clear(); |
| 104 | 194 last_suggestion_.clear(); |
| 105 is_out_of_date_ = false; | 195 *complete_behavior = last_complete_behavior_ = INSTANT_COMPLETE_NOW; |
| 106 commit_on_pointer_release_ = false; | 196 |
| 107 last_transition_type_ = match.transition; | 197 if (mode_ != SILENT) { |
| 108 last_url_ = match.destination_url; | 198 loader_->Update(last_full_text_, last_verbatim_); |
| 109 last_user_text_ = user_text; | 199 |
| 110 | 200 content::NotificationService::current()->Notify( |
| 111 TabContents* tab_contents = delegate_->GetInstantHostTabContents(); | 201 chrome::NOTIFICATION_INSTANT_CONTROLLER_UPDATED, |
| 112 if (!tab_contents) { | 202 content::Source<InstantController>(this), |
| 113 Hide(); | 203 content::NotificationService::NoDetails()); |
| 114 return false; | 204 } |
| 115 } | 205 |
| 116 | |
| 117 Profile* profile = tab_contents->profile(); | |
| 118 const TemplateURL* template_url = match.GetTemplateURL(profile); | |
| 119 const TemplateURL* default_t_url = | |
| 120 TemplateURLServiceFactory::GetForProfile(profile) | |
| 121 ->GetDefaultSearchProvider(); | |
| 122 if (!IsValidInstantTemplateURL(template_url) || !default_t_url || | |
| 123 template_url->id() != default_t_url->id()) { | |
| 124 Hide(); | |
| 125 return false; | |
| 126 } | |
| 127 | |
| 128 if (!loader_.get() || loader_->template_url_id() != template_url->id()) | |
| 129 loader_.reset(new InstantLoader(this, template_url->id(), std::string())); | |
| 130 | |
| 131 if (mode_ == SILENT) { | |
| 132 // For the SILENT mode, we process |user_text| at commit time. | |
| 133 loader_->MaybeLoadInstantURL(tab_contents, template_url); | |
| 134 return true; | |
| 135 } | |
| 136 | |
| 137 UpdateLoader(tab_contents, template_url, match.destination_url, | |
| 138 match.transition, user_text, verbatim, suggested_text); | |
| 139 | |
| 140 content::NotificationService::current()->Notify( | |
| 141 chrome::NOTIFICATION_INSTANT_CONTROLLER_UPDATED, | |
| 142 content::Source<InstantController>(this), | |
| 143 content::NotificationService::NoDetails()); | |
| 144 return true; | 206 return true; |
| 145 } | 207 } |
| 146 | 208 |
| 209 // TODO(tonyg): This method only fires when the omnibox bounds change. It also |
| 210 // needs to fire when the preview bounds change (e.g.: open/close info bar). |
| 147 void InstantController::SetOmniboxBounds(const gfx::Rect& bounds) { | 211 void InstantController::SetOmniboxBounds(const gfx::Rect& bounds) { |
| 148 if (omnibox_bounds_ == bounds) | 212 if (omnibox_bounds_ == bounds || mode_ != INSTANT) |
| 149 return; | 213 return; |
| 150 | 214 |
| 151 // Always track the omnibox bounds. That way if Update is later invoked the | |
| 152 // bounds are in sync. | |
| 153 omnibox_bounds_ = bounds; | 215 omnibox_bounds_ = bounds; |
| 154 | 216 if (omnibox_bounds_.height() > last_omnibox_bounds_.height()) { |
| 155 if (loader_.get() && !is_out_of_date_ && mode_ == INSTANT) | 217 update_bounds_timer_.Stop(); |
| 156 loader_->SetOmniboxBounds(bounds); | 218 SendBoundsToPage(); |
| 157 } | 219 } else if (!update_bounds_timer_.IsRunning()) { |
| 158 | 220 update_bounds_timer_.Start(FROM_HERE, |
| 159 void InstantController::DestroyPreviewContents() { | 221 base::TimeDelta::FromMilliseconds(kUpdateBoundsDelayMS), this, |
| 160 if (!loader_.get()) { | 222 &InstantController::SendBoundsToPage); |
| 161 // We're not showing anything, nothing to do. | 223 } |
| 162 return; | 224 } |
| 163 } | 225 |
| 164 | 226 TabContents* InstantController::GetPreviewContents() const { |
| 165 if (is_displayable_) { | 227 return loader_.get() ? loader_->preview_contents() : NULL; |
| 166 is_displayable_ = false; | 228 } |
| 229 |
| 230 void InstantController::Hide() { |
| 231 last_active_tab_ = NULL; |
| 232 if (is_showing_) { |
| 233 is_showing_ = false; |
| 167 delegate_->HideInstant(); | 234 delegate_->HideInstant(); |
| 168 } | 235 } |
| 169 delete ReleasePreviewContents(INSTANT_COMMIT_DESTROY, NULL); | |
| 170 } | |
| 171 | |
| 172 void InstantController::Hide() { | |
| 173 is_out_of_date_ = true; | |
| 174 commit_on_pointer_release_ = false; | |
| 175 if (is_displayable_) { | |
| 176 is_displayable_ = false; | |
| 177 delegate_->HideInstant(); | |
| 178 } | |
| 179 } | 236 } |
| 180 | 237 |
| 181 bool InstantController::IsCurrent() const { | 238 bool InstantController::IsCurrent() const { |
| 182 // TODO(mmenke): See if we can do something more intelligent in the | 239 DCHECK(IsOutOfDate() || GetPreviewContents()); |
| 183 // navigation pending case. | 240 return !IsOutOfDate() && GetPreviewContents() && loader_->supports_instant(); |
| 184 return is_displayable_ && !loader_->IsNavigationPending() && | |
| 185 !loader_->needs_reload(); | |
| 186 } | |
| 187 | |
| 188 bool InstantController::PrepareForCommit() { | |
| 189 if (is_out_of_date_ || !loader_.get()) | |
| 190 return false; | |
| 191 | |
| 192 // If we are in the visible (INSTANT) mode, return the status of the preview. | |
| 193 if (mode_ == INSTANT) | |
| 194 return IsCurrent(); | |
| 195 | |
| 196 TabContents* tab_contents = delegate_->GetInstantHostTabContents(); | |
| 197 if (!tab_contents) | |
| 198 return false; | |
| 199 | |
| 200 const TemplateURL* template_url = | |
| 201 TemplateURLServiceFactory::GetForProfile(tab_contents->profile()) | |
| 202 ->GetDefaultSearchProvider(); | |
| 203 if (!IsValidInstantTemplateURL(template_url) || | |
| 204 loader_->template_url_id() != template_url->id() || | |
| 205 loader_->IsNavigationPending() || | |
| 206 loader_->is_determining_if_page_supports_instant()) { | |
| 207 return false; | |
| 208 } | |
| 209 | |
| 210 // In the SUGGEST and HIDDEN modes, we must have sent an Update() by now, so | |
| 211 // check if the loader failed to process it. | |
| 212 if ((mode_ == SUGGEST || mode_ == HIDDEN) | |
| 213 && (!loader_->ready() || !loader_->http_status_ok())) { | |
| 214 return false; | |
| 215 } | |
| 216 | |
| 217 // Ignore the suggested text, as we are about to commit the verbatim query. | |
| 218 string16 suggested_text; | |
| 219 UpdateLoader(tab_contents, template_url, last_url_, last_transition_type_, | |
| 220 last_user_text_, true, &suggested_text); | |
| 221 return true; | |
| 222 } | 241 } |
| 223 | 242 |
| 224 TabContents* InstantController::CommitCurrentPreview(InstantCommitType type) { | 243 TabContents* InstantController::CommitCurrentPreview(InstantCommitType type) { |
| 225 DCHECK(loader_.get()); | 244 const TabContents* active_tab = delegate_->GetActiveTabContents(); |
| 226 TabContents* tab_contents = delegate_->GetInstantHostTabContents(); | 245 TabContents* preview = ReleasePreviewContents(type); |
| 227 DCHECK(tab_contents); | 246 AddSessionStorageHistogram(mode_, active_tab, preview); |
| 228 TabContents* preview = ReleasePreviewContents(type, tab_contents); | |
| 229 preview->web_contents()->GetController().CopyStateFromAndPrune( | 247 preview->web_contents()->GetController().CopyStateFromAndPrune( |
| 230 &tab_contents->web_contents()->GetController()); | 248 &active_tab->web_contents()->GetController()); |
| 231 delegate_->CommitInstant(preview); | 249 delegate_->CommitInstant(preview); |
| 232 CompleteRelease(preview); | |
| 233 return preview; | 250 return preview; |
| 234 } | 251 } |
| 235 | 252 |
| 236 bool InstantController::CommitIfCurrent() { | 253 TabContents* InstantController::ReleasePreviewContents(InstantCommitType type) { |
| 237 if (IsCurrent()) { | 254 TabContents* preview = loader_->ReleasePreviewContents(type, last_full_text_); |
| 238 CommitCurrentPreview(INSTANT_COMMIT_PRESSED_ENTER); | 255 |
| 239 return true; | 256 // If the preview page has navigated since the last Update(), we need to add |
| 240 } | 257 // the navigation to history ourselves. Else, the page will navigate after |
| 241 return false; | 258 // commit, and it will be added to history in the usual manner. |
| 242 } | 259 scoped_refptr<history::HistoryAddPageArgs> last_navigation = |
| 243 | 260 loader_->last_navigation(); |
| 244 void InstantController::SetCommitOnPointerRelease() { | 261 if (last_navigation != NULL) { |
| 245 commit_on_pointer_release_ = true; | 262 content::NavigationEntry* entry = |
| 246 } | 263 preview->web_contents()->GetController().GetActiveEntry(); |
| 247 | 264 DCHECK_EQ(last_navigation->url, entry->GetURL()); |
| 248 bool InstantController::IsPointerDownFromActivate() { | 265 |
| 249 DCHECK(loader_.get()); | 266 // Add the page to history. |
| 250 return loader_->IsPointerDownFromActivate(); | 267 preview->history_tab_helper()->UpdateHistoryForNavigation(last_navigation); |
| 251 } | 268 |
| 252 | 269 // Update the page title. |
| 253 #if defined(OS_MACOSX) | 270 preview->history_tab_helper()->UpdateHistoryPageTitle(*entry); |
| 271 |
| 272 // Update the favicon. |
| 273 FaviconService* favicon_service = |
| 274 preview->profile()->GetFaviconService(Profile::EXPLICIT_ACCESS); |
| 275 if (favicon_service && entry->GetFavicon().valid && |
| 276 entry->GetFavicon().image.IsEmpty()) { |
| 277 std::vector<unsigned char> image_data; |
| 278 // TODO: Add all variants once the history service supports it. |
| 279 gfx::PNGCodec::EncodeBGRASkBitmap( |
| 280 entry->GetFavicon().image.AsBitmap(), false, &image_data); |
| 281 favicon_service->SetFavicon(entry->GetURL(), |
| 282 entry->GetFavicon().url, |
| 283 image_data, |
| 284 history::FAVICON); |
| 285 } |
| 286 } |
| 287 |
| 288 // Add a fake history entry with a non-Instant search URL, so that search |
| 289 // terms extraction (for autocomplete history matches) works. |
| 290 HistoryService* history = HistoryServiceFactory::GetForProfile( |
| 291 preview->profile(), Profile::EXPLICIT_ACCESS); |
| 292 if (history) { |
| 293 history->AddPage(url_for_history_, NULL, 0, GURL(), last_transition_type_, |
| 294 history::RedirectList(), history::SOURCE_BROWSED, false); |
| 295 } |
| 296 |
| 297 AddPreviewUsageForHistogram(mode_, PREVIEW_COMMITTED); |
| 298 |
| 299 // We may have gotten here from CommitInstant(), which means the loader may |
| 300 // still be on the stack. So, schedule a destruction for later. |
| 301 MessageLoop::current()->DeleteSoon(FROM_HERE, loader_.release()); |
| 302 |
| 303 // This call is here to hide the preview and reset view state. It won't |
| 304 // actually delete |loader_| because it was just released to DeleteSoon(). |
| 305 DeleteLoader(); |
| 306 |
| 307 return preview; |
| 308 } |
| 309 |
| 310 // TODO(sreeram): Since we never delete the loader except when committing |
| 311 // Instant, the loader may have a very stale page. Reload it when stale. |
| 254 void InstantController::OnAutocompleteLostFocus( | 312 void InstantController::OnAutocompleteLostFocus( |
| 255 gfx::NativeView view_gaining_focus) { | 313 gfx::NativeView view_gaining_focus) { |
| 256 // If |IsPointerDownFromActivate()| returns false, the RenderWidgetHostView | 314 DCHECK(!is_showing_ || GetPreviewContents()); |
| 257 // did not receive a mouseDown event. Therefore, we should destroy the | 315 |
| 258 // preview. Otherwise, the RWHV was clicked, so we commit the preview. | 316 // If the preview is not showing, nothing to do. |
| 259 if (!IsCurrent() || !IsPointerDownFromActivate()) | 317 if (!is_showing_ || !GetPreviewContents()) |
| 260 DestroyPreviewContents(); | 318 return; |
| 261 else | 319 |
| 262 SetCommitOnPointerRelease(); | 320 #if defined(OS_MACOSX) |
| 263 } | 321 if (!loader_->IsPointerDownFromActivate()) |
| 322 Hide(); |
| 264 #else | 323 #else |
| 265 void InstantController::OnAutocompleteLostFocus( | |
| 266 gfx::NativeView view_gaining_focus) { | |
| 267 if (!IsCurrent()) { | |
| 268 DestroyPreviewContents(); | |
| 269 return; | |
| 270 } | |
| 271 | |
| 272 content::RenderWidgetHostView* rwhv = | 324 content::RenderWidgetHostView* rwhv = |
| 273 GetPreviewContents()->web_contents()->GetRenderWidgetHostView(); | 325 GetPreviewContents()->web_contents()->GetRenderWidgetHostView(); |
| 274 if (!view_gaining_focus || !rwhv) { | 326 if (!view_gaining_focus || !rwhv) { |
| 275 DestroyPreviewContents(); | 327 Hide(); |
| 276 return; | 328 return; |
| 277 } | 329 } |
| 278 | 330 |
| 279 #if defined(TOOLKIT_VIEWS) | 331 #if defined(TOOLKIT_VIEWS) |
| 280 // For views the top level widget is always focused. If the focus change | 332 // For views the top level widget is always focused. If the focus change |
| 281 // originated in views determine the child Widget from the view that is being | 333 // originated in views determine the child Widget from the view that is being |
| 282 // focused. | 334 // focused. |
| 283 views::Widget* widget = | 335 views::Widget* widget = |
| 284 views::Widget::GetWidgetForNativeView(view_gaining_focus); | 336 views::Widget::GetWidgetForNativeView(view_gaining_focus); |
| 285 if (widget) { | 337 if (widget) { |
| 286 views::FocusManager* focus_manager = widget->GetFocusManager(); | 338 views::FocusManager* focus_manager = widget->GetFocusManager(); |
| 287 if (focus_manager && focus_manager->is_changing_focus() && | 339 if (focus_manager && focus_manager->is_changing_focus() && |
| 288 focus_manager->GetFocusedView() && | 340 focus_manager->GetFocusedView() && |
| 289 focus_manager->GetFocusedView()->GetWidget()) { | 341 focus_manager->GetFocusedView()->GetWidget()) { |
| 290 view_gaining_focus = | 342 view_gaining_focus = |
| 291 focus_manager->GetFocusedView()->GetWidget()->GetNativeView(); | 343 focus_manager->GetFocusedView()->GetWidget()->GetNativeView(); |
| 292 } | 344 } |
| 293 } | 345 } |
| 294 #endif | 346 #endif |
| 295 | 347 |
| 296 gfx::NativeView tab_view = | 348 gfx::NativeView tab_view = |
| 297 GetPreviewContents()->web_contents()->GetNativeView(); | 349 GetPreviewContents()->web_contents()->GetNativeView(); |
| 350 |
| 298 // Focus is going to the renderer. | 351 // Focus is going to the renderer. |
| 299 if (rwhv->GetNativeView() == view_gaining_focus || | 352 if (rwhv->GetNativeView() == view_gaining_focus || |
| 300 tab_view == view_gaining_focus) { | 353 tab_view == view_gaining_focus) { |
| 301 if (!IsPointerDownFromActivate()) { | |
| 302 // If the mouse is not down, focus is not going to the renderer. Someone | |
| 303 // else moved focus and we shouldn't commit. | |
| 304 DestroyPreviewContents(); | |
| 305 return; | |
| 306 } | |
| 307 | 354 |
| 308 // We're showing instant results. As instant results may shift when | 355 // If the mouse is not down, focus is not going to the renderer. Someone |
| 309 // committing we commit on the mouse up. This way a slow click still works | 356 // else moved focus and we shouldn't commit. |
| 310 // fine. | 357 if (!loader_->IsPointerDownFromActivate()) |
| 311 SetCommitOnPointerRelease(); | 358 Hide(); |
| 359 |
| 312 return; | 360 return; |
| 313 } | 361 } |
| 314 | 362 |
| 315 // Walk up the view hierarchy. If the view gaining focus is a subview of the | 363 // Walk up the view hierarchy. If the view gaining focus is a subview of the |
| 316 // WebContents view (such as a windowed plugin or http auth dialog), we want | 364 // WebContents view (such as a windowed plugin or http auth dialog), we want |
| 317 // to keep the preview contents. Otherwise, focus has gone somewhere else, | 365 // to keep the preview contents. Otherwise, focus has gone somewhere else, |
| 318 // such as the JS inspector, and we want to cancel the preview. | 366 // such as the JS inspector, and we want to cancel the preview. |
| 319 gfx::NativeView view_gaining_focus_ancestor = view_gaining_focus; | 367 gfx::NativeView view_gaining_focus_ancestor = view_gaining_focus; |
| 320 while (view_gaining_focus_ancestor && | 368 while (view_gaining_focus_ancestor && |
| 321 view_gaining_focus_ancestor != tab_view) { | 369 view_gaining_focus_ancestor != tab_view) { |
| 322 view_gaining_focus_ancestor = | 370 view_gaining_focus_ancestor = |
| 323 platform_util::GetParent(view_gaining_focus_ancestor); | 371 platform_util::GetParent(view_gaining_focus_ancestor); |
| 324 } | 372 } |
| 325 | 373 |
| 326 if (view_gaining_focus_ancestor) { | 374 if (view_gaining_focus_ancestor) { |
| 327 CommitCurrentPreview(INSTANT_COMMIT_FOCUS_LOST); | 375 CommitCurrentPreview(INSTANT_COMMIT_FOCUS_LOST); |
| 328 return; | 376 return; |
| 329 } | 377 } |
| 330 | 378 |
| 331 DestroyPreviewContents(); | 379 Hide(); |
| 332 } | |
| 333 #endif | 380 #endif |
| 381 } |
| 334 | 382 |
| 335 void InstantController::OnAutocompleteGotFocus() { | 383 void InstantController::OnAutocompleteGotFocus() { |
| 336 TabContents* tab_contents = delegate_->GetInstantHostTabContents(); | 384 const TabContents* active_tab = delegate_->GetActiveTabContents(); |
| 337 if (!tab_contents) | 385 |
| 338 return; | 386 // We could get here with no active tab if the Browser is closing. |
| 339 | 387 if (!active_tab) |
| 388 return; |
| 389 |
| 390 // Since we don't have any autocomplete match to work with, we'll just use |
| 391 // the default search provider's Instant URL. |
| 340 const TemplateURL* template_url = | 392 const TemplateURL* template_url = |
| 341 TemplateURLServiceFactory::GetForProfile(tab_contents->profile()) | 393 TemplateURLServiceFactory::GetForProfile(active_tab->profile())-> |
| 342 ->GetDefaultSearchProvider(); | 394 GetDefaultSearchProvider(); |
| 343 if (!IsValidInstantTemplateURL(template_url)) | 395 |
| 344 return; | 396 std::string instant_url; |
| 345 | 397 if (!GetInstantURL(template_url, &instant_url)) |
| 346 if (!loader_.get() || loader_->template_url_id() != template_url->id()) | 398 return; |
| 347 loader_.reset(new InstantLoader(this, template_url->id(), std::string())); | 399 |
| 348 loader_->MaybeLoadInstantURL(tab_contents, template_url); | 400 ResetLoader(instant_url, active_tab); |
| 349 } | 401 } |
| 350 | 402 |
| 351 TabContents* InstantController::ReleasePreviewContents( | 403 bool InstantController::commit_on_pointer_release() const { |
| 352 InstantCommitType type, | 404 return GetPreviewContents() && loader_->IsPointerDownFromActivate(); |
| 353 TabContents* current_tab) { | 405 } |
| 354 if (!loader_.get()) | 406 |
| 355 return NULL; | 407 void InstantController::SetSuggestions( |
| 356 | |
| 357 TabContents* tab = loader_->ReleasePreviewContents(type, current_tab); | |
| 358 ClearBlacklist(); | |
| 359 is_out_of_date_ = true; | |
| 360 is_displayable_ = false; | |
| 361 commit_on_pointer_release_ = false; | |
| 362 omnibox_bounds_ = gfx::Rect(); | |
| 363 loader_.reset(); | |
| 364 return tab; | |
| 365 } | |
| 366 | |
| 367 void InstantController::CompleteRelease(TabContents* tab) { | |
| 368 tab->blocked_content_tab_helper()->SetAllContentsBlocked(false); | |
| 369 } | |
| 370 | |
| 371 TabContents* InstantController::GetPreviewContents() const { | |
| 372 return loader_.get() ? loader_->preview_contents() : NULL; | |
| 373 } | |
| 374 | |
| 375 void InstantController::InstantStatusChanged(InstantLoader* loader) { | |
| 376 DCHECK(loader_.get()); | |
| 377 UpdateIsDisplayable(); | |
| 378 } | |
| 379 | |
| 380 void InstantController::SetSuggestedTextFor( | |
| 381 InstantLoader* loader, | 408 InstantLoader* loader, |
| 382 const string16& text, | 409 const std::vector<string16>& suggestions, |
| 383 InstantCompleteBehavior behavior) { | 410 InstantCompleteBehavior behavior) { |
| 384 if (is_out_of_date_) | 411 DCHECK_EQ(loader_.get(), loader); |
| 385 return; | 412 if (loader_ != loader || IsOutOfDate() || mode_ == SILENT || mode_ == HIDDEN) |
| 386 | 413 return; |
| 387 if (mode_ == INSTANT || mode_ == SUGGEST) | 414 |
| 388 delegate_->SetSuggestedText(text, behavior); | 415 loader_processed_last_update_ = true; |
| 389 } | 416 |
| 390 | 417 string16 suggestion; |
| 391 gfx::Rect InstantController::GetInstantBounds() { | 418 if (!suggestions.empty()) |
| 392 return delegate_->GetInstantBounds(); | 419 suggestion = suggestions[0]; |
| 393 } | 420 |
| 394 | 421 string16 suggestion_lower = base::i18n::ToLower(suggestion); |
| 395 bool InstantController::ShouldCommitInstantOnPointerRelease() { | 422 string16 user_text_lower = base::i18n::ToLower(last_user_text_); |
| 396 return commit_on_pointer_release_; | 423 if (user_text_lower.size() >= suggestion_lower.size() || |
| 424 suggestion_lower.compare(0, user_text_lower.size(), user_text_lower)) { |
| 425 suggestion.clear(); |
| 426 } else { |
| 427 suggestion.erase(0, last_user_text_.size()); |
| 428 } |
| 429 |
| 430 last_suggestion_ = suggestion; |
| 431 last_complete_behavior_ = behavior; |
| 432 if (!last_verbatim_) |
| 433 delegate_->SetSuggestedText(suggestion, behavior); |
| 434 |
| 435 if (mode_ != SUGGEST) |
| 436 Show(); |
| 397 } | 437 } |
| 398 | 438 |
| 399 void InstantController::CommitInstantLoader(InstantLoader* loader) { | 439 void InstantController::CommitInstantLoader(InstantLoader* loader) { |
| 400 if (loader_.get() && loader_.get() == loader) { | 440 DCHECK_EQ(loader_.get(), loader); |
| 401 CommitCurrentPreview(INSTANT_COMMIT_FOCUS_LOST); | 441 DCHECK(is_showing_ && !IsOutOfDate()) << is_showing_; |
| 442 if (loader_ != loader || !is_showing_ || IsOutOfDate()) |
| 443 return; |
| 444 |
| 445 CommitCurrentPreview(INSTANT_COMMIT_FOCUS_LOST); |
| 446 } |
| 447 |
| 448 void InstantController::InstantLoaderPreviewLoaded(InstantLoader* loader) { |
| 449 DCHECK_EQ(loader_.get(), loader); |
| 450 AddPreviewUsageForHistogram(mode_, PREVIEW_LOADED); |
| 451 } |
| 452 |
| 453 void InstantController::InstantSupportDetermined(InstantLoader* loader, |
| 454 bool supports_instant) { |
| 455 DCHECK_EQ(loader_.get(), loader); |
| 456 if (supports_instant) { |
| 457 blacklisted_urls_.erase(loader->instant_url()); |
| 402 } else { | 458 } else { |
| 403 // This can happen if the mouse was down, we swapped out the preview and | 459 ++blacklisted_urls_[loader->instant_url()]; |
| 404 // the mouse was released. Generally this shouldn't happen, but if it does | 460 if (loader_ == loader) { |
| 405 // revert. | 461 if (GetPreviewContents()) |
| 406 DestroyPreviewContents(); | 462 AddPreviewUsageForHistogram(mode_, PREVIEW_DELETED); |
| 407 } | 463 |
| 408 } | 464 // Because of the state of the stack, we can't destroy the loader now. |
| 409 | 465 MessageLoop::current()->DeleteSoon(FROM_HERE, loader_.release()); |
| 410 void InstantController::InstantLoaderDoesntSupportInstant( | 466 DeleteLoader(); |
| 411 InstantLoader* loader) { | 467 } |
| 412 VLOG(1) << "provider does not support instant"; | 468 } |
| 413 | 469 |
| 414 // Don't attempt to use instant for this search engine again. | 470 content::Details<const bool> details(&supports_instant); |
| 415 BlacklistFromInstant(); | 471 content::NotificationService::current()->Notify( |
| 416 } | 472 chrome::NOTIFICATION_INSTANT_SUPPORT_DETERMINED, |
| 417 | 473 content::NotificationService::AllSources(), |
| 418 void InstantController::AddToBlacklist(InstantLoader* loader, const GURL& url) { | 474 details); |
| 419 // Don't attempt to use instant for this search engine again. | |
| 420 BlacklistFromInstant(); | |
| 421 } | 475 } |
| 422 | 476 |
| 423 void InstantController::SwappedTabContents(InstantLoader* loader) { | 477 void InstantController::SwappedTabContents(InstantLoader* loader) { |
| 424 if (is_displayable_) | 478 DCHECK_EQ(loader_.get(), loader); |
| 425 delegate_->ShowInstant(loader->preview_contents()); | 479 if (loader_ == loader && is_showing_) |
| 426 } | 480 delegate_->ShowInstant(); |
| 427 | 481 } |
| 428 void InstantController::InstantLoaderContentsFocused() { | 482 |
| 483 void InstantController::InstantLoaderContentsFocused(InstantLoader* loader) { |
| 484 DCHECK_EQ(loader_.get(), loader); |
| 485 DCHECK(is_showing_ && !IsOutOfDate()) << is_showing_; |
| 429 #if defined(USE_AURA) | 486 #if defined(USE_AURA) |
| 430 // On aura the omnibox only receives a focus lost if we initiate the focus | 487 // On aura the omnibox only receives a focus lost if we initiate the focus |
| 431 // change. This does that. | 488 // change. This does that. |
| 432 if (mode_ == INSTANT) | 489 if (is_showing_ && !IsOutOfDate()) |
| 433 delegate_->InstantPreviewFocused(); | 490 delegate_->InstantPreviewFocused(); |
| 434 #endif | 491 #endif |
| 435 } | 492 } |
| 436 | 493 |
| 437 void InstantController::UpdateIsDisplayable() { | 494 void InstantController::ResetLoader(const std::string& instant_url, |
| 438 bool displayable = !is_out_of_date_ && loader_.get() && loader_->ready() && | 495 const TabContents* active_tab) { |
| 439 loader_->http_status_ok(); | 496 if (GetPreviewContents() && loader_->instant_url() != instant_url) |
| 440 if (displayable == is_displayable_ || mode_ != INSTANT) | 497 DeleteLoader(); |
| 441 return; | 498 |
| 442 | 499 if (!GetPreviewContents()) { |
| 443 is_displayable_ = displayable; | 500 loader_.reset(new InstantLoader(this, instant_url, active_tab)); |
| 444 if (!is_displayable_) { | 501 loader_->Init(); |
| 445 delegate_->HideInstant(); | 502 AddPreviewUsageForHistogram(mode_, PREVIEW_CREATED); |
| 446 } else { | 503 } |
| 447 delegate_->ShowInstant(loader_->preview_contents()); | 504 } |
| 448 content::NotificationService::current()->Notify( | 505 |
| 449 chrome::NOTIFICATION_INSTANT_CONTROLLER_SHOWN, | 506 void InstantController::DeleteLoader() { |
| 450 content::Source<InstantController>(this), | 507 Hide(); |
| 451 content::NotificationService::NoDetails()); | 508 last_full_text_.clear(); |
| 452 } | 509 last_user_text_.clear(); |
| 453 } | 510 last_verbatim_ = false; |
| 454 | 511 last_suggestion_.clear(); |
| 455 void InstantController::UpdateLoader(TabContents* tab_contents, | 512 last_complete_behavior_ = INSTANT_COMPLETE_NOW; |
| 456 const TemplateURL* template_url, | 513 last_transition_type_ = content::PAGE_TRANSITION_LINK; |
| 457 const GURL& url, | 514 last_omnibox_bounds_ = gfx::Rect(); |
| 458 content::PageTransition transition_type, | 515 url_for_history_ = GURL(); |
| 459 const string16& user_text, | 516 if (GetPreviewContents()) |
| 460 bool verbatim, | 517 AddPreviewUsageForHistogram(mode_, PREVIEW_DELETED); |
| 461 string16* suggested_text) { | 518 loader_.reset(); |
| 462 if (mode_ == INSTANT) | 519 } |
| 463 loader_->SetOmniboxBounds(omnibox_bounds_); | 520 |
| 464 loader_->Update(tab_contents, template_url, url, transition_type, user_text, | 521 void InstantController::Show() { |
| 465 verbatim, suggested_text); | 522 if (!is_showing_) { |
| 466 UpdateIsDisplayable(); | 523 is_showing_ = true; |
| 467 // For the HIDDEN and SILENT modes, don't send back suggestions. | 524 delegate_->ShowInstant(); |
| 468 if (mode_ == HIDDEN || mode_ == SILENT) | 525 AddPreviewUsageForHistogram(mode_, PREVIEW_SHOWED); |
| 469 suggested_text->clear(); | 526 } |
| 470 } | 527 } |
| 471 | 528 |
| 472 // Returns true if |template_url| is a valid TemplateURL for use by instant. | 529 void InstantController::SendBoundsToPage() { |
| 473 bool InstantController::IsValidInstantTemplateURL( | 530 if (last_omnibox_bounds_ == omnibox_bounds_ || IsOutOfDate() || |
| 474 const TemplateURL* template_url) { | 531 !GetPreviewContents() || loader_->IsPointerDownFromActivate()) { |
| 475 return template_url && template_url->id() && | 532 return; |
| 476 template_url->instant_url_ref().SupportsReplacement() && | 533 } |
| 477 !IsBlacklistedFromInstant(template_url->id()); | 534 |
| 478 } | 535 last_omnibox_bounds_ = omnibox_bounds_; |
| 479 | 536 gfx::Rect preview_bounds = delegate_->GetInstantBounds(); |
| 480 void InstantController::BlacklistFromInstant() { | 537 gfx::Rect intersection = omnibox_bounds_.Intersect(preview_bounds); |
| 481 if (!loader_.get()) | 538 |
| 482 return; | 539 // Translate into window coordinates. |
| 483 | 540 if (!intersection.IsEmpty()) { |
| 484 DCHECK(loader_->template_url_id()); | 541 intersection.Offset(-preview_bounds.origin().x(), |
| 485 blacklisted_ids_.insert(loader_->template_url_id()); | 542 -preview_bounds.origin().y()); |
| 486 | 543 } |
| 487 // Because of the state of the stack we can't destroy the loader now. | 544 |
| 488 ScheduleDestroy(loader_.release()); | 545 // In the current Chrome UI, these must always be true so they sanity check |
| 489 UpdateIsDisplayable(); | 546 // the above operations. In a future UI, these may be removed or adjusted. |
| 490 } | 547 // There is no point in sanity-checking |intersection.y()| because the omnibox |
| 491 | 548 // can be placed anywhere vertically relative to the preview (for example, in |
| 492 bool InstantController::IsBlacklistedFromInstant(TemplateURLID id) { | 549 // Mac fullscreen mode, the omnibox is fully enclosed by the preview bounds). |
| 493 return blacklisted_ids_.count(id) > 0; | 550 DCHECK_LE(0, intersection.x()); |
| 494 } | 551 DCHECK_LE(0, intersection.width()); |
| 495 | 552 DCHECK_LE(0, intersection.height()); |
| 496 void InstantController::ClearBlacklist() { | 553 |
| 497 blacklisted_ids_.clear(); | 554 loader_->SetOmniboxBounds(intersection); |
| 498 } | 555 } |
| 499 | 556 |
| 500 void InstantController::ScheduleDestroy(InstantLoader* loader) { | 557 bool InstantController::GetInstantURL(const TemplateURL* template_url, |
| 501 loaders_to_destroy_.push_back(loader); | 558 std::string* instant_url) const { |
| 502 if (!weak_factory_.HasWeakPtrs()) { | 559 CommandLine* command_line = CommandLine::ForCurrentProcess(); |
| 503 MessageLoop::current()->PostTask( | 560 if (command_line->HasSwitch(switches::kInstantURL)) { |
| 504 FROM_HERE, base::Bind(&InstantController::DestroyLoaders, | 561 *instant_url = command_line->GetSwitchValueASCII(switches::kInstantURL); |
| 505 weak_factory_.GetWeakPtr())); | 562 return true; |
| 506 } | 563 } |
| 507 } | 564 |
| 508 | 565 if (!template_url) |
| 509 void InstantController::DestroyLoaders() { | 566 return false; |
| 510 loaders_to_destroy_.clear(); | 567 |
| 511 } | 568 const TemplateURLRef& instant_url_ref = template_url->instant_url_ref(); |
| 569 if (!instant_url_ref.IsValid() || !instant_url_ref.SupportsReplacement()) |
| 570 return false; |
| 571 |
| 572 *instant_url = instant_url_ref.ReplaceSearchTerms( |
| 573 TemplateURLRef::SearchTermsArgs(string16())); |
| 574 |
| 575 std::map<std::string, int>::const_iterator iter = |
| 576 blacklisted_urls_.find(*instant_url); |
| 577 if (iter != blacklisted_urls_.end() && |
| 578 iter->second > kMaxInstantSupportFailures) { |
| 579 instant_url->clear(); |
| 580 return false; |
| 581 } |
| 582 |
| 583 return true; |
| 584 } |
| 585 |
| 586 bool InstantController::IsOutOfDate() const { |
| 587 return !last_active_tab_ || |
| 588 last_active_tab_ != delegate_->GetActiveTabContents(); |
| 589 } |
| OLD | NEW |