Home | History | Annotate | Download | only in instant
      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:
     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 
    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 
    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 
    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 
    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 
    285 void InstantLoader::TabContentsDelegateImpl::PreviewPainted() {
    286   loader_->PreviewPainted();
    287 }
    288 
    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 
    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 
    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 
    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 
    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 
    401 void InstantLoader::TabContentsDelegateImpl::OpenURLFromTab(
    402     TabContents* source,
    403     const GURL& url, const GURL& referrer,
    404     WindowOpenDisposition disposition,
    405     PageTransition::Type transition) {
    406 }
    407 
    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 
    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 
    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 
    442 void InstantLoader::TabContentsDelegateImpl::ActivateContents(
    443     TabContents* contents) {
    444 }
    445 
    446 void InstantLoader::TabContentsDelegateImpl::DeactivateContents(
    447     TabContents* contents) {
    448 }
    449 
    450 void InstantLoader::TabContentsDelegateImpl::LoadingStateChanged(
    451     TabContents* source) {
    452 }
    453 
    454 void InstantLoader::TabContentsDelegateImpl::CloseContents(
    455     TabContents* source) {
    456 }
    457 
    458 void InstantLoader::TabContentsDelegateImpl::MoveContents(
    459     TabContents* source,
    460     const gfx::Rect& pos) {
    461 }
    462 
    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 
    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 
    480 void InstantLoader::TabContentsDelegateImpl::UpdateTargetURL(
    481     TabContents* source, const GURL& url) {
    482 }
    483 
    484 bool InstantLoader::TabContentsDelegateImpl::ShouldSuppressDialogs() {
    485   // Any message shown during instant cancels instant, so we suppress them.
    486   return true;
    487 }
    488 
    489 void InstantLoader::TabContentsDelegateImpl::BeforeUnloadFired(
    490     TabContents* tab,
    491     bool proceed,
    492     bool* proceed_to_fire_unload) {
    493 }
    494 
    495 void InstantLoader::TabContentsDelegateImpl::SetFocusToLocationBar(
    496     bool select_all) {
    497 }
    498 
    499 bool InstantLoader::TabContentsDelegateImpl::ShouldFocusPageAfterCrash() {
    500   return false;
    501 }
    502 
    503 void InstantLoader::TabContentsDelegateImpl::LostCapture() {
    504   CommitFromMouseReleaseIfNecessary();
    505 }
    506 
    507 void InstantLoader::TabContentsDelegateImpl::DragEnded() {
    508   CommitFromMouseReleaseIfNecessary();
    509 }
    510 
    511 bool InstantLoader::TabContentsDelegateImpl::CanDownload(int request_id) {
    512   // Downloads are disabled.
    513   return false;
    514 }
    515 
    516 void InstantLoader::TabContentsDelegateImpl::HandleMouseUp() {
    517   CommitFromMouseReleaseIfNecessary();
    518 }
    519 
    520 void InstantLoader::TabContentsDelegateImpl::HandleMouseActivate() {
    521   is_mouse_down_from_activate_ = true;
    522 }
    523 
    524 bool InstantLoader::TabContentsDelegateImpl::OnGoToEntryOffset(int offset) {
    525   return false;
    526 }
    527 
    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 
    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 
    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 
    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 
    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
    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 
    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 
    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 
    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 
    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 
    757 bool InstantLoader::IsMouseDownFromActivate() {
    758   return preview_tab_contents_delegate_.get() &&
    759       preview_tab_contents_delegate_->is_mouse_down_from_activate();
    760 }
    761 
    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 
    811 bool InstantLoader::ShouldCommitInstantOnMouseUp() {
    812   return delegate_->ShouldCommitInstantOnMouseUp();
    813 }
    814 
    815 void InstantLoader::CommitInstantLoader() {
    816   delegate_->CommitInstantLoader(this);
    817 }
    818 
    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 
    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 
    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 
    883 void InstantLoader::ShowPreview() {
    884   if (!ready_) {
    885     ready_ = true;
    886     delegate_->InstantStatusChanged(this);
    887   }
    888 }
    889 
    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 
    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).
    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 
    950 void InstantLoader::PageDoesntSupportInstant(bool needs_reload) {
    951   frame_load_observer_.reset(NULL);
    952 
    953   delegate_->InstantLoaderDoesntSupportInstant(this);
    954 }
    955 
    956 void InstantLoader::ProcessBoundsChange() {
    957   SendBoundsToPage(false);
    958 }
    959 
    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 
    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