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