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
46namespace {
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.
51const int kUpdateBoundsDelayMS = 1000;
52
53// If this status code is seen instant is disabled for the specified host.
54const int kHostBlacklistStatusCode = 403;
55
56// Header and value set for all loads.
57const char kPreviewHeader[] = "X-Purpose:";
58const char kPreviewHeaderValue[] = "preview";
59
60}  // namespace
61
62// FrameLoadObserver is responsible for determining if the page supports
63// instant after it has loaded.
64class InstantLoader::FrameLoadObserver : public NotificationObserver {
65 public:
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.
80  void set_text(const string16& text) { text_ = text; }
81
82  // Sets whether verbatim results are obtained rather than predictive.
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
111void 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
139class 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
152  bool is_mouse_down_from_activate() const {
153    return is_mouse_down_from_activate_;
154  }
155
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
263InstantLoader::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
278void InstantLoader::TabContentsDelegateImpl::PrepareForNewLoad() {
279  user_typed_before_load_ = false;
280  waiting_for_new_page_ = true;
281  add_page_vector_.clear();
282  UnregisterForPaintNotifications();
283}
284
285void InstantLoader::TabContentsDelegateImpl::PreviewPainted() {
286  loader_->PreviewPainted();
287}
288
289void 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
304void 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
349void 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
361void 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
373void 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
401void InstantLoader::TabContentsDelegateImpl::OpenURLFromTab(
402    TabContents* source,
403    const GURL& url, const GURL& referrer,
404    WindowOpenDisposition disposition,
405    PageTransition::Type transition) {
406}
407
408void 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
426std::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
434void 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
442void InstantLoader::TabContentsDelegateImpl::ActivateContents(
443    TabContents* contents) {
444}
445
446void InstantLoader::TabContentsDelegateImpl::DeactivateContents(
447    TabContents* contents) {
448}
449
450void InstantLoader::TabContentsDelegateImpl::LoadingStateChanged(
451    TabContents* source) {
452}
453
454void InstantLoader::TabContentsDelegateImpl::CloseContents(
455    TabContents* source) {
456}
457
458void InstantLoader::TabContentsDelegateImpl::MoveContents(
459    TabContents* source,
460    const gfx::Rect& pos) {
461}
462
463bool 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
470void 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
480void InstantLoader::TabContentsDelegateImpl::UpdateTargetURL(
481    TabContents* source, const GURL& url) {
482}
483
484bool InstantLoader::TabContentsDelegateImpl::ShouldSuppressDialogs() {
485  // Any message shown during instant cancels instant, so we suppress them.
486  return true;
487}
488
489void InstantLoader::TabContentsDelegateImpl::BeforeUnloadFired(
490    TabContents* tab,
491    bool proceed,
492    bool* proceed_to_fire_unload) {
493}
494
495void InstantLoader::TabContentsDelegateImpl::SetFocusToLocationBar(
496    bool select_all) {
497}
498
499bool InstantLoader::TabContentsDelegateImpl::ShouldFocusPageAfterCrash() {
500  return false;
501}
502
503void InstantLoader::TabContentsDelegateImpl::LostCapture() {
504  CommitFromMouseReleaseIfNecessary();
505}
506
507void InstantLoader::TabContentsDelegateImpl::DragEnded() {
508  CommitFromMouseReleaseIfNecessary();
509}
510
511bool InstantLoader::TabContentsDelegateImpl::CanDownload(int request_id) {
512  // Downloads are disabled.
513  return false;
514}
515
516void InstantLoader::TabContentsDelegateImpl::HandleMouseUp() {
517  CommitFromMouseReleaseIfNecessary();
518}
519
520void InstantLoader::TabContentsDelegateImpl::HandleMouseActivate() {
521  is_mouse_down_from_activate_ = true;
522}
523
524bool InstantLoader::TabContentsDelegateImpl::OnGoToEntryOffset(int offset) {
525  return false;
526}
527
528bool 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
541bool 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
548bool 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
560void 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
575void 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
595void InstantLoader::TabContentsDelegateImpl
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
605InstantLoader::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
615InstantLoader::~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
623bool 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
731void 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
757bool InstantLoader::IsMouseDownFromActivate() {
758  return preview_tab_contents_delegate_.get() &&
759      preview_tab_contents_delegate_->is_mouse_down_from_activate();
760}
761
762TabContentsWrapper* 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
811bool InstantLoader::ShouldCommitInstantOnMouseUp() {
812  return delegate_->ShouldCommitInstantOnMouseUp();
813}
814
815void InstantLoader::CommitInstantLoader() {
816  delegate_->CommitInstantLoader(this);
817}
818
819void 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
867void 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
874void 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
883void InstantLoader::ShowPreview() {
884  if (!ready_) {
885    ready_ = true;
886    delegate_->InstantStatusChanged(this);
887  }
888}
889
890void 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
918void 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).
930gfx::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
950void InstantLoader::PageDoesntSupportInstant(bool needs_reload) {
951  frame_load_observer_.reset(NULL);
952
953  delegate_->InstantLoaderDoesntSupportInstant(this);
954}
955
956void InstantLoader::ProcessBoundsChange() {
957  SendBoundsToPage(false);
958}
959
960void 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
972void 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