普通文本  |  1013行  |  36.05 KB

// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/instant/instant_loader.h"

#include <algorithm>
#include <string>
#include <utility>
#include <vector>

#include "base/command_line.h"
#include "base/string_number_conversions.h"
#include "base/timer.h"
#include "base/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/favicon_service.h"
#include "chrome/browser/history/history_marshaling.h"
#include "chrome/browser/instant/instant_loader_delegate.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search_engines/template_url.h"
#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/render_messages.h"
#include "content/browser/renderer_host/render_view_host.h"
#include "content/browser/renderer_host/render_widget_host.h"
#include "content/browser/renderer_host/render_widget_host_view.h"
#include "content/browser/tab_contents/navigation_controller.h"
#include "content/browser/tab_contents/navigation_entry.h"
#include "content/browser/tab_contents/provisional_load_details.h"
#include "content/browser/tab_contents/tab_contents.h"
#include "content/browser/tab_contents/tab_contents_delegate.h"
#include "content/browser/tab_contents/tab_contents_view.h"
#include "content/common/notification_details.h"
#include "content/common/notification_observer.h"
#include "content/common/notification_registrar.h"
#include "content/common/notification_service.h"
#include "content/common/notification_source.h"
#include "content/common/notification_type.h"
#include "content/common/page_transition_types.h"
#include "content/common/renderer_preferences.h"
#include "net/http/http_util.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/codec/png_codec.h"

namespace {

// Number of ms to delay before updating the omnibox bounds. This is only used
// when the bounds of the omnibox shrinks. If the bounds grows, we update
// immediately.
const int kUpdateBoundsDelayMS = 1000;

// If this status code is seen instant is disabled for the specified host.
const int kHostBlacklistStatusCode = 403;

// Header and value set for all loads.
const char kPreviewHeader[] = "X-Purpose:";
const char kPreviewHeaderValue[] = "preview";

}  // namespace

// FrameLoadObserver is responsible for determining if the page supports
// instant after it has loaded.
class InstantLoader::FrameLoadObserver : public NotificationObserver {
 public:
  FrameLoadObserver(InstantLoader* loader,
                    TabContents* tab_contents,
                    const string16& text,
                    bool verbatim)
      : loader_(loader),
        tab_contents_(tab_contents),
        text_(text),
        verbatim_(verbatim),
        unique_id_(tab_contents_->controller().pending_entry()->unique_id()) {
    registrar_.Add(this, NotificationType::LOAD_COMPLETED_MAIN_FRAME,
                   Source<TabContents>(tab_contents_));
  }

  // Sets the text to send to the page.
  void set_text(const string16& text) { text_ = text; }

  // Sets whether verbatim results are obtained rather than predictive.
  void set_verbatim(bool verbatim) { verbatim_ = verbatim; }

  // NotificationObserver:
  virtual void Observe(NotificationType type,
                       const NotificationSource& source,
                       const NotificationDetails& details) OVERRIDE;

 private:
  InstantLoader* loader_;

  // The TabContents we're listening for changes on.
  TabContents* tab_contents_;

  // Text to send down to the page.
  string16 text_;

  // Whether verbatim results are obtained.
  bool verbatim_;

  // unique_id of the NavigationEntry we're waiting on.
  const int unique_id_;

  // Registers and unregisters us for notifications.
  NotificationRegistrar registrar_;

  DISALLOW_COPY_AND_ASSIGN(FrameLoadObserver);
};

void InstantLoader::FrameLoadObserver::Observe(
    NotificationType type,
    const NotificationSource& source,
    const NotificationDetails& details) {
  switch (type.value) {
    case NotificationType::LOAD_COMPLETED_MAIN_FRAME: {
      int page_id = *(Details<int>(details).ptr());
      NavigationEntry* active_entry =
          tab_contents_->controller().GetActiveEntry();
      if (!active_entry || active_entry->page_id() != page_id ||
          active_entry->unique_id() != unique_id_) {
        return;
      }
      loader_->SendBoundsToPage(true);
      // TODO: support real cursor position.
      int text_length = static_cast<int>(text_.size());
      tab_contents_->render_view_host()->DetermineIfPageSupportsInstant(
          text_, verbatim_, text_length, text_length);
      break;
    }
    default:
      NOTREACHED();
      break;
  }
}

// TabContentsDelegateImpl -----------------------------------------------------

class InstantLoader::TabContentsDelegateImpl
    : public TabContentsDelegate,
      public NotificationObserver,
      public TabContentsObserver {
 public:
  explicit TabContentsDelegateImpl(InstantLoader* loader);

  // Invoked prior to loading a new URL.
  void PrepareForNewLoad();

  // Invoked when the preview paints. Invokes PreviewPainted on the loader.
  void PreviewPainted();

  bool is_mouse_down_from_activate() const {
    return is_mouse_down_from_activate_;
  }

  void set_user_typed_before_load() { user_typed_before_load_ = true; }

  // Sets the last URL that will be added to history when CommitHistory is
  // invoked and removes all but the first navigation.
  void SetLastHistoryURLAndPrune(const GURL& url);

  // Commits the currently buffered history.
  void CommitHistory(bool supports_instant);

  void RegisterForPaintNotifications(RenderWidgetHost* render_widget_host);

  void UnregisterForPaintNotifications();

  // NotificationObserver:
  virtual void Observe(NotificationType type,
                       const NotificationSource& source,
                       const NotificationDetails& details) OVERRIDE;

  // TabContentsDelegate:
  virtual void OpenURLFromTab(TabContents* source,
                              const GURL& url, const GURL& referrer,
                              WindowOpenDisposition disposition,
                              PageTransition::Type transition) OVERRIDE;
  virtual void NavigationStateChanged(const TabContents* source,
                                      unsigned changed_flags) OVERRIDE;
  virtual std::string GetNavigationHeaders(const GURL& url) OVERRIDE;
  virtual void AddNewContents(TabContents* source,
                              TabContents* new_contents,
                              WindowOpenDisposition disposition,
                              const gfx::Rect& initial_pos,
                              bool user_gesture) OVERRIDE;
  virtual void ActivateContents(TabContents* contents) OVERRIDE;
  virtual void DeactivateContents(TabContents* contents) OVERRIDE;
  virtual void LoadingStateChanged(TabContents* source) OVERRIDE;
  virtual void CloseContents(TabContents* source) OVERRIDE;
  virtual void MoveContents(TabContents* source,
                            const gfx::Rect& pos) OVERRIDE;
  virtual bool ShouldFocusConstrainedWindow() OVERRIDE;
  virtual void WillShowConstrainedWindow(TabContents* source) OVERRIDE;
  virtual void UpdateTargetURL(TabContents* source,
                               const GURL& url) OVERRIDE;
  virtual bool ShouldSuppressDialogs() OVERRIDE;
  virtual void BeforeUnloadFired(TabContents* tab,
                                 bool proceed,
                                 bool* proceed_to_fire_unload) OVERRIDE;
  virtual void SetFocusToLocationBar(bool select_all) OVERRIDE;
  virtual bool ShouldFocusPageAfterCrash() OVERRIDE;
  virtual void LostCapture() OVERRIDE;
  // If the user drags, we won't get a mouse up (at least on Linux). Commit the
  // instant result when the drag ends, so that during the drag the page won't
  // move around.
  virtual void DragEnded() OVERRIDE;
  virtual bool CanDownload(int request_id) OVERRIDE;
  virtual void HandleMouseUp() OVERRIDE;
  virtual void HandleMouseActivate() OVERRIDE;
  virtual bool OnGoToEntryOffset(int offset) OVERRIDE;
  virtual bool ShouldAddNavigationToHistory(
      const history::HistoryAddPageArgs& add_page_args,
      NavigationType::Type navigation_type) OVERRIDE;
  virtual bool ShouldShowHungRendererDialog() OVERRIDE;

  // TabContentsObserver:
  virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;

 private:
  typedef std::vector<scoped_refptr<history::HistoryAddPageArgs> >
      AddPageVector;

  // Message from renderer indicating the page has suggestions.
  void OnSetSuggestions(
      int32 page_id,
      const std::vector<std::string>& suggestions,
      InstantCompleteBehavior behavior);

  // Messages from the renderer when we've determined whether the page supports
  // instant.
  void OnInstantSupportDetermined(int32 page_id, bool result);

  void CommitFromMouseReleaseIfNecessary();

  InstantLoader* loader_;

  NotificationRegistrar registrar_;

  // If we are registered for paint notifications on a RenderWidgetHost this
  // will contain a pointer to it.
  RenderWidgetHost* registered_render_widget_host_;

  // Used to cache data that needs to be added to history. Normally entries are
  // added to history as the user types, but for instant we only want to add the
  // items to history if the user commits instant. So, we cache them here and if
  // committed then add the items to history.
  AddPageVector add_page_vector_;

  // Are we we waiting for a NavigationType of NEW_PAGE? If we're waiting for
  // NEW_PAGE navigation we don't add history items to add_page_vector_.
  bool waiting_for_new_page_;

  // True if the mouse is down from an activate.
  bool is_mouse_down_from_activate_;

  // True if the user typed in the search box before the page loaded.
  bool user_typed_before_load_;

  DISALLOW_COPY_AND_ASSIGN(TabContentsDelegateImpl);
};

InstantLoader::TabContentsDelegateImpl::TabContentsDelegateImpl(
    InstantLoader* loader)
    : TabContentsObserver(loader->preview_contents()->tab_contents()),
      loader_(loader),
      registered_render_widget_host_(NULL),
      waiting_for_new_page_(true),
      is_mouse_down_from_activate_(false),
      user_typed_before_load_(false) {
  DCHECK(loader->preview_contents());
  registrar_.Add(this, NotificationType::INTERSTITIAL_ATTACHED,
      Source<TabContents>(loader->preview_contents()->tab_contents()));
  registrar_.Add(this, NotificationType::FAIL_PROVISIONAL_LOAD_WITH_ERROR,
      Source<NavigationController>(&loader->preview_contents()->controller()));
}

void InstantLoader::TabContentsDelegateImpl::PrepareForNewLoad() {
  user_typed_before_load_ = false;
  waiting_for_new_page_ = true;
  add_page_vector_.clear();
  UnregisterForPaintNotifications();
}

void InstantLoader::TabContentsDelegateImpl::PreviewPainted() {
  loader_->PreviewPainted();
}

void InstantLoader::TabContentsDelegateImpl::SetLastHistoryURLAndPrune(
    const GURL& url) {
  if (add_page_vector_.empty())
    return;

  history::HistoryAddPageArgs* args = add_page_vector_.front().get();
  args->url = url;
  args->redirects.clear();
  args->redirects.push_back(url);

  // Prune all but the first entry.
  add_page_vector_.erase(add_page_vector_.begin() + 1,
                         add_page_vector_.end());
}

void InstantLoader::TabContentsDelegateImpl::CommitHistory(
    bool supports_instant) {
  TabContents* tab = loader_->preview_contents()->tab_contents();
  if (tab->profile()->IsOffTheRecord())
    return;

  for (size_t i = 0; i < add_page_vector_.size(); ++i)
    tab->UpdateHistoryForNavigation(add_page_vector_[i].get());

  NavigationEntry* active_entry = tab->controller().GetActiveEntry();
  if (!active_entry) {
    // It appears to be possible to get here with no active entry. This seems
    // to be possible with an auth dialog, but I can't narrow down the
    // circumstances. If you hit this, file a bug with the steps you did and
    // assign it to me (sky).
    NOTREACHED();
    return;
  }
  tab->UpdateHistoryPageTitle(*active_entry);

  FaviconService* favicon_service =
      tab->profile()->GetFaviconService(Profile::EXPLICIT_ACCESS);

  if (favicon_service && active_entry->favicon().is_valid() &&
      !active_entry->favicon().bitmap().empty()) {
    std::vector<unsigned char> image_data;
    gfx::PNGCodec::EncodeBGRASkBitmap(active_entry->favicon().bitmap(), false,
                                      &image_data);
    favicon_service->SetFavicon(active_entry->url(),
                                active_entry->favicon().url(),
                                image_data,
                                history::FAVICON);
    if (supports_instant && !add_page_vector_.empty()) {
      // If we're using the instant API, then we've tweaked the url that is
      // going to be added to history. We need to also set the favicon for the
      // url we're adding to history (see comment in ReleasePreviewContents
      // for details).
      favicon_service->SetFavicon(add_page_vector_.back()->url,
                                  active_entry->favicon().url(),
                                  image_data,
                                  history::FAVICON);
    }
  }
}

void InstantLoader::TabContentsDelegateImpl::RegisterForPaintNotifications(
    RenderWidgetHost* render_widget_host) {
  DCHECK(registered_render_widget_host_ == NULL);
  registered_render_widget_host_ = render_widget_host;
  Source<RenderWidgetHost> source =
      Source<RenderWidgetHost>(registered_render_widget_host_);
  registrar_.Add(this, NotificationType::RENDER_WIDGET_HOST_DID_PAINT,
                 source);
  registrar_.Add(this, NotificationType::RENDER_WIDGET_HOST_DESTROYED,
                 source);
}

void InstantLoader::TabContentsDelegateImpl::UnregisterForPaintNotifications() {
  if (registered_render_widget_host_) {
    Source<RenderWidgetHost> source =
        Source<RenderWidgetHost>(registered_render_widget_host_);
    registrar_.Remove(this, NotificationType::RENDER_WIDGET_HOST_DID_PAINT,
                      source);
    registrar_.Remove(this, NotificationType::RENDER_WIDGET_HOST_DESTROYED,
                      source);
    registered_render_widget_host_ = NULL;
  }
}

void InstantLoader::TabContentsDelegateImpl::Observe(
    NotificationType type,
    const NotificationSource& source,
    const NotificationDetails& details) {
  switch (type.value) {
    case NotificationType::FAIL_PROVISIONAL_LOAD_WITH_ERROR:
      if (Details<ProvisionalLoadDetails>(details)->url() == loader_->url_) {
        // This typically happens with downloads (which are disabled with
        // instant active). To ensure the download happens when the user presses
        // enter we set needs_reload_ to true, which triggers a reload.
        loader_->needs_reload_ = true;
      }
      break;
    case NotificationType::RENDER_WIDGET_HOST_DID_PAINT:
      UnregisterForPaintNotifications();
      PreviewPainted();
      break;
    case NotificationType::RENDER_WIDGET_HOST_DESTROYED:
      UnregisterForPaintNotifications();
      break;
    case NotificationType::INTERSTITIAL_ATTACHED:
      PreviewPainted();
      break;
    default:
      NOTREACHED() << "Got a notification we didn't register for.";
  }
}

void InstantLoader::TabContentsDelegateImpl::OpenURLFromTab(
    TabContents* source,
    const GURL& url, const GURL& referrer,
    WindowOpenDisposition disposition,
    PageTransition::Type transition) {
}

void InstantLoader::TabContentsDelegateImpl::NavigationStateChanged(
    const TabContents* source,
    unsigned changed_flags) {
  if (!loader_->ready() && !registered_render_widget_host_ &&
      source->controller().entry_count()) {
    // The load has been committed. Install an observer that waits for the
    // first paint then makes the preview active. We wait for the load to be
    // committed before waiting on paint as there is always an initial paint
    // when a new renderer is created from the resize so that if we showed the
    // preview after the first paint we would end up with a white rect.
    RenderWidgetHostView *rwhv = source->GetRenderWidgetHostView();
    if (rwhv)
      RegisterForPaintNotifications(rwhv->GetRenderWidgetHost());
  } else if (source->is_crashed()) {
    PreviewPainted();
  }
}

std::string InstantLoader::TabContentsDelegateImpl::GetNavigationHeaders(
    const GURL& url) {
  std::string header;
  net::HttpUtil::AppendHeaderIfMissing(kPreviewHeader, kPreviewHeaderValue,
                                       &header);
  return header;
}

void InstantLoader::TabContentsDelegateImpl::AddNewContents(
    TabContents* source,
    TabContents* new_contents,
    WindowOpenDisposition disposition,
    const gfx::Rect& initial_pos,
    bool user_gesture) {
}

void InstantLoader::TabContentsDelegateImpl::ActivateContents(
    TabContents* contents) {
}

void InstantLoader::TabContentsDelegateImpl::DeactivateContents(
    TabContents* contents) {
}

void InstantLoader::TabContentsDelegateImpl::LoadingStateChanged(
    TabContents* source) {
}

void InstantLoader::TabContentsDelegateImpl::CloseContents(
    TabContents* source) {
}

void InstantLoader::TabContentsDelegateImpl::MoveContents(
    TabContents* source,
    const gfx::Rect& pos) {
}

bool InstantLoader::TabContentsDelegateImpl::ShouldFocusConstrainedWindow() {
  // Return false so that constrained windows are not initially focused. If
  // we did otherwise the preview would prematurely get committed when focus
  // goes to the constrained window.
  return false;
}

void InstantLoader::TabContentsDelegateImpl::WillShowConstrainedWindow(
    TabContents* source) {
  if (!loader_->ready()) {
    // A constrained window shown for an auth may not paint. Show the preview
    // contents.
    UnregisterForPaintNotifications();
    loader_->ShowPreview();
  }
}

void InstantLoader::TabContentsDelegateImpl::UpdateTargetURL(
    TabContents* source, const GURL& url) {
}

bool InstantLoader::TabContentsDelegateImpl::ShouldSuppressDialogs() {
  // Any message shown during instant cancels instant, so we suppress them.
  return true;
}

void InstantLoader::TabContentsDelegateImpl::BeforeUnloadFired(
    TabContents* tab,
    bool proceed,
    bool* proceed_to_fire_unload) {
}

void InstantLoader::TabContentsDelegateImpl::SetFocusToLocationBar(
    bool select_all) {
}

bool InstantLoader::TabContentsDelegateImpl::ShouldFocusPageAfterCrash() {
  return false;
}

void InstantLoader::TabContentsDelegateImpl::LostCapture() {
  CommitFromMouseReleaseIfNecessary();
}

void InstantLoader::TabContentsDelegateImpl::DragEnded() {
  CommitFromMouseReleaseIfNecessary();
}

bool InstantLoader::TabContentsDelegateImpl::CanDownload(int request_id) {
  // Downloads are disabled.
  return false;
}

void InstantLoader::TabContentsDelegateImpl::HandleMouseUp() {
  CommitFromMouseReleaseIfNecessary();
}

void InstantLoader::TabContentsDelegateImpl::HandleMouseActivate() {
  is_mouse_down_from_activate_ = true;
}

bool InstantLoader::TabContentsDelegateImpl::OnGoToEntryOffset(int offset) {
  return false;
}

bool InstantLoader::TabContentsDelegateImpl::ShouldAddNavigationToHistory(
    const history::HistoryAddPageArgs& add_page_args,
    NavigationType::Type navigation_type) {
  if (waiting_for_new_page_ && navigation_type == NavigationType::NEW_PAGE)
    waiting_for_new_page_ = false;

  if (!waiting_for_new_page_) {
    add_page_vector_.push_back(
        scoped_refptr<history::HistoryAddPageArgs>(add_page_args.Clone()));
  }
  return false;
}

bool InstantLoader::TabContentsDelegateImpl::ShouldShowHungRendererDialog() {
  // If we allow the hung renderer dialog to be shown it'll gain focus,
  // stealing focus from the omnibox causing instant to be cancelled. Return
  // false so that doesn't happen.
  return false;
}

bool InstantLoader::TabContentsDelegateImpl::OnMessageReceived(
    const IPC::Message& message) {
  bool handled = true;
  IPC_BEGIN_MESSAGE_MAP(TabContentsDelegateImpl, message)
    IPC_MESSAGE_HANDLER(ViewHostMsg_SetSuggestions, OnSetSuggestions)
    IPC_MESSAGE_HANDLER(ViewHostMsg_InstantSupportDetermined,
                        OnInstantSupportDetermined)
    IPC_MESSAGE_UNHANDLED(handled = false)
  IPC_END_MESSAGE_MAP()
  return handled;
}

void InstantLoader::TabContentsDelegateImpl::OnSetSuggestions(
    int32 page_id,
    const std::vector<std::string>& suggestions,
    InstantCompleteBehavior behavior) {
  TabContentsWrapper* source = loader_->preview_contents();
  if (!source->controller().GetActiveEntry() ||
      page_id != source->controller().GetActiveEntry()->page_id())
    return;

  if (suggestions.empty())
    loader_->SetCompleteSuggestedText(string16(), behavior);
  else
    loader_->SetCompleteSuggestedText(UTF8ToUTF16(suggestions[0]), behavior);
}

void InstantLoader::TabContentsDelegateImpl::OnInstantSupportDetermined(
    int32 page_id,
    bool result) {
  TabContents* source = loader_->preview_contents()->tab_contents();
  if (!source->controller().GetActiveEntry() ||
      page_id != source->controller().GetActiveEntry()->page_id())
    return;

  Details<const bool> details(&result);
  NotificationService::current()->Notify(
      NotificationType::INSTANT_SUPPORT_DETERMINED,
      NotificationService::AllSources(),
      details);

  if (result)
    loader_->PageFinishedLoading();
  else
    loader_->PageDoesntSupportInstant(user_typed_before_load_);
}

void InstantLoader::TabContentsDelegateImpl
    ::CommitFromMouseReleaseIfNecessary() {
  bool was_down = is_mouse_down_from_activate_;
  is_mouse_down_from_activate_ = false;
  if (was_down && loader_->ShouldCommitInstantOnMouseUp())
    loader_->CommitInstantLoader();
}

// InstantLoader ---------------------------------------------------------------

InstantLoader::InstantLoader(InstantLoaderDelegate* delegate, TemplateURLID id)
    : delegate_(delegate),
      template_url_id_(id),
      ready_(false),
      http_status_ok_(true),
      last_transition_type_(PageTransition::LINK),
      verbatim_(false),
      needs_reload_(false) {
}

InstantLoader::~InstantLoader() {
  registrar_.RemoveAll();

  // Delete the TabContents before the delegate as the TabContents holds a
  // reference to the delegate.
  preview_contents_.reset();
}

bool InstantLoader::Update(TabContentsWrapper* tab_contents,
                           const TemplateURL* template_url,
                           const GURL& url,
                           PageTransition::Type transition_type,
                           const string16& user_text,
                           bool verbatim,
                           string16* suggested_text) {
  DCHECK(!url.is_empty() && url.is_valid());

  // Strip leading ?.
  string16 new_user_text =
      !user_text.empty() && (UTF16ToWide(user_text)[0] == L'?') ?
      user_text.substr(1) : user_text;

  // We should preserve the transition type regardless of whether we're already
  // showing the url.
  last_transition_type_ = transition_type;

  // If state hasn't changed, reuse the last suggestion. There are two cases:
  // 1. If no template url (not using instant API), then we only care if the url
  //    changes.
  // 2. Template url (using instant API) then the important part is if the
  //    user_text changes.
  //    We have to be careful in checking user_text as in some situations
  //    InstantController passes in an empty string (when it knows the user_text
  //    won't matter).
  if ((!template_url_id_ && url_ == url) ||
      (template_url_id_ &&
       (new_user_text.empty() || user_text_ == new_user_text))) {
    suggested_text->assign(last_suggestion_);
    // Track the url even if we're not going to update. This is important as
    // when we get the suggest text we set user_text_ to the new suggest text,
    // but yet the url is much different.
    url_ = url;
    return false;
  }

  url_ = url;
  user_text_ = new_user_text;
  verbatim_ = verbatim;
  last_suggestion_.clear();
  needs_reload_ = false;

  bool created_preview_contents = preview_contents_.get() == NULL;
  if (created_preview_contents)
    CreatePreviewContents(tab_contents);

  if (template_url) {
    DCHECK(template_url_id_ == template_url->id());
    if (!created_preview_contents) {
      if (is_waiting_for_load()) {
        // The page hasn't loaded yet. We'll send the script down when it does.
        frame_load_observer_->set_text(user_text_);
        frame_load_observer_->set_verbatim(verbatim);
        preview_tab_contents_delegate_->set_user_typed_before_load();
        return true;
      }
      // TODO: support real cursor position.
      int text_length = static_cast<int>(user_text_.size());
      preview_contents_->render_view_host()->SearchBoxChange(
          user_text_, verbatim, text_length, text_length);

      string16 complete_suggested_text_lower = l10n_util::ToLower(
          complete_suggested_text_);
      string16 user_text_lower = l10n_util::ToLower(user_text_);
      if (!verbatim &&
          complete_suggested_text_lower.size() > user_text_lower.size() &&
          !complete_suggested_text_lower.compare(0, user_text_lower.size(),
                                                 user_text_lower)) {
        *suggested_text = last_suggestion_ =
            complete_suggested_text_.substr(user_text_.size());
      }
    } else {
      preview_tab_contents_delegate_->PrepareForNewLoad();

      // Load the instant URL. We don't reflect the url we load in url() as
      // callers expect that we're loading the URL they tell us to.
      //
      // This uses an empty string for the replacement text as the url doesn't
      // really have the search params, but we need to use the replace
      // functionality so that embeded tags (like {google:baseURL}) are escaped
      // correctly.
      // TODO(sky): having to use a replaceable url is a bit of a hack here.
      GURL instant_url(
          template_url->instant_url()->ReplaceSearchTerms(
              *template_url, string16(), -1, string16()));
      CommandLine* cl = CommandLine::ForCurrentProcess();
      if (cl->HasSwitch(switches::kInstantURL))
        instant_url = GURL(cl->GetSwitchValueASCII(switches::kInstantURL));
      preview_contents_->controller().LoadURL(
          instant_url, GURL(), transition_type);
      preview_contents_->render_view_host()->SearchBoxChange(
          user_text_, verbatim, 0, 0);
      frame_load_observer_.reset(
          new FrameLoadObserver(this,
                                preview_contents()->tab_contents(),
                                user_text_,
                                verbatim));
    }
  } else {
    DCHECK(template_url_id_ == 0);
    preview_tab_contents_delegate_->PrepareForNewLoad();
    frame_load_observer_.reset(NULL);
    preview_contents_->controller().LoadURL(url_, GURL(), transition_type);
  }
  return true;
}

void InstantLoader::SetOmniboxBounds(const gfx::Rect& bounds) {
  if (omnibox_bounds_ == bounds)
    return;

  // Don't update the page while the mouse is down. http://crbug.com/71952
  if (IsMouseDownFromActivate())
    return;

  omnibox_bounds_ = bounds;
  if (preview_contents_.get() && is_showing_instant() &&
      !is_waiting_for_load()) {
    // Updating the bounds is rather expensive, and because of the async nature
    // of the omnibox the bounds can dance around a bit. Delay the update in
    // hopes of things settling down. To avoid hiding results we grow
    // immediately, but delay shrinking.
    update_bounds_timer_.Stop();
    if (omnibox_bounds_.height() > last_omnibox_bounds_.height()) {
      SendBoundsToPage(false);
    } else {
      update_bounds_timer_.Start(
          base::TimeDelta::FromMilliseconds(kUpdateBoundsDelayMS),
          this, &InstantLoader::ProcessBoundsChange);
    }
  }
}

bool InstantLoader::IsMouseDownFromActivate() {
  return preview_tab_contents_delegate_.get() &&
      preview_tab_contents_delegate_->is_mouse_down_from_activate();
}

TabContentsWrapper* InstantLoader::ReleasePreviewContents(
    InstantCommitType type) {
  if (!preview_contents_.get())
    return NULL;

  // FrameLoadObserver is only used for instant results, and instant results are
  // only committed if active (when the FrameLoadObserver isn't installed).
  DCHECK(type == INSTANT_COMMIT_DESTROY || !frame_load_observer_.get());

  if (type != INSTANT_COMMIT_DESTROY && is_showing_instant()) {
    if (type == INSTANT_COMMIT_FOCUS_LOST)
      preview_contents_->render_view_host()->SearchBoxCancel();
    else
      preview_contents_->render_view_host()->SearchBoxSubmit(
          user_text_, type == INSTANT_COMMIT_PRESSED_ENTER);
  }
  omnibox_bounds_ = gfx::Rect();
  last_omnibox_bounds_ = gfx::Rect();
  GURL url;
  url.Swap(&url_);
  user_text_.clear();
  complete_suggested_text_.clear();
  if (preview_contents_.get()) {
    if (type != INSTANT_COMMIT_DESTROY) {
      if (template_url_id_) {
        // The URL used during instant is mostly gibberish, and not something
        // we'll parse and match as a past search. Set it to something we can
        // parse.
        preview_tab_contents_delegate_->SetLastHistoryURLAndPrune(url);
      }
      preview_tab_contents_delegate_->CommitHistory(template_url_id_ != 0);
    }
    if (preview_contents_->tab_contents()->GetRenderWidgetHostView()) {
#if defined(OS_MACOSX)
      preview_contents_->tab_contents()->GetRenderWidgetHostView()->
          SetTakesFocusOnlyOnMouseDown(false);
      registrar_.Remove(
          this,
          NotificationType::RENDER_VIEW_HOST_CHANGED,
          Source<NavigationController>(&preview_contents_->controller()));
#endif
    }
    preview_contents_->tab_contents()->set_delegate(NULL);
    ready_ = false;
  }
  update_bounds_timer_.Stop();
  return preview_contents_.release();
}

bool InstantLoader::ShouldCommitInstantOnMouseUp() {
  return delegate_->ShouldCommitInstantOnMouseUp();
}

void InstantLoader::CommitInstantLoader() {
  delegate_->CommitInstantLoader(this);
}

void InstantLoader::SetCompleteSuggestedText(
    const string16& complete_suggested_text,
    InstantCompleteBehavior behavior) {
  if (!is_showing_instant()) {
    // We're not trying to use the instant API with this page. Ignore it.
    return;
  }

  ShowPreview();

  if (complete_suggested_text == complete_suggested_text_)
    return;

  if (verbatim_) {
    // Don't show suggest results for verbatim queries.
    return;
  }

  string16 user_text_lower = l10n_util::ToLower(user_text_);
  string16 complete_suggested_text_lower = l10n_util::ToLower(
      complete_suggested_text);
  last_suggestion_.clear();
  if (user_text_lower.compare(0, user_text_lower.size(),
                              complete_suggested_text_lower,
                              0, user_text_lower.size())) {
    // The user text no longer contains the suggested text, ignore it.
    complete_suggested_text_.clear();
    delegate_->SetSuggestedTextFor(this, string16(), behavior);
    return;
  }

  complete_suggested_text_ = complete_suggested_text;
  if (behavior == INSTANT_COMPLETE_NOW) {
    // We are effectively showing complete_suggested_text_ now. Update
    // user_text_ so we don't notify the page again if Update happens to be
    // invoked (which is more than likely if this callback completes before the
    // omnibox is done).
    string16 suggestion = complete_suggested_text_.substr(user_text_.size());
    user_text_ = complete_suggested_text_;
    delegate_->SetSuggestedTextFor(this, suggestion, behavior);
  } else {
    DCHECK((behavior == INSTANT_COMPLETE_DELAYED) ||
           (behavior == INSTANT_COMPLETE_NEVER));
    last_suggestion_ = complete_suggested_text_.substr(user_text_.size());
    delegate_->SetSuggestedTextFor(this, last_suggestion_, behavior);
  }
}

void InstantLoader::PreviewPainted() {
  // If instant is supported then we wait for the first suggest result before
  // showing the page.
  if (!template_url_id_)
    ShowPreview();
}

void InstantLoader::SetHTTPStatusOK(bool is_ok) {
  if (is_ok == http_status_ok_)
    return;

  http_status_ok_ = is_ok;
  if (ready_)
    delegate_->InstantStatusChanged(this);
}

void InstantLoader::ShowPreview() {
  if (!ready_) {
    ready_ = true;
    delegate_->InstantStatusChanged(this);
  }
}

void InstantLoader::Observe(NotificationType type,
                            const NotificationSource& source,
                            const NotificationDetails& details) {
#if defined(OS_MACOSX)
  if (type.value == NotificationType::RENDER_VIEW_HOST_CHANGED) {
    if (preview_contents_->tab_contents()->GetRenderWidgetHostView()) {
      preview_contents_->tab_contents()->GetRenderWidgetHostView()->
          SetTakesFocusOnlyOnMouseDown(true);
    }
    return;
  }
#endif
  if (type.value == NotificationType::NAV_ENTRY_COMMITTED) {
    NavigationController::LoadCommittedDetails* load_details =
        Details<NavigationController::LoadCommittedDetails>(details).ptr();
    if (load_details->is_main_frame) {
      if (load_details->http_status_code == kHostBlacklistStatusCode) {
        delegate_->AddToBlacklist(this, load_details->entry->url());
      } else {
        SetHTTPStatusOK(load_details->http_status_code == 200);
      }
    }
    return;
  }

  NOTREACHED() << "Got a notification we didn't register for.";
}

void InstantLoader::PageFinishedLoading() {
  frame_load_observer_.reset();

  // Send the bounds of the omnibox down now.
  SendBoundsToPage(false);

  // Wait for the user input before showing, this way the page should be up to
  // date by the time we show it.
}

// TODO(tonyg): This method only fires when the omnibox bounds change. It also
// needs to fire when the preview bounds change (e.g. open/close info bar).
gfx::Rect InstantLoader::GetOmniboxBoundsInTermsOfPreview() {
  gfx::Rect preview_bounds(delegate_->GetInstantBounds());
  gfx::Rect intersection(omnibox_bounds_.Intersect(preview_bounds));

  // Translate into window's coordinates.
  if (!intersection.IsEmpty()) {
    intersection.Offset(-preview_bounds.origin().x(),
                        -preview_bounds.origin().y());
  }

  // In the current Chrome UI, these must always be true so they sanity check
  // the above operations. In a future UI, these may be removed or adjusted.
  DCHECK_EQ(0, intersection.y());
  DCHECK_LE(0, intersection.x());
  DCHECK_LE(0, intersection.width());
  DCHECK_LE(0, intersection.height());

  return intersection;
}

void InstantLoader::PageDoesntSupportInstant(bool needs_reload) {
  frame_load_observer_.reset(NULL);

  delegate_->InstantLoaderDoesntSupportInstant(this);
}

void InstantLoader::ProcessBoundsChange() {
  SendBoundsToPage(false);
}

void InstantLoader::SendBoundsToPage(bool force_if_waiting) {
  if (last_omnibox_bounds_ == omnibox_bounds_)
    return;

  if (preview_contents_.get() && is_showing_instant() &&
      (force_if_waiting || !is_waiting_for_load())) {
    last_omnibox_bounds_ = omnibox_bounds_;
    preview_contents_->render_view_host()->SearchBoxResize(
        GetOmniboxBoundsInTermsOfPreview());
  }
}

void InstantLoader::CreatePreviewContents(TabContentsWrapper* tab_contents) {
  TabContents* new_contents =
      new TabContents(
          tab_contents->profile(), NULL, MSG_ROUTING_NONE, NULL, NULL);
  preview_contents_.reset(new TabContentsWrapper(new_contents));
  new_contents->SetAllContentsBlocked(true);
  // Propagate the max page id. That way if we end up merging the two
  // NavigationControllers (which happens if we commit) none of the page ids
  // will overlap.
  int32 max_page_id = tab_contents->tab_contents()->GetMaxPageID();
  if (max_page_id != -1)
    preview_contents_->controller().set_max_restored_page_id(max_page_id + 1);

  preview_tab_contents_delegate_.reset(new TabContentsDelegateImpl(this));
  new_contents->set_delegate(preview_tab_contents_delegate_.get());

  gfx::Rect tab_bounds;
  tab_contents->view()->GetContainerBounds(&tab_bounds);
  preview_contents_->view()->SizeContents(tab_bounds.size());

#if defined(OS_MACOSX)
  // If |preview_contents_| does not currently have a RWHV, we will call
  // SetTakesFocusOnlyOnMouseDown() as a result of the
  // RENDER_VIEW_HOST_CHANGED notification.
  if (preview_contents_->tab_contents()->GetRenderWidgetHostView()) {
    preview_contents_->tab_contents()->GetRenderWidgetHostView()->
        SetTakesFocusOnlyOnMouseDown(true);
  }
  registrar_.Add(
      this,
      NotificationType::RENDER_VIEW_HOST_CHANGED,
      Source<NavigationController>(&preview_contents_->controller()));
#endif

  registrar_.Add(
      this,
      NotificationType::NAV_ENTRY_COMMITTED,
      Source<NavigationController>(&preview_contents_->controller()));

  preview_contents_->tab_contents()->ShowContents();
}