• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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