1 // Copyright (c) 2011 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_loader.h"
6
7 #include <algorithm>
8 #include <string>
9 #include <utility>
10 #include <vector>
11
12 #include "base/command_line.h"
13 #include "base/string_number_conversions.h"
14 #include "base/timer.h"
15 #include "base/utf_string_conversions.h"
16 #include "base/values.h"
17 #include "chrome/browser/favicon_service.h"
18 #include "chrome/browser/history/history_marshaling.h"
19 #include "chrome/browser/instant/instant_loader_delegate.h"
20 #include "chrome/browser/profiles/profile.h"
21 #include "chrome/browser/search_engines/template_url.h"
22 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
23 #include "chrome/common/chrome_switches.h"
24 #include "chrome/common/render_messages.h"
25 #include "content/browser/renderer_host/render_view_host.h"
26 #include "content/browser/renderer_host/render_widget_host.h"
27 #include "content/browser/renderer_host/render_widget_host_view.h"
28 #include "content/browser/tab_contents/navigation_controller.h"
29 #include "content/browser/tab_contents/navigation_entry.h"
30 #include "content/browser/tab_contents/provisional_load_details.h"
31 #include "content/browser/tab_contents/tab_contents.h"
32 #include "content/browser/tab_contents/tab_contents_delegate.h"
33 #include "content/browser/tab_contents/tab_contents_view.h"
34 #include "content/common/notification_details.h"
35 #include "content/common/notification_observer.h"
36 #include "content/common/notification_registrar.h"
37 #include "content/common/notification_service.h"
38 #include "content/common/notification_source.h"
39 #include "content/common/notification_type.h"
40 #include "content/common/page_transition_types.h"
41 #include "content/common/renderer_preferences.h"
42 #include "net/http/http_util.h"
43 #include "ui/base/l10n/l10n_util.h"
44 #include "ui/gfx/codec/png_codec.h"
45
46 namespace {
47
48 // Number of ms to delay before updating the omnibox bounds. This is only used
49 // when the bounds of the omnibox shrinks. If the bounds grows, we update
50 // immediately.
51 const int kUpdateBoundsDelayMS = 1000;
52
53 // If this status code is seen instant is disabled for the specified host.
54 const int kHostBlacklistStatusCode = 403;
55
56 // Header and value set for all loads.
57 const char kPreviewHeader[] = "X-Purpose:";
58 const char kPreviewHeaderValue[] = "preview";
59
60 } // namespace
61
62 // FrameLoadObserver is responsible for determining if the page supports
63 // instant after it has loaded.
64 class InstantLoader::FrameLoadObserver : public NotificationObserver {
65 public:
FrameLoadObserver(InstantLoader * loader,TabContents * tab_contents,const string16 & text,bool verbatim)66 FrameLoadObserver(InstantLoader* loader,
67 TabContents* tab_contents,
68 const string16& text,
69 bool verbatim)
70 : loader_(loader),
71 tab_contents_(tab_contents),
72 text_(text),
73 verbatim_(verbatim),
74 unique_id_(tab_contents_->controller().pending_entry()->unique_id()) {
75 registrar_.Add(this, NotificationType::LOAD_COMPLETED_MAIN_FRAME,
76 Source<TabContents>(tab_contents_));
77 }
78
79 // Sets the text to send to the page.
set_text(const string16 & text)80 void set_text(const string16& text) { text_ = text; }
81
82 // Sets whether verbatim results are obtained rather than predictive.
set_verbatim(bool verbatim)83 void set_verbatim(bool verbatim) { verbatim_ = verbatim; }
84
85 // NotificationObserver:
86 virtual void Observe(NotificationType type,
87 const NotificationSource& source,
88 const NotificationDetails& details) OVERRIDE;
89
90 private:
91 InstantLoader* loader_;
92
93 // The TabContents we're listening for changes on.
94 TabContents* tab_contents_;
95
96 // Text to send down to the page.
97 string16 text_;
98
99 // Whether verbatim results are obtained.
100 bool verbatim_;
101
102 // unique_id of the NavigationEntry we're waiting on.
103 const int unique_id_;
104
105 // Registers and unregisters us for notifications.
106 NotificationRegistrar registrar_;
107
108 DISALLOW_COPY_AND_ASSIGN(FrameLoadObserver);
109 };
110
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)111 void InstantLoader::FrameLoadObserver::Observe(
112 NotificationType type,
113 const NotificationSource& source,
114 const NotificationDetails& details) {
115 switch (type.value) {
116 case NotificationType::LOAD_COMPLETED_MAIN_FRAME: {
117 int page_id = *(Details<int>(details).ptr());
118 NavigationEntry* active_entry =
119 tab_contents_->controller().GetActiveEntry();
120 if (!active_entry || active_entry->page_id() != page_id ||
121 active_entry->unique_id() != unique_id_) {
122 return;
123 }
124 loader_->SendBoundsToPage(true);
125 // TODO: support real cursor position.
126 int text_length = static_cast<int>(text_.size());
127 tab_contents_->render_view_host()->DetermineIfPageSupportsInstant(
128 text_, verbatim_, text_length, text_length);
129 break;
130 }
131 default:
132 NOTREACHED();
133 break;
134 }
135 }
136
137 // TabContentsDelegateImpl -----------------------------------------------------
138
139 class InstantLoader::TabContentsDelegateImpl
140 : public TabContentsDelegate,
141 public NotificationObserver,
142 public TabContentsObserver {
143 public:
144 explicit TabContentsDelegateImpl(InstantLoader* loader);
145
146 // Invoked prior to loading a new URL.
147 void PrepareForNewLoad();
148
149 // Invoked when the preview paints. Invokes PreviewPainted on the loader.
150 void PreviewPainted();
151
is_mouse_down_from_activate() const152 bool is_mouse_down_from_activate() const {
153 return is_mouse_down_from_activate_;
154 }
155
set_user_typed_before_load()156 void set_user_typed_before_load() { user_typed_before_load_ = true; }
157
158 // Sets the last URL that will be added to history when CommitHistory is
159 // invoked and removes all but the first navigation.
160 void SetLastHistoryURLAndPrune(const GURL& url);
161
162 // Commits the currently buffered history.
163 void CommitHistory(bool supports_instant);
164
165 void RegisterForPaintNotifications(RenderWidgetHost* render_widget_host);
166
167 void UnregisterForPaintNotifications();
168
169 // NotificationObserver:
170 virtual void Observe(NotificationType type,
171 const NotificationSource& source,
172 const NotificationDetails& details) OVERRIDE;
173
174 // TabContentsDelegate:
175 virtual void OpenURLFromTab(TabContents* source,
176 const GURL& url, const GURL& referrer,
177 WindowOpenDisposition disposition,
178 PageTransition::Type transition) OVERRIDE;
179 virtual void NavigationStateChanged(const TabContents* source,
180 unsigned changed_flags) OVERRIDE;
181 virtual std::string GetNavigationHeaders(const GURL& url) OVERRIDE;
182 virtual void AddNewContents(TabContents* source,
183 TabContents* new_contents,
184 WindowOpenDisposition disposition,
185 const gfx::Rect& initial_pos,
186 bool user_gesture) OVERRIDE;
187 virtual void ActivateContents(TabContents* contents) OVERRIDE;
188 virtual void DeactivateContents(TabContents* contents) OVERRIDE;
189 virtual void LoadingStateChanged(TabContents* source) OVERRIDE;
190 virtual void CloseContents(TabContents* source) OVERRIDE;
191 virtual void MoveContents(TabContents* source,
192 const gfx::Rect& pos) OVERRIDE;
193 virtual bool ShouldFocusConstrainedWindow() OVERRIDE;
194 virtual void WillShowConstrainedWindow(TabContents* source) OVERRIDE;
195 virtual void UpdateTargetURL(TabContents* source,
196 const GURL& url) OVERRIDE;
197 virtual bool ShouldSuppressDialogs() OVERRIDE;
198 virtual void BeforeUnloadFired(TabContents* tab,
199 bool proceed,
200 bool* proceed_to_fire_unload) OVERRIDE;
201 virtual void SetFocusToLocationBar(bool select_all) OVERRIDE;
202 virtual bool ShouldFocusPageAfterCrash() OVERRIDE;
203 virtual void LostCapture() OVERRIDE;
204 // If the user drags, we won't get a mouse up (at least on Linux). Commit the
205 // instant result when the drag ends, so that during the drag the page won't
206 // move around.
207 virtual void DragEnded() OVERRIDE;
208 virtual bool CanDownload(int request_id) OVERRIDE;
209 virtual void HandleMouseUp() OVERRIDE;
210 virtual void HandleMouseActivate() OVERRIDE;
211 virtual bool OnGoToEntryOffset(int offset) OVERRIDE;
212 virtual bool ShouldAddNavigationToHistory(
213 const history::HistoryAddPageArgs& add_page_args,
214 NavigationType::Type navigation_type) OVERRIDE;
215 virtual bool ShouldShowHungRendererDialog() OVERRIDE;
216
217 // TabContentsObserver:
218 virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
219
220 private:
221 typedef std::vector<scoped_refptr<history::HistoryAddPageArgs> >
222 AddPageVector;
223
224 // Message from renderer indicating the page has suggestions.
225 void OnSetSuggestions(
226 int32 page_id,
227 const std::vector<std::string>& suggestions,
228 InstantCompleteBehavior behavior);
229
230 // Messages from the renderer when we've determined whether the page supports
231 // instant.
232 void OnInstantSupportDetermined(int32 page_id, bool result);
233
234 void CommitFromMouseReleaseIfNecessary();
235
236 InstantLoader* loader_;
237
238 NotificationRegistrar registrar_;
239
240 // If we are registered for paint notifications on a RenderWidgetHost this
241 // will contain a pointer to it.
242 RenderWidgetHost* registered_render_widget_host_;
243
244 // Used to cache data that needs to be added to history. Normally entries are
245 // added to history as the user types, but for instant we only want to add the
246 // items to history if the user commits instant. So, we cache them here and if
247 // committed then add the items to history.
248 AddPageVector add_page_vector_;
249
250 // Are we we waiting for a NavigationType of NEW_PAGE? If we're waiting for
251 // NEW_PAGE navigation we don't add history items to add_page_vector_.
252 bool waiting_for_new_page_;
253
254 // True if the mouse is down from an activate.
255 bool is_mouse_down_from_activate_;
256
257 // True if the user typed in the search box before the page loaded.
258 bool user_typed_before_load_;
259
260 DISALLOW_COPY_AND_ASSIGN(TabContentsDelegateImpl);
261 };
262
TabContentsDelegateImpl(InstantLoader * loader)263 InstantLoader::TabContentsDelegateImpl::TabContentsDelegateImpl(
264 InstantLoader* loader)
265 : TabContentsObserver(loader->preview_contents()->tab_contents()),
266 loader_(loader),
267 registered_render_widget_host_(NULL),
268 waiting_for_new_page_(true),
269 is_mouse_down_from_activate_(false),
270 user_typed_before_load_(false) {
271 DCHECK(loader->preview_contents());
272 registrar_.Add(this, NotificationType::INTERSTITIAL_ATTACHED,
273 Source<TabContents>(loader->preview_contents()->tab_contents()));
274 registrar_.Add(this, NotificationType::FAIL_PROVISIONAL_LOAD_WITH_ERROR,
275 Source<NavigationController>(&loader->preview_contents()->controller()));
276 }
277
PrepareForNewLoad()278 void InstantLoader::TabContentsDelegateImpl::PrepareForNewLoad() {
279 user_typed_before_load_ = false;
280 waiting_for_new_page_ = true;
281 add_page_vector_.clear();
282 UnregisterForPaintNotifications();
283 }
284
PreviewPainted()285 void InstantLoader::TabContentsDelegateImpl::PreviewPainted() {
286 loader_->PreviewPainted();
287 }
288
SetLastHistoryURLAndPrune(const GURL & url)289 void InstantLoader::TabContentsDelegateImpl::SetLastHistoryURLAndPrune(
290 const GURL& url) {
291 if (add_page_vector_.empty())
292 return;
293
294 history::HistoryAddPageArgs* args = add_page_vector_.front().get();
295 args->url = url;
296 args->redirects.clear();
297 args->redirects.push_back(url);
298
299 // Prune all but the first entry.
300 add_page_vector_.erase(add_page_vector_.begin() + 1,
301 add_page_vector_.end());
302 }
303
CommitHistory(bool supports_instant)304 void InstantLoader::TabContentsDelegateImpl::CommitHistory(
305 bool supports_instant) {
306 TabContents* tab = loader_->preview_contents()->tab_contents();
307 if (tab->profile()->IsOffTheRecord())
308 return;
309
310 for (size_t i = 0; i < add_page_vector_.size(); ++i)
311 tab->UpdateHistoryForNavigation(add_page_vector_[i].get());
312
313 NavigationEntry* active_entry = tab->controller().GetActiveEntry();
314 if (!active_entry) {
315 // It appears to be possible to get here with no active entry. This seems
316 // to be possible with an auth dialog, but I can't narrow down the
317 // circumstances. If you hit this, file a bug with the steps you did and
318 // assign it to me (sky).
319 NOTREACHED();
320 return;
321 }
322 tab->UpdateHistoryPageTitle(*active_entry);
323
324 FaviconService* favicon_service =
325 tab->profile()->GetFaviconService(Profile::EXPLICIT_ACCESS);
326
327 if (favicon_service && active_entry->favicon().is_valid() &&
328 !active_entry->favicon().bitmap().empty()) {
329 std::vector<unsigned char> image_data;
330 gfx::PNGCodec::EncodeBGRASkBitmap(active_entry->favicon().bitmap(), false,
331 &image_data);
332 favicon_service->SetFavicon(active_entry->url(),
333 active_entry->favicon().url(),
334 image_data,
335 history::FAVICON);
336 if (supports_instant && !add_page_vector_.empty()) {
337 // If we're using the instant API, then we've tweaked the url that is
338 // going to be added to history. We need to also set the favicon for the
339 // url we're adding to history (see comment in ReleasePreviewContents
340 // for details).
341 favicon_service->SetFavicon(add_page_vector_.back()->url,
342 active_entry->favicon().url(),
343 image_data,
344 history::FAVICON);
345 }
346 }
347 }
348
RegisterForPaintNotifications(RenderWidgetHost * render_widget_host)349 void InstantLoader::TabContentsDelegateImpl::RegisterForPaintNotifications(
350 RenderWidgetHost* render_widget_host) {
351 DCHECK(registered_render_widget_host_ == NULL);
352 registered_render_widget_host_ = render_widget_host;
353 Source<RenderWidgetHost> source =
354 Source<RenderWidgetHost>(registered_render_widget_host_);
355 registrar_.Add(this, NotificationType::RENDER_WIDGET_HOST_DID_PAINT,
356 source);
357 registrar_.Add(this, NotificationType::RENDER_WIDGET_HOST_DESTROYED,
358 source);
359 }
360
UnregisterForPaintNotifications()361 void InstantLoader::TabContentsDelegateImpl::UnregisterForPaintNotifications() {
362 if (registered_render_widget_host_) {
363 Source<RenderWidgetHost> source =
364 Source<RenderWidgetHost>(registered_render_widget_host_);
365 registrar_.Remove(this, NotificationType::RENDER_WIDGET_HOST_DID_PAINT,
366 source);
367 registrar_.Remove(this, NotificationType::RENDER_WIDGET_HOST_DESTROYED,
368 source);
369 registered_render_widget_host_ = NULL;
370 }
371 }
372
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)373 void InstantLoader::TabContentsDelegateImpl::Observe(
374 NotificationType type,
375 const NotificationSource& source,
376 const NotificationDetails& details) {
377 switch (type.value) {
378 case NotificationType::FAIL_PROVISIONAL_LOAD_WITH_ERROR:
379 if (Details<ProvisionalLoadDetails>(details)->url() == loader_->url_) {
380 // This typically happens with downloads (which are disabled with
381 // instant active). To ensure the download happens when the user presses
382 // enter we set needs_reload_ to true, which triggers a reload.
383 loader_->needs_reload_ = true;
384 }
385 break;
386 case NotificationType::RENDER_WIDGET_HOST_DID_PAINT:
387 UnregisterForPaintNotifications();
388 PreviewPainted();
389 break;
390 case NotificationType::RENDER_WIDGET_HOST_DESTROYED:
391 UnregisterForPaintNotifications();
392 break;
393 case NotificationType::INTERSTITIAL_ATTACHED:
394 PreviewPainted();
395 break;
396 default:
397 NOTREACHED() << "Got a notification we didn't register for.";
398 }
399 }
400
OpenURLFromTab(TabContents * source,const GURL & url,const GURL & referrer,WindowOpenDisposition disposition,PageTransition::Type transition)401 void InstantLoader::TabContentsDelegateImpl::OpenURLFromTab(
402 TabContents* source,
403 const GURL& url, const GURL& referrer,
404 WindowOpenDisposition disposition,
405 PageTransition::Type transition) {
406 }
407
NavigationStateChanged(const TabContents * source,unsigned changed_flags)408 void InstantLoader::TabContentsDelegateImpl::NavigationStateChanged(
409 const TabContents* source,
410 unsigned changed_flags) {
411 if (!loader_->ready() && !registered_render_widget_host_ &&
412 source->controller().entry_count()) {
413 // The load has been committed. Install an observer that waits for the
414 // first paint then makes the preview active. We wait for the load to be
415 // committed before waiting on paint as there is always an initial paint
416 // when a new renderer is created from the resize so that if we showed the
417 // preview after the first paint we would end up with a white rect.
418 RenderWidgetHostView *rwhv = source->GetRenderWidgetHostView();
419 if (rwhv)
420 RegisterForPaintNotifications(rwhv->GetRenderWidgetHost());
421 } else if (source->is_crashed()) {
422 PreviewPainted();
423 }
424 }
425
GetNavigationHeaders(const GURL & url)426 std::string InstantLoader::TabContentsDelegateImpl::GetNavigationHeaders(
427 const GURL& url) {
428 std::string header;
429 net::HttpUtil::AppendHeaderIfMissing(kPreviewHeader, kPreviewHeaderValue,
430 &header);
431 return header;
432 }
433
AddNewContents(TabContents * source,TabContents * new_contents,WindowOpenDisposition disposition,const gfx::Rect & initial_pos,bool user_gesture)434 void InstantLoader::TabContentsDelegateImpl::AddNewContents(
435 TabContents* source,
436 TabContents* new_contents,
437 WindowOpenDisposition disposition,
438 const gfx::Rect& initial_pos,
439 bool user_gesture) {
440 }
441
ActivateContents(TabContents * contents)442 void InstantLoader::TabContentsDelegateImpl::ActivateContents(
443 TabContents* contents) {
444 }
445
DeactivateContents(TabContents * contents)446 void InstantLoader::TabContentsDelegateImpl::DeactivateContents(
447 TabContents* contents) {
448 }
449
LoadingStateChanged(TabContents * source)450 void InstantLoader::TabContentsDelegateImpl::LoadingStateChanged(
451 TabContents* source) {
452 }
453
CloseContents(TabContents * source)454 void InstantLoader::TabContentsDelegateImpl::CloseContents(
455 TabContents* source) {
456 }
457
MoveContents(TabContents * source,const gfx::Rect & pos)458 void InstantLoader::TabContentsDelegateImpl::MoveContents(
459 TabContents* source,
460 const gfx::Rect& pos) {
461 }
462
ShouldFocusConstrainedWindow()463 bool InstantLoader::TabContentsDelegateImpl::ShouldFocusConstrainedWindow() {
464 // Return false so that constrained windows are not initially focused. If
465 // we did otherwise the preview would prematurely get committed when focus
466 // goes to the constrained window.
467 return false;
468 }
469
WillShowConstrainedWindow(TabContents * source)470 void InstantLoader::TabContentsDelegateImpl::WillShowConstrainedWindow(
471 TabContents* source) {
472 if (!loader_->ready()) {
473 // A constrained window shown for an auth may not paint. Show the preview
474 // contents.
475 UnregisterForPaintNotifications();
476 loader_->ShowPreview();
477 }
478 }
479
UpdateTargetURL(TabContents * source,const GURL & url)480 void InstantLoader::TabContentsDelegateImpl::UpdateTargetURL(
481 TabContents* source, const GURL& url) {
482 }
483
ShouldSuppressDialogs()484 bool InstantLoader::TabContentsDelegateImpl::ShouldSuppressDialogs() {
485 // Any message shown during instant cancels instant, so we suppress them.
486 return true;
487 }
488
BeforeUnloadFired(TabContents * tab,bool proceed,bool * proceed_to_fire_unload)489 void InstantLoader::TabContentsDelegateImpl::BeforeUnloadFired(
490 TabContents* tab,
491 bool proceed,
492 bool* proceed_to_fire_unload) {
493 }
494
SetFocusToLocationBar(bool select_all)495 void InstantLoader::TabContentsDelegateImpl::SetFocusToLocationBar(
496 bool select_all) {
497 }
498
ShouldFocusPageAfterCrash()499 bool InstantLoader::TabContentsDelegateImpl::ShouldFocusPageAfterCrash() {
500 return false;
501 }
502
LostCapture()503 void InstantLoader::TabContentsDelegateImpl::LostCapture() {
504 CommitFromMouseReleaseIfNecessary();
505 }
506
DragEnded()507 void InstantLoader::TabContentsDelegateImpl::DragEnded() {
508 CommitFromMouseReleaseIfNecessary();
509 }
510
CanDownload(int request_id)511 bool InstantLoader::TabContentsDelegateImpl::CanDownload(int request_id) {
512 // Downloads are disabled.
513 return false;
514 }
515
HandleMouseUp()516 void InstantLoader::TabContentsDelegateImpl::HandleMouseUp() {
517 CommitFromMouseReleaseIfNecessary();
518 }
519
HandleMouseActivate()520 void InstantLoader::TabContentsDelegateImpl::HandleMouseActivate() {
521 is_mouse_down_from_activate_ = true;
522 }
523
OnGoToEntryOffset(int offset)524 bool InstantLoader::TabContentsDelegateImpl::OnGoToEntryOffset(int offset) {
525 return false;
526 }
527
ShouldAddNavigationToHistory(const history::HistoryAddPageArgs & add_page_args,NavigationType::Type navigation_type)528 bool InstantLoader::TabContentsDelegateImpl::ShouldAddNavigationToHistory(
529 const history::HistoryAddPageArgs& add_page_args,
530 NavigationType::Type navigation_type) {
531 if (waiting_for_new_page_ && navigation_type == NavigationType::NEW_PAGE)
532 waiting_for_new_page_ = false;
533
534 if (!waiting_for_new_page_) {
535 add_page_vector_.push_back(
536 scoped_refptr<history::HistoryAddPageArgs>(add_page_args.Clone()));
537 }
538 return false;
539 }
540
ShouldShowHungRendererDialog()541 bool InstantLoader::TabContentsDelegateImpl::ShouldShowHungRendererDialog() {
542 // If we allow the hung renderer dialog to be shown it'll gain focus,
543 // stealing focus from the omnibox causing instant to be cancelled. Return
544 // false so that doesn't happen.
545 return false;
546 }
547
OnMessageReceived(const IPC::Message & message)548 bool InstantLoader::TabContentsDelegateImpl::OnMessageReceived(
549 const IPC::Message& message) {
550 bool handled = true;
551 IPC_BEGIN_MESSAGE_MAP(TabContentsDelegateImpl, message)
552 IPC_MESSAGE_HANDLER(ViewHostMsg_SetSuggestions, OnSetSuggestions)
553 IPC_MESSAGE_HANDLER(ViewHostMsg_InstantSupportDetermined,
554 OnInstantSupportDetermined)
555 IPC_MESSAGE_UNHANDLED(handled = false)
556 IPC_END_MESSAGE_MAP()
557 return handled;
558 }
559
OnSetSuggestions(int32 page_id,const std::vector<std::string> & suggestions,InstantCompleteBehavior behavior)560 void InstantLoader::TabContentsDelegateImpl::OnSetSuggestions(
561 int32 page_id,
562 const std::vector<std::string>& suggestions,
563 InstantCompleteBehavior behavior) {
564 TabContentsWrapper* source = loader_->preview_contents();
565 if (!source->controller().GetActiveEntry() ||
566 page_id != source->controller().GetActiveEntry()->page_id())
567 return;
568
569 if (suggestions.empty())
570 loader_->SetCompleteSuggestedText(string16(), behavior);
571 else
572 loader_->SetCompleteSuggestedText(UTF8ToUTF16(suggestions[0]), behavior);
573 }
574
OnInstantSupportDetermined(int32 page_id,bool result)575 void InstantLoader::TabContentsDelegateImpl::OnInstantSupportDetermined(
576 int32 page_id,
577 bool result) {
578 TabContents* source = loader_->preview_contents()->tab_contents();
579 if (!source->controller().GetActiveEntry() ||
580 page_id != source->controller().GetActiveEntry()->page_id())
581 return;
582
583 Details<const bool> details(&result);
584 NotificationService::current()->Notify(
585 NotificationType::INSTANT_SUPPORT_DETERMINED,
586 NotificationService::AllSources(),
587 details);
588
589 if (result)
590 loader_->PageFinishedLoading();
591 else
592 loader_->PageDoesntSupportInstant(user_typed_before_load_);
593 }
594
595 void InstantLoader::TabContentsDelegateImpl
CommitFromMouseReleaseIfNecessary()596 ::CommitFromMouseReleaseIfNecessary() {
597 bool was_down = is_mouse_down_from_activate_;
598 is_mouse_down_from_activate_ = false;
599 if (was_down && loader_->ShouldCommitInstantOnMouseUp())
600 loader_->CommitInstantLoader();
601 }
602
603 // InstantLoader ---------------------------------------------------------------
604
InstantLoader(InstantLoaderDelegate * delegate,TemplateURLID id)605 InstantLoader::InstantLoader(InstantLoaderDelegate* delegate, TemplateURLID id)
606 : delegate_(delegate),
607 template_url_id_(id),
608 ready_(false),
609 http_status_ok_(true),
610 last_transition_type_(PageTransition::LINK),
611 verbatim_(false),
612 needs_reload_(false) {
613 }
614
~InstantLoader()615 InstantLoader::~InstantLoader() {
616 registrar_.RemoveAll();
617
618 // Delete the TabContents before the delegate as the TabContents holds a
619 // reference to the delegate.
620 preview_contents_.reset();
621 }
622
Update(TabContentsWrapper * tab_contents,const TemplateURL * template_url,const GURL & url,PageTransition::Type transition_type,const string16 & user_text,bool verbatim,string16 * suggested_text)623 bool InstantLoader::Update(TabContentsWrapper* tab_contents,
624 const TemplateURL* template_url,
625 const GURL& url,
626 PageTransition::Type transition_type,
627 const string16& user_text,
628 bool verbatim,
629 string16* suggested_text) {
630 DCHECK(!url.is_empty() && url.is_valid());
631
632 // Strip leading ?.
633 string16 new_user_text =
634 !user_text.empty() && (UTF16ToWide(user_text)[0] == L'?') ?
635 user_text.substr(1) : user_text;
636
637 // We should preserve the transition type regardless of whether we're already
638 // showing the url.
639 last_transition_type_ = transition_type;
640
641 // If state hasn't changed, reuse the last suggestion. There are two cases:
642 // 1. If no template url (not using instant API), then we only care if the url
643 // changes.
644 // 2. Template url (using instant API) then the important part is if the
645 // user_text changes.
646 // We have to be careful in checking user_text as in some situations
647 // InstantController passes in an empty string (when it knows the user_text
648 // won't matter).
649 if ((!template_url_id_ && url_ == url) ||
650 (template_url_id_ &&
651 (new_user_text.empty() || user_text_ == new_user_text))) {
652 suggested_text->assign(last_suggestion_);
653 // Track the url even if we're not going to update. This is important as
654 // when we get the suggest text we set user_text_ to the new suggest text,
655 // but yet the url is much different.
656 url_ = url;
657 return false;
658 }
659
660 url_ = url;
661 user_text_ = new_user_text;
662 verbatim_ = verbatim;
663 last_suggestion_.clear();
664 needs_reload_ = false;
665
666 bool created_preview_contents = preview_contents_.get() == NULL;
667 if (created_preview_contents)
668 CreatePreviewContents(tab_contents);
669
670 if (template_url) {
671 DCHECK(template_url_id_ == template_url->id());
672 if (!created_preview_contents) {
673 if (is_waiting_for_load()) {
674 // The page hasn't loaded yet. We'll send the script down when it does.
675 frame_load_observer_->set_text(user_text_);
676 frame_load_observer_->set_verbatim(verbatim);
677 preview_tab_contents_delegate_->set_user_typed_before_load();
678 return true;
679 }
680 // TODO: support real cursor position.
681 int text_length = static_cast<int>(user_text_.size());
682 preview_contents_->render_view_host()->SearchBoxChange(
683 user_text_, verbatim, text_length, text_length);
684
685 string16 complete_suggested_text_lower = l10n_util::ToLower(
686 complete_suggested_text_);
687 string16 user_text_lower = l10n_util::ToLower(user_text_);
688 if (!verbatim &&
689 complete_suggested_text_lower.size() > user_text_lower.size() &&
690 !complete_suggested_text_lower.compare(0, user_text_lower.size(),
691 user_text_lower)) {
692 *suggested_text = last_suggestion_ =
693 complete_suggested_text_.substr(user_text_.size());
694 }
695 } else {
696 preview_tab_contents_delegate_->PrepareForNewLoad();
697
698 // Load the instant URL. We don't reflect the url we load in url() as
699 // callers expect that we're loading the URL they tell us to.
700 //
701 // This uses an empty string for the replacement text as the url doesn't
702 // really have the search params, but we need to use the replace
703 // functionality so that embeded tags (like {google:baseURL}) are escaped
704 // correctly.
705 // TODO(sky): having to use a replaceable url is a bit of a hack here.
706 GURL instant_url(
707 template_url->instant_url()->ReplaceSearchTerms(
708 *template_url, string16(), -1, string16()));
709 CommandLine* cl = CommandLine::ForCurrentProcess();
710 if (cl->HasSwitch(switches::kInstantURL))
711 instant_url = GURL(cl->GetSwitchValueASCII(switches::kInstantURL));
712 preview_contents_->controller().LoadURL(
713 instant_url, GURL(), transition_type);
714 preview_contents_->render_view_host()->SearchBoxChange(
715 user_text_, verbatim, 0, 0);
716 frame_load_observer_.reset(
717 new FrameLoadObserver(this,
718 preview_contents()->tab_contents(),
719 user_text_,
720 verbatim));
721 }
722 } else {
723 DCHECK(template_url_id_ == 0);
724 preview_tab_contents_delegate_->PrepareForNewLoad();
725 frame_load_observer_.reset(NULL);
726 preview_contents_->controller().LoadURL(url_, GURL(), transition_type);
727 }
728 return true;
729 }
730
SetOmniboxBounds(const gfx::Rect & bounds)731 void InstantLoader::SetOmniboxBounds(const gfx::Rect& bounds) {
732 if (omnibox_bounds_ == bounds)
733 return;
734
735 // Don't update the page while the mouse is down. http://crbug.com/71952
736 if (IsMouseDownFromActivate())
737 return;
738
739 omnibox_bounds_ = bounds;
740 if (preview_contents_.get() && is_showing_instant() &&
741 !is_waiting_for_load()) {
742 // Updating the bounds is rather expensive, and because of the async nature
743 // of the omnibox the bounds can dance around a bit. Delay the update in
744 // hopes of things settling down. To avoid hiding results we grow
745 // immediately, but delay shrinking.
746 update_bounds_timer_.Stop();
747 if (omnibox_bounds_.height() > last_omnibox_bounds_.height()) {
748 SendBoundsToPage(false);
749 } else {
750 update_bounds_timer_.Start(
751 base::TimeDelta::FromMilliseconds(kUpdateBoundsDelayMS),
752 this, &InstantLoader::ProcessBoundsChange);
753 }
754 }
755 }
756
IsMouseDownFromActivate()757 bool InstantLoader::IsMouseDownFromActivate() {
758 return preview_tab_contents_delegate_.get() &&
759 preview_tab_contents_delegate_->is_mouse_down_from_activate();
760 }
761
ReleasePreviewContents(InstantCommitType type)762 TabContentsWrapper* InstantLoader::ReleasePreviewContents(
763 InstantCommitType type) {
764 if (!preview_contents_.get())
765 return NULL;
766
767 // FrameLoadObserver is only used for instant results, and instant results are
768 // only committed if active (when the FrameLoadObserver isn't installed).
769 DCHECK(type == INSTANT_COMMIT_DESTROY || !frame_load_observer_.get());
770
771 if (type != INSTANT_COMMIT_DESTROY && is_showing_instant()) {
772 if (type == INSTANT_COMMIT_FOCUS_LOST)
773 preview_contents_->render_view_host()->SearchBoxCancel();
774 else
775 preview_contents_->render_view_host()->SearchBoxSubmit(
776 user_text_, type == INSTANT_COMMIT_PRESSED_ENTER);
777 }
778 omnibox_bounds_ = gfx::Rect();
779 last_omnibox_bounds_ = gfx::Rect();
780 GURL url;
781 url.Swap(&url_);
782 user_text_.clear();
783 complete_suggested_text_.clear();
784 if (preview_contents_.get()) {
785 if (type != INSTANT_COMMIT_DESTROY) {
786 if (template_url_id_) {
787 // The URL used during instant is mostly gibberish, and not something
788 // we'll parse and match as a past search. Set it to something we can
789 // parse.
790 preview_tab_contents_delegate_->SetLastHistoryURLAndPrune(url);
791 }
792 preview_tab_contents_delegate_->CommitHistory(template_url_id_ != 0);
793 }
794 if (preview_contents_->tab_contents()->GetRenderWidgetHostView()) {
795 #if defined(OS_MACOSX)
796 preview_contents_->tab_contents()->GetRenderWidgetHostView()->
797 SetTakesFocusOnlyOnMouseDown(false);
798 registrar_.Remove(
799 this,
800 NotificationType::RENDER_VIEW_HOST_CHANGED,
801 Source<NavigationController>(&preview_contents_->controller()));
802 #endif
803 }
804 preview_contents_->tab_contents()->set_delegate(NULL);
805 ready_ = false;
806 }
807 update_bounds_timer_.Stop();
808 return preview_contents_.release();
809 }
810
ShouldCommitInstantOnMouseUp()811 bool InstantLoader::ShouldCommitInstantOnMouseUp() {
812 return delegate_->ShouldCommitInstantOnMouseUp();
813 }
814
CommitInstantLoader()815 void InstantLoader::CommitInstantLoader() {
816 delegate_->CommitInstantLoader(this);
817 }
818
SetCompleteSuggestedText(const string16 & complete_suggested_text,InstantCompleteBehavior behavior)819 void InstantLoader::SetCompleteSuggestedText(
820 const string16& complete_suggested_text,
821 InstantCompleteBehavior behavior) {
822 if (!is_showing_instant()) {
823 // We're not trying to use the instant API with this page. Ignore it.
824 return;
825 }
826
827 ShowPreview();
828
829 if (complete_suggested_text == complete_suggested_text_)
830 return;
831
832 if (verbatim_) {
833 // Don't show suggest results for verbatim queries.
834 return;
835 }
836
837 string16 user_text_lower = l10n_util::ToLower(user_text_);
838 string16 complete_suggested_text_lower = l10n_util::ToLower(
839 complete_suggested_text);
840 last_suggestion_.clear();
841 if (user_text_lower.compare(0, user_text_lower.size(),
842 complete_suggested_text_lower,
843 0, user_text_lower.size())) {
844 // The user text no longer contains the suggested text, ignore it.
845 complete_suggested_text_.clear();
846 delegate_->SetSuggestedTextFor(this, string16(), behavior);
847 return;
848 }
849
850 complete_suggested_text_ = complete_suggested_text;
851 if (behavior == INSTANT_COMPLETE_NOW) {
852 // We are effectively showing complete_suggested_text_ now. Update
853 // user_text_ so we don't notify the page again if Update happens to be
854 // invoked (which is more than likely if this callback completes before the
855 // omnibox is done).
856 string16 suggestion = complete_suggested_text_.substr(user_text_.size());
857 user_text_ = complete_suggested_text_;
858 delegate_->SetSuggestedTextFor(this, suggestion, behavior);
859 } else {
860 DCHECK((behavior == INSTANT_COMPLETE_DELAYED) ||
861 (behavior == INSTANT_COMPLETE_NEVER));
862 last_suggestion_ = complete_suggested_text_.substr(user_text_.size());
863 delegate_->SetSuggestedTextFor(this, last_suggestion_, behavior);
864 }
865 }
866
PreviewPainted()867 void InstantLoader::PreviewPainted() {
868 // If instant is supported then we wait for the first suggest result before
869 // showing the page.
870 if (!template_url_id_)
871 ShowPreview();
872 }
873
SetHTTPStatusOK(bool is_ok)874 void InstantLoader::SetHTTPStatusOK(bool is_ok) {
875 if (is_ok == http_status_ok_)
876 return;
877
878 http_status_ok_ = is_ok;
879 if (ready_)
880 delegate_->InstantStatusChanged(this);
881 }
882
ShowPreview()883 void InstantLoader::ShowPreview() {
884 if (!ready_) {
885 ready_ = true;
886 delegate_->InstantStatusChanged(this);
887 }
888 }
889
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)890 void InstantLoader::Observe(NotificationType type,
891 const NotificationSource& source,
892 const NotificationDetails& details) {
893 #if defined(OS_MACOSX)
894 if (type.value == NotificationType::RENDER_VIEW_HOST_CHANGED) {
895 if (preview_contents_->tab_contents()->GetRenderWidgetHostView()) {
896 preview_contents_->tab_contents()->GetRenderWidgetHostView()->
897 SetTakesFocusOnlyOnMouseDown(true);
898 }
899 return;
900 }
901 #endif
902 if (type.value == NotificationType::NAV_ENTRY_COMMITTED) {
903 NavigationController::LoadCommittedDetails* load_details =
904 Details<NavigationController::LoadCommittedDetails>(details).ptr();
905 if (load_details->is_main_frame) {
906 if (load_details->http_status_code == kHostBlacklistStatusCode) {
907 delegate_->AddToBlacklist(this, load_details->entry->url());
908 } else {
909 SetHTTPStatusOK(load_details->http_status_code == 200);
910 }
911 }
912 return;
913 }
914
915 NOTREACHED() << "Got a notification we didn't register for.";
916 }
917
PageFinishedLoading()918 void InstantLoader::PageFinishedLoading() {
919 frame_load_observer_.reset();
920
921 // Send the bounds of the omnibox down now.
922 SendBoundsToPage(false);
923
924 // Wait for the user input before showing, this way the page should be up to
925 // date by the time we show it.
926 }
927
928 // TODO(tonyg): This method only fires when the omnibox bounds change. It also
929 // needs to fire when the preview bounds change (e.g. open/close info bar).
GetOmniboxBoundsInTermsOfPreview()930 gfx::Rect InstantLoader::GetOmniboxBoundsInTermsOfPreview() {
931 gfx::Rect preview_bounds(delegate_->GetInstantBounds());
932 gfx::Rect intersection(omnibox_bounds_.Intersect(preview_bounds));
933
934 // Translate into window's coordinates.
935 if (!intersection.IsEmpty()) {
936 intersection.Offset(-preview_bounds.origin().x(),
937 -preview_bounds.origin().y());
938 }
939
940 // In the current Chrome UI, these must always be true so they sanity check
941 // the above operations. In a future UI, these may be removed or adjusted.
942 DCHECK_EQ(0, intersection.y());
943 DCHECK_LE(0, intersection.x());
944 DCHECK_LE(0, intersection.width());
945 DCHECK_LE(0, intersection.height());
946
947 return intersection;
948 }
949
PageDoesntSupportInstant(bool needs_reload)950 void InstantLoader::PageDoesntSupportInstant(bool needs_reload) {
951 frame_load_observer_.reset(NULL);
952
953 delegate_->InstantLoaderDoesntSupportInstant(this);
954 }
955
ProcessBoundsChange()956 void InstantLoader::ProcessBoundsChange() {
957 SendBoundsToPage(false);
958 }
959
SendBoundsToPage(bool force_if_waiting)960 void InstantLoader::SendBoundsToPage(bool force_if_waiting) {
961 if (last_omnibox_bounds_ == omnibox_bounds_)
962 return;
963
964 if (preview_contents_.get() && is_showing_instant() &&
965 (force_if_waiting || !is_waiting_for_load())) {
966 last_omnibox_bounds_ = omnibox_bounds_;
967 preview_contents_->render_view_host()->SearchBoxResize(
968 GetOmniboxBoundsInTermsOfPreview());
969 }
970 }
971
CreatePreviewContents(TabContentsWrapper * tab_contents)972 void InstantLoader::CreatePreviewContents(TabContentsWrapper* tab_contents) {
973 TabContents* new_contents =
974 new TabContents(
975 tab_contents->profile(), NULL, MSG_ROUTING_NONE, NULL, NULL);
976 preview_contents_.reset(new TabContentsWrapper(new_contents));
977 new_contents->SetAllContentsBlocked(true);
978 // Propagate the max page id. That way if we end up merging the two
979 // NavigationControllers (which happens if we commit) none of the page ids
980 // will overlap.
981 int32 max_page_id = tab_contents->tab_contents()->GetMaxPageID();
982 if (max_page_id != -1)
983 preview_contents_->controller().set_max_restored_page_id(max_page_id + 1);
984
985 preview_tab_contents_delegate_.reset(new TabContentsDelegateImpl(this));
986 new_contents->set_delegate(preview_tab_contents_delegate_.get());
987
988 gfx::Rect tab_bounds;
989 tab_contents->view()->GetContainerBounds(&tab_bounds);
990 preview_contents_->view()->SizeContents(tab_bounds.size());
991
992 #if defined(OS_MACOSX)
993 // If |preview_contents_| does not currently have a RWHV, we will call
994 // SetTakesFocusOnlyOnMouseDown() as a result of the
995 // RENDER_VIEW_HOST_CHANGED notification.
996 if (preview_contents_->tab_contents()->GetRenderWidgetHostView()) {
997 preview_contents_->tab_contents()->GetRenderWidgetHostView()->
998 SetTakesFocusOnlyOnMouseDown(true);
999 }
1000 registrar_.Add(
1001 this,
1002 NotificationType::RENDER_VIEW_HOST_CHANGED,
1003 Source<NavigationController>(&preview_contents_->controller()));
1004 #endif
1005
1006 registrar_.Add(
1007 this,
1008 NotificationType::NAV_ENTRY_COMMITTED,
1009 Source<NavigationController>(&preview_contents_->controller()));
1010
1011 preview_contents_->tab_contents()->ShowContents();
1012 }
1013