blob: d167ceb315b3b61f3cdbe9f03d4c8a156ed58b36 [file] [log] [blame]
[email protected]fd2b9ce2010-08-11 04:03:571// 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/tab_contents/match_preview.h"
6
7#include <algorithm>
8
9#include "base/command_line.h"
[email protected]03bb953d2010-09-14 21:38:3010#include "base/utf_string_conversions.h"
11#include "chrome/browser/autocomplete/autocomplete.h"
12#include "chrome/browser/favicon_service.h"
13#include "chrome/browser/history/history_marshaling.h"
14#include "chrome/browser/profile.h"
15#include "chrome/browser/renderer_host/render_view_host.h"
16#include "chrome/browser/renderer_host/render_widget_host.h"
17#include "chrome/browser/renderer_host/render_widget_host_view.h"
18#include "chrome/browser/search_engines/template_url.h"
19#include "chrome/browser/search_engines/template_url_model.h"
20#include "chrome/browser/tab_contents/match_preview_delegate.h"
[email protected]fd2b9ce2010-08-11 04:03:5721#include "chrome/browser/tab_contents/navigation_controller.h"
22#include "chrome/browser/tab_contents/navigation_entry.h"
23#include "chrome/browser/tab_contents/tab_contents.h"
24#include "chrome/browser/tab_contents/tab_contents_delegate.h"
[email protected]03bb953d2010-09-14 21:38:3025#include "chrome/browser/tab_contents/tab_contents_view.h"
[email protected]fd2b9ce2010-08-11 04:03:5726#include "chrome/common/chrome_switches.h"
[email protected]03bb953d2010-09-14 21:38:3027#include "chrome/common/notification_observer.h"
28#include "chrome/common/notification_registrar.h"
[email protected]fd2b9ce2010-08-11 04:03:5729#include "chrome/common/notification_service.h"
30#include "chrome/common/page_transition_types.h"
[email protected]03bb953d2010-09-14 21:38:3031#include "chrome/common/render_messages.h"
[email protected]fd2b9ce2010-08-11 04:03:5732#include "chrome/common/renderer_preferences.h"
[email protected]03bb953d2010-09-14 21:38:3033#include "gfx/codec/png_codec.h"
[email protected]fd2b9ce2010-08-11 04:03:5734#include "ipc/ipc_message.h"
35
[email protected]03bb953d2010-09-14 21:38:3036namespace {
37
38const char kUserInputScript[] =
39 "if (window.chrome.userInput) window.chrome.userInput(\"$1\");";
40
41// Sends the user input script to |tab_contents|. |text| is the text the user
42// input into the omnibox.
43void SendUserInputScript(TabContents* tab_contents,
44 const string16& text,
45 bool done) {
46 // TODO: support done.
47 string16 escaped_text(text);
48 ReplaceSubstringsAfterOffset(&escaped_text, 0L, ASCIIToUTF16("\""),
49 ASCIIToUTF16("\\\""));
50 string16 script = ReplaceStringPlaceholders(ASCIIToUTF16(kUserInputScript),
51 escaped_text, NULL);
52 tab_contents->render_view_host()->ExecuteJavascriptInWebFrame(
53 std::wstring(),
54 UTF16ToWide(script));
55}
56
57} // namespace
58
59// FrameLoadObserver is responsible for waiting for the TabContents to finish
60// loading and when done sending the necessary script down to the page.
61class MatchPreview::FrameLoadObserver : public NotificationObserver {
62 public:
63 FrameLoadObserver(MatchPreview* match_preview, const string16& text)
64 : match_preview_(match_preview),
65 tab_contents_(match_preview->preview_contents()),
66 unique_id_(tab_contents_->controller().pending_entry()->unique_id()),
67 text_(text),
68 send_done_(false) {
69 registrar_.Add(this, NotificationType::LOAD_COMPLETED_MAIN_FRAME,
70 Source<TabContents>(tab_contents_));
71 registrar_.Add(this, NotificationType::TAB_CONTENTS_DESTROYED,
72 Source<TabContents>(tab_contents_));
73 }
74
75 // Sets the text to send to the page.
76 void set_text(const string16& text) { text_ = text; }
77
78 // Invoked when the MatchPreview releases ownership of the TabContents and
79 // the page hasn't finished loading.
80 void DetachFromPreview() {
81 match_preview_ = NULL;
82 send_done_ = true;
83 }
84
85 // NotificationObserver:
86 virtual void Observe(NotificationType type,
87 const NotificationSource& source,
88 const NotificationDetails& details) {
89 switch (type.value) {
90 case NotificationType::LOAD_COMPLETED_MAIN_FRAME: {
91 int page_id = *(Details<int>(details).ptr());
92 NavigationEntry* active_entry =
93 tab_contents_->controller().GetActiveEntry();
94 if (!active_entry || active_entry->page_id() != page_id ||
95 active_entry->unique_id() != unique_id_) {
96 return;
97 }
98
99 SendUserInputScript(tab_contents_, text_, send_done_);
100
101 if (match_preview_)
102 match_preview_->PageFinishedLoading();
103
104 delete this;
105 return;
106 }
107
108 case NotificationType::TAB_CONTENTS_DESTROYED:
109 delete this;
110 return;
111
112 default:
113 NOTREACHED();
114 break;
115 }
116 }
117
118 private:
119 // MatchPreview that created us.
120 MatchPreview* match_preview_;
121
122 // The TabContents we're listening for changes on.
123 TabContents* tab_contents_;
124
125 // unique_id of the NavigationEntry we're waiting on.
126 const int unique_id_;
127
128 // Text to send down to the page.
129 string16 text_;
130
131 // Passed to SendScript.
132 bool send_done_;
133
134 // Registers and unregisters us for notifications.
135 NotificationRegistrar registrar_;
136
137 DISALLOW_COPY_AND_ASSIGN(FrameLoadObserver);
138};
139
140// PaintObserver implementation. When the RenderWidgetHost paints itself this
141// notifies MatchPreview, which makes the TabContents active.
142class MatchPreview::PaintObserverImpl : public RenderWidgetHost::PaintObserver {
143 public:
144 explicit PaintObserverImpl(MatchPreview* preview)
145 : match_preview_(preview) {
146 }
147
148 virtual void RenderWidgetHostWillPaint(RenderWidgetHost* rwh) {
149 }
150
151 virtual void RenderWidgetHostDidPaint(RenderWidgetHost* rwh) {
152 match_preview_->PreviewDidPaint();
153 rwh->set_paint_observer(NULL);
154 // WARNING: we've been deleted.
155 }
156
157 private:
158 MatchPreview* match_preview_;
159
160 DISALLOW_COPY_AND_ASSIGN(PaintObserverImpl);
161};
162
[email protected]fd2b9ce2010-08-11 04:03:57163class MatchPreview::TabContentsDelegateImpl : public TabContentsDelegate {
164 public:
165 explicit TabContentsDelegateImpl(MatchPreview* match_preview)
[email protected]03bb953d2010-09-14 21:38:30166 : match_preview_(match_preview),
167 installed_paint_observer_(false),
168 waiting_for_new_page_(true) {
169 }
170
171 // Invoked prior to loading a new URL.
172 void PrepareForNewLoad() {
173 waiting_for_new_page_ = true;
174 add_page_vector_.clear();
175 }
176
177 // Invoked when removed as the delegate. Gives a chance to do any necessary
178 // cleanup.
179 void Reset() {
180 installed_paint_observer_ = false;
181 }
182
183 // Commits the currently buffered history.
184 void CommitHistory() {
185 TabContents* tab = match_preview_->preview_contents();
186 if (tab->profile()->IsOffTheRecord())
187 return;
188
189 for (size_t i = 0; i < add_page_vector_.size(); ++i)
190 tab->UpdateHistoryForNavigation(add_page_vector_[i].get());
191
192 NavigationEntry* active_entry = tab->controller().GetActiveEntry();
193 DCHECK(active_entry);
194 tab->UpdateHistoryPageTitle(*active_entry);
195
196 FaviconService* favicon_service =
197 tab->profile()->GetFaviconService(Profile::EXPLICIT_ACCESS);
198
199 if (favicon_service && active_entry->favicon().is_valid() &&
200 !active_entry->favicon().bitmap().empty()) {
201 std::vector<unsigned char> image_data;
202 gfx::PNGCodec::EncodeBGRASkBitmap(active_entry->favicon().bitmap(), false,
203 &image_data);
204 favicon_service->SetFavicon(active_entry->url(),
205 active_entry->favicon().url(),
206 image_data);
207 }
[email protected]fd2b9ce2010-08-11 04:03:57208 }
209
210 virtual void OpenURLFromTab(TabContents* source,
211 const GURL& url, const GURL& referrer,
212 WindowOpenDisposition disposition,
213 PageTransition::Type transition) {}
214 virtual void NavigationStateChanged(const TabContents* source,
[email protected]03bb953d2010-09-14 21:38:30215 unsigned changed_flags) {
216 if (!installed_paint_observer_ && source->controller().entry_count()) {
217 // The load has been committed. Install an observer that waits for the
218 // first paint then makes the preview active. We wait for the load to be
219 // committed before waiting on paint as there is always an initial paint
220 // when a new renderer is created from the resize so that if we showed the
221 // preview after the first paint we would end up with a white rect.
222 installed_paint_observer_ = true;
223 source->GetRenderWidgetHostView()->GetRenderWidgetHost()->
224 set_paint_observer(new PaintObserverImpl(match_preview_));
225 }
226 }
[email protected]fd2b9ce2010-08-11 04:03:57227 virtual void AddNewContents(TabContents* source,
228 TabContents* new_contents,
229 WindowOpenDisposition disposition,
230 const gfx::Rect& initial_pos,
231 bool user_gesture) {}
232 virtual void ActivateContents(TabContents* contents) {
[email protected]fd2b9ce2010-08-11 04:03:57233 }
[email protected]ea42e7782010-08-23 23:58:12234 virtual void DeactivateContents(TabContents* contents) {}
[email protected]fd2b9ce2010-08-11 04:03:57235 virtual void LoadingStateChanged(TabContents* source) {}
236 virtual void CloseContents(TabContents* source) {}
237 virtual void MoveContents(TabContents* source, const gfx::Rect& pos) {}
238 virtual void DetachContents(TabContents* source) {}
239 virtual bool IsPopup(const TabContents* source) const {
240 return false;
241 }
242 virtual TabContents* GetConstrainingContents(TabContents* source) {
243 return NULL;
244 }
245 virtual void ToolbarSizeChanged(TabContents* source, bool is_animating) {}
246 virtual void URLStarredChanged(TabContents* source, bool starred) {}
247 virtual void UpdateTargetURL(TabContents* source, const GURL& url) {}
248 virtual void ContentsMouseEvent(
249 TabContents* source, const gfx::Point& location, bool motion) {}
250 virtual void ContentsZoomChange(bool zoom_in) {}
251 virtual void OnContentSettingsChange(TabContents* source) {}
252 virtual bool IsApplication() const { return false; }
253 virtual void ConvertContentsToApplication(TabContents* source) {}
254 virtual bool CanReloadContents(TabContents* source) const { return true; }
255 virtual gfx::Rect GetRootWindowResizerRect() const {
[email protected]03bb953d2010-09-14 21:38:30256 return gfx::Rect();
[email protected]fd2b9ce2010-08-11 04:03:57257 }
258 virtual void ShowHtmlDialog(HtmlDialogUIDelegate* delegate,
259 gfx::NativeWindow parent_window) {}
260 virtual void BeforeUnloadFired(TabContents* tab,
261 bool proceed,
262 bool* proceed_to_fire_unload) {}
263 virtual void ForwardMessageToExternalHost(const std::string& message,
264 const std::string& origin,
265 const std::string& target) {}
266 virtual bool IsExternalTabContainer() const { return false; }
267 virtual void SetFocusToLocationBar(bool select_all) {}
268 virtual void RenderWidgetShowing() {}
269 virtual ExtensionFunctionDispatcher* CreateExtensionFunctionDispatcher(
270 RenderViewHost* render_view_host,
271 const std::string& extension_id) {
272 return NULL;
273 }
274 virtual bool TakeFocus(bool reverse) { return false; }
275 virtual void SetTabContentBlocked(TabContents* contents, bool blocked) {}
276 virtual void TabContentsFocused(TabContents* tab_content) {
[email protected]fd2b9ce2010-08-11 04:03:57277 }
278 virtual int GetExtraRenderViewHeight() const { return 0; }
279 virtual bool CanDownload(int request_id) { return false; }
280 virtual void OnStartDownload(DownloadItem* download, TabContents* tab) {}
281 virtual bool HandleContextMenu(const ContextMenuParams& params) {
282 return false;
283 }
284 virtual bool ExecuteContextMenuCommand(int command) {
285 return false;
286 }
287 virtual void ConfirmAddSearchProvider(const TemplateURL* template_url,
288 Profile* profile) {}
289 virtual void ShowPageInfo(Profile* profile,
290 const GURL& url,
291 const NavigationEntry::SSLStatus& ssl,
292 bool show_history) {}
293 virtual bool PreHandleKeyboardEvent(const NativeWebKeyboardEvent& event,
294 bool* is_keyboard_shortcut) {
295 return false;
296 }
297 virtual void HandleKeyboardEvent(const NativeWebKeyboardEvent& event) {}
298 virtual void ShowRepostFormWarningDialog(TabContents* tab_contents) {}
299 virtual void ShowContentSettingsWindow(ContentSettingsType content_type) {}
300 virtual void ShowCollectedCookiesDialog(TabContents* tab_contents) {}
301 virtual bool OnGoToEntryOffset(int offset) { return false; }
[email protected]03bb953d2010-09-14 21:38:30302 virtual bool ShouldAddNavigationsToHistory(
[email protected]ec0b6c42010-08-26 03:16:58303 const history::HistoryAddPageArgs& add_page_args,
304 NavigationType::Type navigation_type) {
[email protected]03bb953d2010-09-14 21:38:30305 if (waiting_for_new_page_ && navigation_type == NavigationType::NEW_PAGE)
306 waiting_for_new_page_ = false;
307
308 if (!waiting_for_new_page_) {
309 add_page_vector_.push_back(
310 scoped_refptr<history::HistoryAddPageArgs>(add_page_args.Clone()));
311 }
[email protected]ec0b6c42010-08-26 03:16:58312 return false;
313 }
[email protected]fd2b9ce2010-08-11 04:03:57314 virtual void OnDidGetApplicationInfo(TabContents* tab_contents,
315 int32 page_id) {}
[email protected]fd2b9ce2010-08-11 04:03:57316 virtual gfx::NativeWindow GetFrameNativeWindow() {
[email protected]03bb953d2010-09-14 21:38:30317 return NULL;
[email protected]fd2b9ce2010-08-11 04:03:57318 }
319 virtual void TabContentsCreated(TabContents* new_contents) {}
320 virtual bool infobars_enabled() { return false; }
321 virtual bool ShouldEnablePreferredSizeNotifications() { return false; }
322 virtual void UpdatePreferredSize(const gfx::Size& pref_size) {}
323 virtual void ContentTypeChanged(TabContents* source) {}
324
[email protected]03bb953d2010-09-14 21:38:30325 virtual void OnSetSuggestResult(int32 page_id, const std::string& result) {
326 TabContents* source = match_preview_->preview_contents();
327 // TODO: only allow for default search provider.
328 if (source->controller().GetActiveEntry() &&
329 page_id == source->controller().GetActiveEntry()->page_id()) {
330 match_preview_->SetCompleteSuggestedText(UTF8ToUTF16(result));
331 }
332 }
333
[email protected]fd2b9ce2010-08-11 04:03:57334 private:
[email protected]03bb953d2010-09-14 21:38:30335 typedef std::vector<scoped_refptr<history::HistoryAddPageArgs> >
336 AddPageVector;
337
[email protected]fd2b9ce2010-08-11 04:03:57338 MatchPreview* match_preview_;
339
[email protected]03bb953d2010-09-14 21:38:30340 // Has the paint observer been installed? See comment in
341 // NavigationStateChanged for details on this.
342 bool installed_paint_observer_;
343
344 // Used to cache data that needs to be added to history. Normally entries are
345 // added to history as the user types, but for match preview we only want to
346 // add the items to history if the user commits the match preview. So, we
347 // cache them here and if committed then add the items to history.
348 AddPageVector add_page_vector_;
349
350 // Are we we waiting for a NavigationType of NEW_PAGE? If we're waiting for
351 // NEW_PAGE navigation we don't add history items to add_page_vector_.
352 bool waiting_for_new_page_;
353
[email protected]fd2b9ce2010-08-11 04:03:57354 DISALLOW_COPY_AND_ASSIGN(TabContentsDelegateImpl);
355};
356
[email protected]fd2b9ce2010-08-11 04:03:57357// static
358bool MatchPreview::IsEnabled() {
359 static bool enabled = false;
360 static bool checked = false;
361 if (!checked) {
362 checked = true;
363 enabled = CommandLine::ForCurrentProcess()->HasSwitch(
364 switches::kEnableMatchPreview);
365 }
366 return enabled;
367}
368
[email protected]03bb953d2010-09-14 21:38:30369MatchPreview::MatchPreview(MatchPreviewDelegate* delegate)
370 : delegate_(delegate),
371 tab_contents_(NULL),
372 is_active_(false),
373 template_url_id_(0) {
374 preview_tab_contents_delegate_.reset(new TabContentsDelegateImpl(this));
375}
376
377MatchPreview::~MatchPreview() {
378 // Delete the TabContents before the delegate as the TabContents holds a
379 // reference to the delegate.
380 preview_contents_.reset(NULL);
381}
382
383void MatchPreview::Update(TabContents* tab_contents,
384 const AutocompleteMatch& match,
385 const string16& user_text,
386 string16* suggested_text) {
387 if (tab_contents != tab_contents_)
388 DestroyPreviewContents();
389
390 tab_contents_ = tab_contents;
391
392 if (url_ == match.destination_url)
[email protected]fd2b9ce2010-08-11 04:03:57393 return;
394
[email protected]03bb953d2010-09-14 21:38:30395 url_ = match.destination_url;
[email protected]fd2b9ce2010-08-11 04:03:57396
397 if (url_.is_empty() || !url_.is_valid()) {
398 DestroyPreviewContents();
399 return;
400 }
401
[email protected]03bb953d2010-09-14 21:38:30402 user_text_ = user_text;
403
404 if (preview_contents_.get() == NULL) {
[email protected]fd2b9ce2010-08-11 04:03:57405 preview_contents_.reset(
[email protected]03bb953d2010-09-14 21:38:30406 new TabContents(tab_contents_->profile(), NULL, MSG_ROUTING_NONE,
407 NULL, NULL));
408 // Propagate the max page id. That way if we end up merging the two
409 // NavigationControllers (which happens if we commit) none of the page ids
410 // will overlap.
411 int32 max_page_id = tab_contents_->GetMaxPageID();
412 if (max_page_id != -1)
413 preview_contents_->controller().set_max_restored_page_id(max_page_id + 1);
414
415 preview_contents_->set_delegate(preview_tab_contents_delegate_.get());
416
417 gfx::Rect tab_bounds;
418 tab_contents_->view()->GetContainerBounds(&tab_bounds);
419 preview_contents_->view()->SizeContents(tab_bounds.size());
420
421 preview_contents_->ShowContents();
422 }
423 preview_tab_contents_delegate_->PrepareForNewLoad();
424
425 const TemplateURL* template_url = match.template_url;
426 if (match.type == AutocompleteMatch::SEARCH_WHAT_YOU_TYPED ||
427 match.type == AutocompleteMatch::SEARCH_HISTORY ||
428 match.type == AutocompleteMatch::SEARCH_SUGGEST) {
429 TemplateURLModel* model = tab_contents->profile()->GetTemplateURLModel();
430 template_url = model ? model->GetDefaultSearchProvider() : NULL;
431 }
432 TemplateURLID template_url_id = template_url ? template_url->id() : 0;
433
434 if (template_url && template_url->supports_instant() &&
435 TemplateURL::SupportsReplacement(template_url)) {
436 if (template_url_id == template_url_id_) {
437 if (frame_load_observer_.get()) {
438 // The page hasn't loaded yet. We'll send the script down when it does.
439 frame_load_observer_->set_text(user_text_);
440 return;
441 }
442 SendUserInputScript(preview_contents_.get(), user_text_, false);
443 if (complete_suggested_text_.size() > user_text_.size() &&
444 !complete_suggested_text_.compare(0, user_text_.size(), user_text_)) {
445 *suggested_text = complete_suggested_text_.substr(user_text_.size());
446 }
447 } else {
448 // TODO: should we use a different url for instant?
449 GURL url = GURL(template_url->url()->ReplaceSearchTerms(
450 *template_url, std::wstring(),
451 TemplateURLRef::NO_SUGGESTIONS_AVAILABLE, std::wstring()));
452 // user_text_ is sent once the page finishes loading by FrameLoadObserver.
453 preview_contents_->controller().LoadURL(url, GURL(), match.transition);
454 frame_load_observer_.reset(new FrameLoadObserver(this, user_text_));
455 }
456 } else {
457 frame_load_observer_.reset(NULL);
458 preview_contents_->controller().LoadURL(url_, GURL(), match.transition);
[email protected]fd2b9ce2010-08-11 04:03:57459 }
460
[email protected]03bb953d2010-09-14 21:38:30461 template_url_id_ = template_url_id;
[email protected]fd2b9ce2010-08-11 04:03:57462}
463
464void MatchPreview::DestroyPreviewContents() {
[email protected]03bb953d2010-09-14 21:38:30465 delegate_->HideMatchPreview();
466 delete ReleasePreviewContents(false);
[email protected]fd2b9ce2010-08-11 04:03:57467}
468
469void MatchPreview::CommitCurrentPreview() {
470 DCHECK(preview_contents_.get());
[email protected]03bb953d2010-09-14 21:38:30471 delegate_->CommitMatchPreview();
[email protected]fd2b9ce2010-08-11 04:03:57472}
473
[email protected]03bb953d2010-09-14 21:38:30474TabContents* MatchPreview::ReleasePreviewContents(bool commit_history) {
475 template_url_id_ = 0;
[email protected]fd2b9ce2010-08-11 04:03:57476 url_ = GURL();
[email protected]03bb953d2010-09-14 21:38:30477 user_text_.clear();
478 complete_suggested_text_.clear();
479 if (frame_load_observer_.get()) {
480 frame_load_observer_->DetachFromPreview();
481 // FrameLoadObserver will delete itself either when the TabContents is
482 // deleted, or when the page finishes loading.
483 FrameLoadObserver* unused ALLOW_UNUSED = frame_load_observer_.release();
484 }
485 if (preview_contents_.get()) {
486 if (commit_history)
487 preview_tab_contents_delegate_->CommitHistory();
488 // Destroy the paint observer.
489 if (preview_contents_->GetRenderWidgetHostView()) {
490 // RenderWidgetHostView may be null during shutdown.
491 preview_contents_->GetRenderWidgetHostView()->GetRenderWidgetHost()->
492 set_paint_observer(NULL);
493 }
494 preview_contents_->set_delegate(NULL);
495 preview_tab_contents_delegate_->Reset();
496 is_active_ = false;
497 }
[email protected]fd2b9ce2010-08-11 04:03:57498 return preview_contents_.release();
499}
[email protected]03bb953d2010-09-14 21:38:30500
501void MatchPreview::SetCompleteSuggestedText(
502 const string16& complete_suggested_text) {
503 if (complete_suggested_text == complete_suggested_text_)
504 return;
505
506 if (user_text_.compare(0, user_text_.size(), complete_suggested_text,
507 0, user_text_.size())) {
508 // The user text no longer contains the suggested text, ignore it.
509 complete_suggested_text_.clear();
510 delegate_->SetSuggestedText(string16());
511 return;
512 }
513
514 complete_suggested_text_ = complete_suggested_text;
515 delegate_->SetSuggestedText(
516 complete_suggested_text_.substr(user_text_.size()));
517}
518
519void MatchPreview::PreviewDidPaint() {
520 DCHECK(!is_active_);
521 is_active_ = true;
522 delegate_->ShowMatchPreview();
523}
524
525void MatchPreview::PageFinishedLoading() {
526 // FrameLoadObserver deletes itself after this call.
527 FrameLoadObserver* unused ALLOW_UNUSED = frame_load_observer_.release();
528}