OLD | NEW |
(Empty) | |
| 1 // Copyright 2013 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/instant/instant_controller_impl.h" |
| 6 |
| 7 #include "base/string_util.h" |
| 8 #include "base/stringprintf.h" |
| 9 #include "base/utf_string_conversions.h" |
| 10 #include "chrome/browser/autocomplete/autocomplete_match.h" |
| 11 #include "chrome/browser/history/history_service.h" |
| 12 #include "chrome/browser/history/history_service_factory.h" |
| 13 #include "chrome/browser/instant/instant_controller_utils.h" |
| 14 #include "chrome/browser/instant/instant_overlay.h" |
| 15 #include "chrome/browser/instant/instant_overlay_model.h" |
| 16 #include "chrome/browser/instant/instant_preloader.h" |
| 17 #include "chrome/browser/instant/instant_service.h" |
| 18 #include "chrome/browser/instant/instant_service_factory.h" |
| 19 #include "chrome/browser/ui/browser_instant_controller.h" |
| 20 #include "chrome/common/chrome_notification_types.h" |
| 21 #include "content/public/browser/navigation_entry.h" |
| 22 #include "content/public/browser/notification_service.h" |
| 23 #include "content/public/browser/web_contents.h" |
| 24 |
| 25 #if defined(OS_MACOSX) |
| 26 #include "content/public/browser/render_widget_host_view.h" |
| 27 #endif |
| 28 |
| 29 namespace { |
| 30 |
| 31 // An artificial delay (in milliseconds) we introduce before telling the Instant |
| 32 // page about the new omnibox bounds, in cases where the bounds shrink. This is |
| 33 // to avoid the page jumping up/down very fast in response to bounds changes. |
| 34 const int kUpdateBoundsDelayMS = 1000; |
| 35 |
| 36 } // namespace |
| 37 |
| 38 InstantControllerImpl::InstantControllerImpl( |
| 39 chrome::BrowserInstantController* browser, |
| 40 Profile* profile) |
| 41 : browser_(browser), |
| 42 service_(InstantServiceFactory::GetForProfile(profile)), |
| 43 last_verbatim_(false), |
| 44 last_transition_type_(content::PAGE_TRANSITION_LINK) { |
| 45 service_->AddObserver(this); |
| 46 model_.reset(new InstantOverlayModel(service_)); |
| 47 } |
| 48 |
| 49 InstantControllerImpl::~InstantControllerImpl() { |
| 50 service_->RemoveObserver(this); |
| 51 } |
| 52 |
| 53 bool InstantControllerImpl::Update(const AutocompleteMatch& match, |
| 54 const string16& user_text, |
| 55 const string16& full_text, |
| 56 size_t selection_start, |
| 57 size_t selection_end, |
| 58 bool verbatim, |
| 59 bool /* user_input_in_progress */, |
| 60 bool omnibox_popup_is_open, |
| 61 bool /* escape_pressed */, |
| 62 bool is_keyword_search) { |
| 63 // If the overlay is being committed, don't change anything. |
| 64 // TODO(sreeram): Add a browser test for this. |
| 65 if (IsOverlayBeingCommitted()) |
| 66 return false; |
| 67 |
| 68 // No overlay for URLs and keyword searches or if the user isn't typing. |
| 69 if (!AutocompleteMatch::IsSearchType(match.type) || is_keyword_search || |
| 70 !omnibox_popup_is_open || user_text.empty() || full_text.empty()) { |
| 71 HideOverlay(); |
| 72 return false; |
| 73 } |
| 74 |
| 75 if (!overlay_) { |
| 76 // If we don't have a valid (Instant-supporting) web contents ready, bail. |
| 77 if (!service_->preloader()->supports_instant()) |
| 78 return false; |
| 79 |
| 80 overlay_.reset(new InstantOverlay( |
| 81 this, service_, service_->preloader()->ReleaseContents())); |
| 82 } |
| 83 |
| 84 // Query is verbatim not only when |verbatim| is true (which indicates that |
| 85 // the user pressed Delete/Backspace), but also when there's any selection |
| 86 // (including inline autocompletion) or if the cursor is not at the end. |
| 87 verbatim = verbatim || selection_start != selection_end || |
| 88 selection_start != full_text.size(); |
| 89 |
| 90 last_omnibox_text_ = full_text; |
| 91 last_verbatim_ = verbatim; |
| 92 last_transition_type_ = match.transition; |
| 93 url_for_history_ = match.destination_url; |
| 94 |
| 95 service_->LogDebugEvent(base::StringPrintf("%p Update '%s' %s [%d,%d]", |
| 96 this, |
| 97 UTF16ToUTF8(last_omnibox_text_).c_str(), |
| 98 verbatim ? "verbatim" : "psychic", |
| 99 static_cast<int>(selection_start), |
| 100 static_cast<int>(selection_end))); |
| 101 |
| 102 overlay_->page()->Change(full_text, verbatim, selection_start, selection_end); |
| 103 |
| 104 content::NotificationService::current()->Notify( |
| 105 chrome::NOTIFICATION_INSTANT_UPDATED, |
| 106 content::NotificationService::AllSources(), |
| 107 content::NotificationService::NoDetails()); |
| 108 |
| 109 return true; |
| 110 } |
| 111 |
| 112 void InstantControllerImpl::HandleAutocompleteResults( |
| 113 const std::vector<AutocompleteProvider*>& /* providers */) { |
| 114 } |
| 115 |
| 116 bool InstantControllerImpl::OnUpOrDownKeyPressed(int /* count */) { |
| 117 return false; |
| 118 } |
| 119 |
| 120 void InstantControllerImpl::OnCancel(const AutocompleteMatch& /* match */, |
| 121 const string16& /* full_text */) { |
| 122 } |
| 123 |
| 124 bool InstantControllerImpl::IsOverlayingSearchResults() const { |
| 125 return model_->contents() != NULL; |
| 126 } |
| 127 |
| 128 content::WebContents* InstantControllerImpl::GetOverlayContents() const { |
| 129 return overlay_ ? overlay_->contents() : NULL; |
| 130 } |
| 131 |
| 132 const InstantOverlayModel* InstantControllerImpl::model() const { |
| 133 return model_.get(); |
| 134 } |
| 135 |
| 136 void InstantControllerImpl::AddOverlayModelObserver( |
| 137 InstantOverlayModelObserver* observer) { |
| 138 model_->AddObserver(observer); |
| 139 } |
| 140 |
| 141 void InstantControllerImpl::RemoveOverlayModelObserver( |
| 142 InstantOverlayModelObserver* observer) { |
| 143 model_->RemoveObserver(observer); |
| 144 } |
| 145 |
| 146 bool InstantControllerImpl::CommitIfPossible(InstantCommitType type) { |
| 147 if (!IsOverlayingSearchResults()) |
| 148 return false; |
| 149 |
| 150 service_->LogDebugEvent(base::StringPrintf("%p CommitIfPossible '%s' %s", |
| 151 this, |
| 152 UTF16ToUTF8(last_omnibox_text_).c_str(), |
| 153 InstantControllerUtils::CommitTypeToString(type).c_str())); |
| 154 |
| 155 if (type == INSTANT_COMMIT_FOCUS_LOST) |
| 156 overlay_->page()->Blur(last_omnibox_text_); |
| 157 else if (type != INSTANT_COMMIT_NAVIGATED) |
| 158 overlay_->page()->Submit(last_omnibox_text_); |
| 159 |
| 160 HistoryService* history = HistoryServiceFactory::GetForProfile( |
| 161 service_->profile(), Profile::EXPLICIT_ACCESS); |
| 162 if (history) { |
| 163 history->AddPage(url_for_history_, base::Time::Now(), NULL, 0, GURL(), |
| 164 history::RedirectList(), last_transition_type_, |
| 165 history::SOURCE_BROWSED, false); |
| 166 |
| 167 if (overlay_->page()->navigated_after_change() || |
| 168 type == INSTANT_COMMIT_NAVIGATED) { |
| 169 content::NavigationEntry* entry = |
| 170 GetOverlayContents()->GetController().GetLastCommittedEntry(); |
| 171 history->AddPage(entry->GetVirtualURL(), base::Time::Now(), |
| 172 history::SOURCE_BROWSED); |
| 173 history->SetPageTitle(entry->GetVirtualURL(), |
| 174 entry->GetTitleForDisplay("")); |
| 175 } |
| 176 } |
| 177 |
| 178 scoped_ptr<content::WebContents> contents = overlay_->ReleaseContents(); |
| 179 |
| 180 if (type == INSTANT_COMMIT_PRESSED_ALT_ENTER) { |
| 181 contents->GetController().PruneAllButActive(); |
| 182 } else { |
| 183 content::WebContents* active_tab = browser_->GetActiveWebContents(); |
| 184 contents->GetController().CopyStateFromAndPrune( |
| 185 &active_tab->GetController()); |
| 186 } |
| 187 |
| 188 browser_->CommitInstant(contents.Pass(), last_transition_type_, |
| 189 type == INSTANT_COMMIT_PRESSED_ALT_ENTER); |
| 190 |
| 191 service_->LogDebugEvent(base::StringPrintf("%p Committed", this)); |
| 192 |
| 193 HideOverlay(); |
| 194 |
| 195 return true; |
| 196 } |
| 197 |
| 198 scoped_ptr<content::WebContents> InstantControllerImpl::ReleaseNTPContents() { |
| 199 return scoped_ptr<content::WebContents>(); |
| 200 } |
| 201 |
| 202 // TODO(tonyg): This method only fires when the popup bounds change. It also |
| 203 // needs to fire when the overlay bounds change (e.g.: open/close info bar). |
| 204 void InstantControllerImpl::SetPopupBounds(const gfx::Rect& bounds) { |
| 205 if (popup_bounds_ == bounds) |
| 206 return; |
| 207 |
| 208 popup_bounds_ = bounds; |
| 209 |
| 210 if (bounds.height() > last_popup_bounds_.height()) { |
| 211 update_bounds_timer_.Stop(); |
| 212 SendPopupBoundsToPage(); |
| 213 } else if (!update_bounds_timer_.IsRunning()) { |
| 214 update_bounds_timer_.Start(FROM_HERE, |
| 215 base::TimeDelta::FromMilliseconds(kUpdateBoundsDelayMS), |
| 216 this, &InstantControllerImpl::SendPopupBoundsToPage); |
| 217 } |
| 218 } |
| 219 |
| 220 void InstantControllerImpl::SetOmniboxBounds(const gfx::Rect& /* bounds */) { |
| 221 } |
| 222 |
| 223 void InstantControllerImpl::OmniboxFocusChanged( |
| 224 OmniboxFocusState state, |
| 225 OmniboxFocusChangeReason reason, |
| 226 gfx::NativeView view_gaining_focus) { |
| 227 |
| 228 service_->LogDebugEvent(base::StringPrintf("%p OmniboxFocus %s due to %s", |
| 229 this, |
| 230 InstantControllerUtils::FocusStateToString(state).c_str(), |
| 231 InstantControllerUtils::FocusChangeReasonToString(reason).c_str())); |
| 232 |
| 233 if (state == OMNIBOX_FOCUS_NONE) { |
| 234 // Don't do anything if we don't have an overlay or if the overlay isn't |
| 235 // showing or if we are in the midst of committing the overlay. |
| 236 if (!overlay_ || !IsOverlayingSearchResults() || IsOverlayBeingCommitted()) |
| 237 return; |
| 238 |
| 239 view_gaining_focus = |
| 240 InstantControllerUtils::GetViewGainingFocus(view_gaining_focus); |
| 241 if (InstantControllerUtils::IsViewInContents(view_gaining_focus, |
| 242 GetOverlayContents())) |
| 243 CommitIfPossible(INSTANT_COMMIT_FOCUS_LOST); |
| 244 else |
| 245 HideOverlay(); |
| 246 } else { |
| 247 service_->preloader()->InitContents(); |
| 248 } |
| 249 } |
| 250 |
| 251 void InstantControllerImpl::TabDeactivated( |
| 252 content::WebContents* /* contents */) { |
| 253 if (GetOverlayContents()) |
| 254 HideOverlay(); |
| 255 } |
| 256 |
| 257 void InstantControllerImpl::SearchModeChanged( |
| 258 const chrome::search::Mode& /* old_mode */, |
| 259 const chrome::search::Mode& /* new_mode */) { |
| 260 } |
| 261 |
| 262 void InstantControllerImpl::ActiveTabChanged() { |
| 263 } |
| 264 |
| 265 void InstantControllerImpl::SwappedOverlayContents() { |
| 266 if (IsOverlayingSearchResults()) |
| 267 ShowOverlay(); |
| 268 } |
| 269 |
| 270 void InstantControllerImpl::FocusedOverlayContents() { |
| 271 if (IsOverlayingSearchResults()) |
| 272 browser_->InstantOverlayFocused(); |
| 273 } |
| 274 |
| 275 void InstantControllerImpl::RenderViewGone( |
| 276 const content::WebContents* /* contents */) { |
| 277 HideOverlay(); |
| 278 } |
| 279 |
| 280 void InstantControllerImpl::InitSearchBox( |
| 281 const content::WebContents* /* contents */) { |
| 282 } |
| 283 |
| 284 void InstantControllerImpl::InstantSupportDetermined( |
| 285 const content::WebContents* /* contents */) { |
| 286 if (!overlay_->page()->supports_instant()) |
| 287 HideOverlay(); |
| 288 service_->InstantSupportDetermined(); |
| 289 } |
| 290 |
| 291 void InstantControllerImpl::SetSuggestion( |
| 292 const content::WebContents* /* contents */, |
| 293 const InstantSuggestion& suggestion) { |
| 294 string16 text = suggestion.text; |
| 295 |
| 296 if (StartsWith(text, last_omnibox_text_, true)) { |
| 297 text.erase(0, last_omnibox_text_.size()); |
| 298 } else if (!InstantControllerUtils::NormalizeAndStripPrefix( |
| 299 &text, last_omnibox_text_)) { |
| 300 text.clear(); |
| 301 } |
| 302 |
| 303 if (last_verbatim_) |
| 304 text.clear(); |
| 305 |
| 306 if (!text.empty()) { |
| 307 service_->LogDebugEvent(base::StringPrintf("%p SetSuggestion '%s'", |
| 308 this, |
| 309 UTF16ToUTF8(text).c_str())); |
| 310 browser_->SetInstantSuggestion(InstantSuggestion( |
| 311 text, INSTANT_COMPLETE_NOW, INSTANT_SUGGESTION_SEARCH)); |
| 312 } |
| 313 |
| 314 ShowOverlay(); |
| 315 } |
| 316 |
| 317 void InstantControllerImpl::NavigateToURL( |
| 318 const content::WebContents* /* contents */, |
| 319 const GURL& /* url */, |
| 320 content::PageTransition /* transition */, |
| 321 WindowOpenDisposition /* disposition */) { |
| 322 } |
| 323 |
| 324 void InstantControllerImpl::ShowOverlay( |
| 325 const content::WebContents* /* contents */, |
| 326 int /* height */, |
| 327 InstantSizeUnits /* height_units */) { |
| 328 } |
| 329 |
| 330 void InstantControllerImpl::SetFocusState( |
| 331 const content::WebContents* /* contents */, |
| 332 OmniboxFocusState /* focus_state */) { |
| 333 } |
| 334 |
| 335 void InstantControllerImpl::DeleteMostVisitedItem( |
| 336 const content::WebContents* /* contents */, |
| 337 const GURL& /* url */) { |
| 338 } |
| 339 |
| 340 void InstantControllerImpl::UndoMostVisitedItemDeletion( |
| 341 const content::WebContents* /* contents */, |
| 342 const GURL& /* url */) { |
| 343 } |
| 344 |
| 345 void InstantControllerImpl::UndoAllMostVisitedItemDeletions( |
| 346 const content::WebContents* /* contents */) { |
| 347 } |
| 348 |
| 349 void InstantControllerImpl::InstantStatusChanged() { |
| 350 HideOverlay(); |
| 351 } |
| 352 |
| 353 void InstantControllerImpl::ThemeInfoChanged() { |
| 354 } |
| 355 |
| 356 void InstantControllerImpl::MostVisitedItemsChanged() { |
| 357 } |
| 358 |
| 359 bool InstantControllerImpl::IsOverlayBeingCommitted() const { |
| 360 return (overlay_ && overlay_->is_pointer_down_from_activate()) || |
| 361 (IsOverlayingSearchResults() && !GetOverlayContents()); |
| 362 } |
| 363 |
| 364 void InstantControllerImpl::ShowOverlay() { |
| 365 #if defined(OS_MACOSX) |
| 366 content::RenderWidgetHostView* rwhv = |
| 367 GetOverlayContents()->GetRenderWidgetHostView(); |
| 368 if (rwhv) |
| 369 rwhv->SetTakesFocusOnlyOnMouseDown(true); |
| 370 #endif |
| 371 model_->SetOverlayState(GetOverlayContents(), 100, INSTANT_SIZE_PERCENT); |
| 372 } |
| 373 |
| 374 void InstantControllerImpl::HideOverlay() { |
| 375 model_->SetOverlayState(NULL, 0, INSTANT_SIZE_PIXELS); |
| 376 last_popup_bounds_ = gfx::Rect(); |
| 377 if (overlay_) { |
| 378 overlay_->ReleaseContents(); |
| 379 MessageLoop::current()->DeleteSoon(FROM_HERE, overlay_.release()); |
| 380 } |
| 381 } |
| 382 |
| 383 void InstantControllerImpl::SendPopupBoundsToPage() { |
| 384 if (!overlay_ || last_popup_bounds_ == popup_bounds_ || |
| 385 overlay_->is_pointer_down_from_activate()) |
| 386 return; |
| 387 |
| 388 last_popup_bounds_ = popup_bounds_; |
| 389 |
| 390 gfx::Rect overlay_bounds = browser_->GetInstantBounds(); |
| 391 gfx::Rect intersection = gfx::IntersectRects(popup_bounds_, overlay_bounds); |
| 392 |
| 393 // Translate into window coordinates. |
| 394 if (!intersection.IsEmpty()) { |
| 395 intersection.Offset(-overlay_bounds.origin().x(), |
| 396 -overlay_bounds.origin().y()); |
| 397 } |
| 398 |
| 399 // In the current Chrome UI, these must always be true so they sanity check |
| 400 // the above operations. In a future UI, these may be removed or adjusted. |
| 401 // There is no point in sanity-checking intersection.y() because the omnibox |
| 402 // can be placed anywhere vertically relative to the overlay (for example, in |
| 403 // Mac fullscreen mode, the omnibox is fully enclosed by the overlay bounds). |
| 404 DCHECK_LE(0, intersection.x()); |
| 405 DCHECK_LE(0, intersection.width()); |
| 406 DCHECK_LE(0, intersection.height()); |
| 407 |
| 408 overlay_->page()->PopupBounds(intersection); |
| 409 } |
OLD | NEW |