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