instant_loader.cc revision 731df977c0511bca2206b5f333555b1205ff1f43
1// Copyright (c) 2010 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 <utility>
9
10#include "base/command_line.h"
11#include "base/string_number_conversions.h"
12#include "base/utf_string_conversions.h"
13#include "base/values.h"
14#include "chrome/browser/favicon_service.h"
15#include "chrome/browser/history/history_marshaling.h"
16#include "chrome/browser/instant/instant_loader_delegate.h"
17#include "chrome/browser/profile.h"
18#include "chrome/browser/renderer_host/render_view_host.h"
19#include "chrome/browser/renderer_host/render_widget_host.h"
20#include "chrome/browser/renderer_host/render_widget_host_view.h"
21#include "chrome/browser/search_engines/template_url.h"
22#include "chrome/browser/tab_contents/navigation_controller.h"
23#include "chrome/browser/tab_contents/navigation_entry.h"
24#include "chrome/browser/tab_contents/tab_contents.h"
25#include "chrome/browser/tab_contents/tab_contents_delegate.h"
26#include "chrome/browser/tab_contents/tab_contents_view.h"
27#include "chrome/common/notification_observer.h"
28#include "chrome/common/notification_registrar.h"
29#include "chrome/common/notification_service.h"
30#include "chrome/common/page_transition_types.h"
31#include "chrome/common/render_messages.h"
32#include "chrome/common/renderer_preferences.h"
33#include "gfx/codec/png_codec.h"
34#include "ipc/ipc_message.h"
35
36namespace {
37
38// Script sent as the user is typing and the provider supports instant.
39// Params:
40// . the text the user typed.
41// TODO: add support for the 2nd and 3rd params.
42const char kUserInputScript[] =
43    "if (window.chrome.userInput) window.chrome.userInput(\"$1\", 0, 0);";
44
45// Script sent when the page is committed and the provider supports instant.
46// Params:
47// . the text the user typed.
48// . boolean indicating if the user pressed enter to accept the text.
49const char kUserDoneScript[] =
50    "if (window.chrome.userWantsQuery) "
51      "window.chrome.userWantsQuery(\"$1\", $2);";
52
53// Script sent when the bounds of the omnibox changes and the provider supports
54// instant. The params are the bounds relative to the origin of the preview
55// (x, y, width, height).
56const char kSetOmniboxBoundsScript[] =
57    "if (window.chrome.setDropdownDimensions) "
58    "window.chrome.setDropdownDimensions($1, $2, $3, $4);";
59
60// Script sent to see if the page really supports instant.
61const char kSupportsInstantScript[] =
62    "if (window.chrome.setDropdownDimensions) true; else false;";
63
64// Number of ms to delay before updating the omnibox bounds. This is a bit long
65// as updating the bounds ends up being quite expensive.
66const int kUpdateBoundsDelayMS = 500;
67
68// Escapes quotes in the |text| so that it be passed to JavaScript as a quoted
69// string.
70string16 EscapeUserText(const string16& text) {
71  string16 escaped_text(text);
72  ReplaceSubstringsAfterOffset(&escaped_text, 0L, ASCIIToUTF16("\""),
73                               ASCIIToUTF16("\\\""));
74  return escaped_text;
75}
76
77// Sends the script for when the user commits the preview. |pressed_enter| is
78// true if the user pressed enter to commit.
79void SendDoneScript(TabContents* tab_contents,
80                    const string16& text,
81                    bool pressed_enter) {
82  std::vector<string16> params;
83  params.push_back(EscapeUserText(text));
84  params.push_back(pressed_enter ? ASCIIToUTF16("true") :
85                   ASCIIToUTF16("false"));
86  string16 script = ReplaceStringPlaceholders(ASCIIToUTF16(kUserDoneScript),
87                                              params,
88                                              NULL);
89  tab_contents->render_view_host()->ExecuteJavascriptInWebFrame(
90      std::wstring(),
91      UTF16ToWide(script));
92}
93
94// Sends the user input script to |tab_contents|. |text| is the text the user
95// input into the omnibox.
96void SendUserInputScript(TabContents* tab_contents, const string16& text) {
97  string16 script = ReplaceStringPlaceholders(ASCIIToUTF16(kUserInputScript),
98                                              EscapeUserText(text),
99                                              NULL);
100  tab_contents->render_view_host()->ExecuteJavascriptInWebFrame(
101      std::wstring(),
102      UTF16ToWide(script));
103}
104
105// Sends the script for setting the bounds of the omnibox to |tab_contents|.
106void SendOmniboxBoundsScript(TabContents* tab_contents,
107                             const gfx::Rect& bounds) {
108  std::vector<string16> bounds_vector;
109  bounds_vector.push_back(base::IntToString16(bounds.x()));
110  bounds_vector.push_back(base::IntToString16(bounds.y()));
111  bounds_vector.push_back(base::IntToString16(bounds.width()));
112  bounds_vector.push_back(base::IntToString16(bounds.height()));
113  string16 script = ReplaceStringPlaceholders(
114      ASCIIToUTF16(kSetOmniboxBoundsScript),
115      bounds_vector,
116      NULL);
117  tab_contents->render_view_host()->ExecuteJavascriptInWebFrame(
118      std::wstring(),
119      UTF16ToWide(script));
120}
121
122}  // namespace
123
124// FrameLoadObserver is responsible for waiting for the TabContents to finish
125// loading and when done sending the necessary script down to the page.
126class InstantLoader::FrameLoadObserver : public NotificationObserver {
127 public:
128  FrameLoadObserver(InstantLoader* loader, const string16& text)
129      : loader_(loader),
130        tab_contents_(loader->preview_contents()),
131        unique_id_(tab_contents_->controller().pending_entry()->unique_id()),
132        text_(text),
133        initial_text_(text),
134        execute_js_id_(0) {
135    registrar_.Add(this, NotificationType::LOAD_COMPLETED_MAIN_FRAME,
136                   Source<TabContents>(tab_contents_));
137  }
138
139  // Sets the text to send to the page.
140  void set_text(const string16& text) { text_ = text; }
141
142  // NotificationObserver:
143  virtual void Observe(NotificationType type,
144                       const NotificationSource& source,
145                       const NotificationDetails& details) {
146    switch (type.value) {
147      case NotificationType::LOAD_COMPLETED_MAIN_FRAME: {
148        int page_id = *(Details<int>(details).ptr());
149        NavigationEntry* active_entry =
150            tab_contents_->controller().GetActiveEntry();
151        if (!active_entry || active_entry->page_id() != page_id ||
152            active_entry->unique_id() != unique_id_) {
153          return;
154        }
155
156        DetermineIfPageSupportsInstant();
157        break;
158      }
159
160      case NotificationType::EXECUTE_JAVASCRIPT_RESULT: {
161        typedef std::pair<int, Value*> ExecuteDetailType;
162        ExecuteDetailType* result =
163            (static_cast<Details<ExecuteDetailType > >(details)).ptr();
164        if (result->first != execute_js_id_)
165          return;
166
167        DCHECK(loader_);
168        bool bool_result;
169        if (!result->second || !result->second->IsType(Value::TYPE_BOOLEAN) ||
170            !result->second->GetAsBoolean(&bool_result) || !bool_result) {
171          DoesntSupportInstant();
172          return;
173        }
174        SupportsInstant();
175        return;
176      }
177
178      default:
179        NOTREACHED();
180        break;
181    }
182  }
183
184 private:
185  // Executes the javascript to determine if the page supports script.  The
186  // results are passed back to us by way of NotificationObserver.
187  void DetermineIfPageSupportsInstant() {
188    DCHECK_EQ(0, execute_js_id_);
189
190    RenderViewHost* rvh = tab_contents_->render_view_host();
191    registrar_.Add(this, NotificationType::EXECUTE_JAVASCRIPT_RESULT,
192                   Source<RenderViewHost>(rvh));
193    execute_js_id_ = rvh->ExecuteJavascriptInWebFrameNotifyResult(
194        string16(),
195        ASCIIToUTF16(kSupportsInstantScript));
196  }
197
198  // Invoked when we determine the page doesn't really support instant.
199  void DoesntSupportInstant() {
200    DCHECK(loader_);
201
202    loader_->PageDoesntSupportInstant(text_ != initial_text_);
203
204    // WARNING: we've been deleted.
205  }
206
207  // Invoked when we determine the page really supports instant.
208  void SupportsInstant() {
209    DCHECK(loader_);
210
211    gfx::Rect bounds = loader_->GetOmniboxBoundsInTermsOfPreview();
212    loader_->last_omnibox_bounds_ = loader_->omnibox_bounds_;
213    if (!bounds.IsEmpty())
214      SendOmniboxBoundsScript(tab_contents_, bounds);
215
216    SendUserInputScript(tab_contents_, text_);
217
218    loader_->PageFinishedLoading();
219
220    // WARNING: we've been deleted.
221  }
222
223  // InstantLoader that created us.
224  InstantLoader* loader_;
225
226  // The TabContents we're listening for changes on.
227  TabContents* tab_contents_;
228
229  // unique_id of the NavigationEntry we're waiting on.
230  const int unique_id_;
231
232  // Text to send down to the page.
233  string16 text_;
234
235  // Initial text supplied to constructor.
236  const string16 initial_text_;
237
238  // Registers and unregisters us for notifications.
239  NotificationRegistrar registrar_;
240
241  // ID of the javascript that was executed to determine if the page supports
242  // instant.
243  int execute_js_id_;
244
245  DISALLOW_COPY_AND_ASSIGN(FrameLoadObserver);
246};
247
248// PaintObserver implementation. When the RenderWidgetHost paints itself this
249// notifies InstantLoader, which makes the TabContents active.
250class InstantLoader::PaintObserverImpl
251    : public RenderWidgetHost::PaintObserver {
252 public:
253  explicit PaintObserverImpl(InstantLoader* loader) : loader_(loader) {
254  }
255
256  virtual void RenderWidgetHostWillPaint(RenderWidgetHost* rwh) {
257  }
258
259  virtual void RenderWidgetHostDidPaint(RenderWidgetHost* rwh) {
260    loader_->PreviewPainted();
261    rwh->set_paint_observer(NULL);
262    // WARNING: we've been deleted.
263  }
264
265 private:
266  InstantLoader* loader_;
267
268  DISALLOW_COPY_AND_ASSIGN(PaintObserverImpl);
269};
270
271class InstantLoader::TabContentsDelegateImpl : public TabContentsDelegate {
272 public:
273  explicit TabContentsDelegateImpl(InstantLoader* loader)
274      : loader_(loader),
275        installed_paint_observer_(false),
276        waiting_for_new_page_(true),
277        is_mouse_down_from_activate_(false) {
278  }
279
280  // Invoked prior to loading a new URL.
281  void PrepareForNewLoad() {
282    waiting_for_new_page_ = true;
283    add_page_vector_.clear();
284  }
285
286  // Invoked when removed as the delegate. Gives a chance to do any necessary
287  // cleanup.
288  void Reset() {
289    installed_paint_observer_ = false;
290    is_mouse_down_from_activate_ = false;
291  }
292
293  bool is_mouse_down_from_activate() const {
294    return is_mouse_down_from_activate_;
295  }
296
297  // Commits the currently buffered history.
298  void CommitHistory() {
299    TabContents* tab = loader_->preview_contents();
300    if (tab->profile()->IsOffTheRecord())
301      return;
302
303    for (size_t i = 0; i < add_page_vector_.size(); ++i)
304      tab->UpdateHistoryForNavigation(add_page_vector_[i].get());
305
306    NavigationEntry* active_entry = tab->controller().GetActiveEntry();
307    DCHECK(active_entry);
308    tab->UpdateHistoryPageTitle(*active_entry);
309
310    FaviconService* favicon_service =
311        tab->profile()->GetFaviconService(Profile::EXPLICIT_ACCESS);
312
313    if (favicon_service && active_entry->favicon().is_valid() &&
314        !active_entry->favicon().bitmap().empty()) {
315      std::vector<unsigned char> image_data;
316      gfx::PNGCodec::EncodeBGRASkBitmap(active_entry->favicon().bitmap(), false,
317                                        &image_data);
318      favicon_service->SetFavicon(active_entry->url(),
319                                  active_entry->favicon().url(),
320                                  image_data);
321    }
322  }
323
324  virtual void OpenURLFromTab(TabContents* source,
325                              const GURL& url, const GURL& referrer,
326                              WindowOpenDisposition disposition,
327                              PageTransition::Type transition) {}
328  virtual void NavigationStateChanged(const TabContents* source,
329                                      unsigned changed_flags) {
330    if (!installed_paint_observer_ && source->controller().entry_count()) {
331      // The load has been committed. Install an observer that waits for the
332      // first paint then makes the preview active. We wait for the load to be
333      // committed before waiting on paint as there is always an initial paint
334      // when a new renderer is created from the resize so that if we showed the
335      // preview after the first paint we would end up with a white rect.
336      installed_paint_observer_ = true;
337      source->GetRenderWidgetHostView()->GetRenderWidgetHost()->
338          set_paint_observer(new PaintObserverImpl(loader_));
339    }
340  }
341  virtual void AddNewContents(TabContents* source,
342                              TabContents* new_contents,
343                              WindowOpenDisposition disposition,
344                              const gfx::Rect& initial_pos,
345                              bool user_gesture) {}
346  virtual void ActivateContents(TabContents* contents) {
347  }
348  virtual void DeactivateContents(TabContents* contents) {}
349  virtual void LoadingStateChanged(TabContents* source) {}
350  virtual void CloseContents(TabContents* source) {}
351  virtual void MoveContents(TabContents* source, const gfx::Rect& pos) {}
352  virtual void DetachContents(TabContents* source) {}
353  virtual bool IsPopup(const TabContents* source) const {
354    return false;
355  }
356  virtual bool ShouldFocusConstrainedWindow(TabContents* source) {
357    // Return false so that constrained windows are not initially focused. If
358    // we did otherwise the preview would prematurely get committed when focus
359    // goes to the constrained window.
360    return false;
361  }
362  virtual void WillShowConstrainedWindow(TabContents* source) {
363    if (!loader_->ready()) {
364      // A constrained window shown for an auth may not paint. Show the preview
365      // contents.
366      if (installed_paint_observer_) {
367        source->GetRenderWidgetHostView()->GetRenderWidgetHost()->
368            set_paint_observer(NULL);
369      }
370      installed_paint_observer_ = true;
371      loader_->ShowPreview();
372    }
373  }
374  virtual void ToolbarSizeChanged(TabContents* source, bool is_animating) {}
375  virtual void URLStarredChanged(TabContents* source, bool starred) {}
376  virtual void UpdateTargetURL(TabContents* source, const GURL& url) {}
377  virtual void ContentsMouseEvent(
378      TabContents* source, const gfx::Point& location, bool motion) {}
379  virtual void ContentsZoomChange(bool zoom_in) {}
380  virtual void OnContentSettingsChange(TabContents* source) {}
381  virtual bool IsApplication() const { return false; }
382  virtual void ConvertContentsToApplication(TabContents* source) {}
383  virtual bool CanReloadContents(TabContents* source) const { return true; }
384  virtual gfx::Rect GetRootWindowResizerRect() const {
385    return gfx::Rect();
386  }
387  virtual void ShowHtmlDialog(HtmlDialogUIDelegate* delegate,
388                              gfx::NativeWindow parent_window) {}
389  virtual void BeforeUnloadFired(TabContents* tab,
390                                 bool proceed,
391                                 bool* proceed_to_fire_unload) {}
392  virtual void ForwardMessageToExternalHost(const std::string& message,
393                                            const std::string& origin,
394                                            const std::string& target) {}
395  virtual bool IsExternalTabContainer() const { return false; }
396  virtual void SetFocusToLocationBar(bool select_all) {}
397  virtual bool ShouldFocusPageAfterCrash() { return false; }
398  virtual void RenderWidgetShowing() {}
399  virtual ExtensionFunctionDispatcher* CreateExtensionFunctionDispatcher(
400      RenderViewHost* render_view_host,
401      const std::string& extension_id) {
402    return NULL;
403  }
404  virtual bool TakeFocus(bool reverse) { return false; }
405  virtual void LostCapture() {
406    CommitFromMouseReleaseIfNecessary();
407  }
408  virtual void SetTabContentBlocked(TabContents* contents, bool blocked) {}
409  virtual void TabContentsFocused(TabContents* tab_content) {
410  }
411  virtual int GetExtraRenderViewHeight() const { return 0; }
412  virtual bool CanDownload(int request_id) { return false; }
413  virtual void OnStartDownload(DownloadItem* download, TabContents* tab) {}
414  virtual bool HandleContextMenu(const ContextMenuParams& params) {
415    return false;
416  }
417  virtual bool ExecuteContextMenuCommand(int command) {
418    return false;
419  }
420  virtual void ConfirmAddSearchProvider(const TemplateURL* template_url,
421                                        Profile* profile) {}
422  virtual void ShowPageInfo(Profile* profile,
423                            const GURL& url,
424                            const NavigationEntry::SSLStatus& ssl,
425                            bool show_history) {}
426  virtual bool PreHandleKeyboardEvent(const NativeWebKeyboardEvent& event,
427                                      bool* is_keyboard_shortcut) {
428    return false;
429  }
430  virtual void HandleMouseUp() {
431    CommitFromMouseReleaseIfNecessary();
432  }
433  virtual void HandleMouseActivate() {
434    is_mouse_down_from_activate_ = true;
435  }
436  virtual void ShowRepostFormWarningDialog(TabContents* tab_contents) {}
437  virtual void ShowContentSettingsWindow(ContentSettingsType content_type) {}
438  virtual void ShowCollectedCookiesDialog(TabContents* tab_contents) {}
439  virtual bool OnGoToEntryOffset(int offset) { return false; }
440  virtual bool ShouldAddNavigationToHistory(
441      const history::HistoryAddPageArgs& add_page_args,
442      NavigationType::Type navigation_type) {
443    if (waiting_for_new_page_ && navigation_type == NavigationType::NEW_PAGE)
444      waiting_for_new_page_ = false;
445
446    if (!waiting_for_new_page_) {
447      add_page_vector_.push_back(
448          scoped_refptr<history::HistoryAddPageArgs>(add_page_args.Clone()));
449    }
450    return false;
451  }
452  virtual void OnDidGetApplicationInfo(TabContents* tab_contents,
453                                       int32 page_id) {}
454  virtual gfx::NativeWindow GetFrameNativeWindow() {
455    return NULL;
456  }
457  virtual void TabContentsCreated(TabContents* new_contents) {}
458  virtual bool infobars_enabled() { return false; }
459  virtual bool ShouldEnablePreferredSizeNotifications() { return false; }
460  virtual void UpdatePreferredSize(const gfx::Size& pref_size) {}
461  virtual void ContentTypeChanged(TabContents* source) {}
462
463  virtual void OnSetSuggestResult(int32 page_id, const std::string& result) {
464    TabContents* source = loader_->preview_contents();
465    // TODO: only allow for default search provider.
466    if (source->controller().GetActiveEntry() &&
467        page_id == source->controller().GetActiveEntry()->page_id()) {
468      loader_->SetCompleteSuggestedText(UTF8ToUTF16(result));
469    }
470  }
471
472 private:
473  typedef std::vector<scoped_refptr<history::HistoryAddPageArgs> >
474      AddPageVector;
475
476  void CommitFromMouseReleaseIfNecessary() {
477    bool was_down = is_mouse_down_from_activate_;
478    is_mouse_down_from_activate_ = false;
479    if (was_down && loader_->ShouldCommitInstantOnMouseUp())
480      loader_->CommitInstantLoader();
481  }
482
483  InstantLoader* loader_;
484
485  // Has the paint observer been installed? See comment in
486  // NavigationStateChanged for details on this.
487  bool installed_paint_observer_;
488
489  // Used to cache data that needs to be added to history. Normally entries are
490  // added to history as the user types, but for instant we only want to add the
491  // items to history if the user commits instant. So, we cache them here and if
492  // committed then add the items to history.
493  AddPageVector add_page_vector_;
494
495  // Are we we waiting for a NavigationType of NEW_PAGE? If we're waiting for
496  // NEW_PAGE navigation we don't add history items to add_page_vector_.
497  bool waiting_for_new_page_;
498
499  // Returns true if the mouse is down from an activate.
500  bool is_mouse_down_from_activate_;
501
502  DISALLOW_COPY_AND_ASSIGN(TabContentsDelegateImpl);
503};
504
505InstantLoader::InstantLoader(InstantLoaderDelegate* delegate, TemplateURLID id)
506    : delegate_(delegate),
507      template_url_id_(id),
508      ready_(false),
509      last_transition_type_(PageTransition::LINK) {
510  preview_tab_contents_delegate_.reset(new TabContentsDelegateImpl(this));
511}
512
513InstantLoader::~InstantLoader() {
514  // Delete the TabContents before the delegate as the TabContents holds a
515  // reference to the delegate.
516  preview_contents_.reset(NULL);
517}
518
519void InstantLoader::Update(TabContents* tab_contents,
520                           const TemplateURL* template_url,
521                           const GURL& url,
522                           PageTransition::Type transition_type,
523                           const string16& user_text,
524                           string16* suggested_text) {
525  if (url_ == url)
526    return;
527
528  DCHECK(!url.is_empty() && url.is_valid());
529
530  last_transition_type_ = transition_type;
531  url_ = url;
532  user_text_ = user_text;
533
534  bool created_preview_contents;
535  if (preview_contents_.get() == NULL) {
536    preview_contents_.reset(
537        new TabContents(tab_contents->profile(), NULL, MSG_ROUTING_NONE,
538                        NULL, NULL));
539    preview_contents_->SetAllContentsBlocked(true);
540    // Propagate the max page id. That way if we end up merging the two
541    // NavigationControllers (which happens if we commit) none of the page ids
542    // will overlap.
543    int32 max_page_id = tab_contents->GetMaxPageID();
544    if (max_page_id != -1)
545      preview_contents_->controller().set_max_restored_page_id(max_page_id + 1);
546
547    preview_contents_->set_delegate(preview_tab_contents_delegate_.get());
548
549    gfx::Rect tab_bounds;
550    tab_contents->view()->GetContainerBounds(&tab_bounds);
551    preview_contents_->view()->SizeContents(tab_bounds.size());
552
553    preview_contents_->ShowContents();
554    created_preview_contents = true;
555  } else {
556    created_preview_contents = false;
557  }
558  preview_tab_contents_delegate_->PrepareForNewLoad();
559
560  if (template_url) {
561    DCHECK(template_url_id_ == template_url->id());
562    if (!created_preview_contents) {
563      if (is_waiting_for_load()) {
564        // The page hasn't loaded yet. We'll send the script down when it does.
565        frame_load_observer_->set_text(user_text_);
566        return;
567      }
568      SendUserInputScript(preview_contents_.get(), user_text_);
569      if (complete_suggested_text_.size() > user_text_.size() &&
570          !complete_suggested_text_.compare(0, user_text_.size(), user_text_)) {
571        *suggested_text = complete_suggested_text_.substr(user_text_.size());
572      }
573    } else {
574      // Load the instant URL. We don't reflect the url we load in url() as
575      // callers expect that we're loading the URL they tell us to.
576      GURL instant_url(
577          template_url->instant_url()->ReplaceSearchTerms(
578              *template_url, UTF16ToWideHack(user_text), -1, std::wstring()));
579      initial_instant_url_ = url;
580      preview_contents_->controller().LoadURL(
581          instant_url, GURL(), transition_type);
582      frame_load_observer_.reset(new FrameLoadObserver(this, user_text_));
583    }
584  } else {
585    DCHECK(template_url_id_ == 0);
586    frame_load_observer_.reset(NULL);
587    preview_contents_->controller().LoadURL(url_, GURL(), transition_type);
588  }
589}
590
591void InstantLoader::SetOmniboxBounds(const gfx::Rect& bounds) {
592  if (omnibox_bounds_ == bounds)
593    return;
594
595  omnibox_bounds_ = bounds;
596  if (preview_contents_.get() && is_showing_instant() &&
597      !is_waiting_for_load()) {
598    // Updating the bounds is rather expensive, and because of the async nature
599    // of the omnibox the bounds can dance around a bit. Delay the update in
600    // hopes of things settling down.
601    if (update_bounds_timer_.IsRunning())
602      update_bounds_timer_.Stop();
603    update_bounds_timer_.Start(
604        base::TimeDelta::FromMilliseconds(kUpdateBoundsDelayMS),
605        this, &InstantLoader::ProcessBoundsChange);
606  }
607}
608
609bool InstantLoader::IsMouseDownFromActivate() {
610  return preview_tab_contents_delegate_->is_mouse_down_from_activate();
611}
612
613TabContents* InstantLoader::ReleasePreviewContents(InstantCommitType type) {
614  if (!preview_contents_.get())
615    return NULL;
616
617  // FrameLoadObserver is only used for instant results, and instant results are
618  // only committed if active (when the FrameLoadObserver isn't installed).
619  DCHECK(type == INSTANT_COMMIT_DESTROY || !frame_load_observer_.get());
620
621  if (type != INSTANT_COMMIT_DESTROY && is_showing_instant()) {
622    SendDoneScript(preview_contents_.get(),
623                   user_text_,
624                   type == INSTANT_COMMIT_PRESSED_ENTER);
625  }
626  omnibox_bounds_ = gfx::Rect();
627  last_omnibox_bounds_ = gfx::Rect();
628  url_ = GURL();
629  user_text_.clear();
630  complete_suggested_text_.clear();
631  if (preview_contents_.get()) {
632    if (type != INSTANT_COMMIT_DESTROY)
633      preview_tab_contents_delegate_->CommitHistory();
634    // Destroy the paint observer.
635    // RenderWidgetHostView may be null during shutdown.
636    if (preview_contents_->GetRenderWidgetHostView()) {
637      preview_contents_->GetRenderWidgetHostView()->GetRenderWidgetHost()->
638          set_paint_observer(NULL);
639#if defined(OS_MACOSX)
640      preview_contents_->GetRenderWidgetHostView()->
641          SetTakesFocusOnlyOnMouseDown(false);
642#endif
643    }
644    preview_contents_->set_delegate(NULL);
645    preview_tab_contents_delegate_->Reset();
646    ready_ = false;
647  }
648  update_bounds_timer_.Stop();
649  return preview_contents_.release();
650}
651
652bool InstantLoader::ShouldCommitInstantOnMouseUp() {
653  return delegate_->ShouldCommitInstantOnMouseUp();
654}
655
656void InstantLoader::CommitInstantLoader() {
657  delegate_->CommitInstantLoader(this);
658}
659
660void InstantLoader::ClearTemplateURLID() {
661  // This should only be invoked for sites we thought supported instant.
662  DCHECK(template_url_id_);
663
664  // The frame load observer should have completed.
665  DCHECK(!frame_load_observer_.get());
666
667  // We shouldn't be ready.
668  DCHECK(!ready());
669
670  template_url_id_ = 0;
671  ShowPreview();
672}
673
674void InstantLoader::SetCompleteSuggestedText(
675    const string16& complete_suggested_text) {
676  ShowPreview();
677
678  if (complete_suggested_text == complete_suggested_text_)
679    return;
680
681  if (user_text_.compare(0, user_text_.size(), complete_suggested_text,
682                         0, user_text_.size())) {
683    // The user text no longer contains the suggested text, ignore it.
684    complete_suggested_text_.clear();
685    delegate_->SetSuggestedTextFor(this, string16());
686    return;
687  }
688
689  complete_suggested_text_ = complete_suggested_text;
690  delegate_->SetSuggestedTextFor(
691      this,
692      complete_suggested_text_.substr(user_text_.size()));
693}
694
695void InstantLoader::PreviewPainted() {
696  // If instant is supported then we wait for the first suggest result before
697  // showing the page.
698  if (!template_url_id_)
699    ShowPreview();
700}
701
702void InstantLoader::ShowPreview() {
703  if (!ready_) {
704    ready_ = true;
705#if defined(OS_MACOSX)
706    if (preview_contents_->GetRenderWidgetHostView()) {
707      preview_contents_->GetRenderWidgetHostView()->
708          SetTakesFocusOnlyOnMouseDown(true);
709    }
710#endif
711    delegate_->ShowInstantLoader(this);
712  }
713}
714
715void InstantLoader::PageFinishedLoading() {
716  frame_load_observer_.reset();
717  // Wait for the user input before showing, this way the page should be up to
718  // date by the time we show it.
719}
720
721gfx::Rect InstantLoader::GetOmniboxBoundsInTermsOfPreview() {
722  if (omnibox_bounds_.IsEmpty())
723    return omnibox_bounds_;
724
725  gfx::Rect preview_bounds(delegate_->GetInstantBounds());
726  return gfx::Rect(omnibox_bounds_.x() - preview_bounds.x(),
727                   omnibox_bounds_.y() - preview_bounds.y(),
728                   omnibox_bounds_.width(),
729                   omnibox_bounds_.height());
730}
731
732void InstantLoader::PageDoesntSupportInstant(bool needs_reload) {
733  GURL url_to_load = url_;
734
735  // Because we didn't process any of the requests to load in Update we're
736  // actually at initial_instant_url_. We need to reset url_ so that callers see
737  // the correct state.
738  url_ = initial_instant_url_;
739
740  frame_load_observer_.reset(NULL);
741
742  delegate_->InstantLoaderDoesntSupportInstant(this, needs_reload, url_to_load);
743}
744
745void InstantLoader::ProcessBoundsChange() {
746  if (last_omnibox_bounds_ == omnibox_bounds_)
747    return;
748
749  last_omnibox_bounds_ = omnibox_bounds_;
750  if (preview_contents_.get() && is_showing_instant() &&
751      !is_waiting_for_load()) {
752    SendOmniboxBoundsScript(preview_contents_.get(),
753                            GetOmniboxBoundsInTermsOfPreview());
754  }
755}
756