blob: d20720f278b8fdc1d97ae4097f7dc3bd3ef50dfa [file] [log] [blame]
// Copyright (c) 2012 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 "chrome/browser/content_settings/tab_specific_content_settings.h"
#include "chrome/browser/instant/instant_loader_delegate.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/tab_contents/thumbnail_generator.h"
#include "chrome/browser/ui/blocked_content/blocked_content_tab_helper.h"
#include "chrome/browser/ui/constrained_window_tab_helper.h"
#include "chrome/browser/ui/constrained_window_tab_helper_delegate.h"
#include "chrome/browser/ui/tab_contents/core_tab_helper.h"
#include "chrome/browser/ui/tab_contents/core_tab_helper_delegate.h"
#include "chrome/browser/ui/tab_contents/tab_contents.h"
#include "chrome/common/render_messages.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/notification_source.h"
#include "content/public/browser/notification_types.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_delegate.h"
namespace {
int kUserDataKey;
class InstantLoaderUserData : public base::SupportsUserData::Data {
public:
explicit InstantLoaderUserData(InstantLoader* loader) : loader_(loader) {}
virtual ~InstantLoaderUserData() {}
InstantLoader* loader() const { return loader_; }
private:
InstantLoader* loader_;
DISALLOW_COPY_AND_ASSIGN(InstantLoaderUserData);
};
}
// WebContentsDelegateImpl -----------------------------------------------------
class InstantLoader::WebContentsDelegateImpl
: public ConstrainedWindowTabHelperDelegate,
public CoreTabHelperDelegate,
public content::WebContentsDelegate,
public content::WebContentsObserver {
public:
explicit WebContentsDelegateImpl(InstantLoader* loader);
bool is_pointer_down_from_activate() const {
return is_pointer_down_from_activate_;
}
// ConstrainedWindowTabHelperDelegate:
virtual bool ShouldFocusConstrainedWindow() OVERRIDE;
// CoreTabHelperDelegate:
virtual void SwapTabContents(content::WebContents* old_contents,
content::WebContents* new_contents) OVERRIDE;
// content::WebContentsDelegate:
virtual bool ShouldSuppressDialogs() OVERRIDE;
virtual bool ShouldFocusPageAfterCrash() OVERRIDE;
virtual void LostCapture() OVERRIDE;
virtual void WebContentsFocused(content::WebContents* contents) OVERRIDE;
virtual bool CanDownload(content::RenderViewHost* render_view_host,
int request_id,
const std::string& request_method) OVERRIDE;
virtual void HandleMouseUp() OVERRIDE;
virtual void HandlePointerActivate() OVERRIDE;
virtual void HandleGestureBegin() OVERRIDE;
virtual void HandleGestureEnd() OVERRIDE;
virtual void DragEnded() OVERRIDE;
virtual bool OnGoToEntryOffset(int offset) OVERRIDE;
// content::WebContentsObserver:
virtual void DidFinishLoad(
int64 frame_id,
const GURL& validated_url,
bool is_main_frame,
content::RenderViewHost* render_view_host) OVERRIDE;
virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
private:
// Message from renderer indicating the page has suggestions.
void OnSetSuggestions(int page_id,
const std::vector<InstantSuggestion>& suggestions);
// Message from the renderer determining whether it supports the Instant API.
void OnInstantSupportDetermined(int page_id, bool result);
// Message from the renderer requesting the preview be resized.
void OnSetInstantPreviewHeight(int page_id,
int height,
InstantSizeUnits units);
void CommitFromPointerReleaseIfNecessary();
void MaybeSetAndNotifyInstantSupportDetermined(bool supports_instant);
InstantLoader* const loader_;
// True if the mouse or a touch pointer is down from an activate.
bool is_pointer_down_from_activate_;
DISALLOW_COPY_AND_ASSIGN(WebContentsDelegateImpl);
};
InstantLoader::WebContentsDelegateImpl::WebContentsDelegateImpl(
InstantLoader* loader)
: content::WebContentsObserver(loader->preview_contents_->web_contents()),
loader_(loader),
is_pointer_down_from_activate_(false) {
}
bool InstantLoader::WebContentsDelegateImpl::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::WebContentsDelegateImpl::SwapTabContents(
content::WebContents* old_contents,
content::WebContents* new_contents) {
// If this is being called, something is swapping in to our
// |preview_contents_| before we've added it to the tab strip.
loader_->ReplacePreviewContents(old_contents, new_contents);
}
bool InstantLoader::WebContentsDelegateImpl::ShouldSuppressDialogs() {
// Any message shown during Instant cancels Instant, so we suppress them.
return true;
}
bool InstantLoader::WebContentsDelegateImpl::ShouldFocusPageAfterCrash() {
return false;
}
void InstantLoader::WebContentsDelegateImpl::LostCapture() {
CommitFromPointerReleaseIfNecessary();
}
void InstantLoader::WebContentsDelegateImpl::WebContentsFocused(
content::WebContents* contents) {
loader_->loader_delegate_->InstantLoaderContentsFocused(loader_);
}
bool InstantLoader::WebContentsDelegateImpl::CanDownload(
content::RenderViewHost* render_view_host,
int request_id,
const std::string& request_method) {
// Downloads are disabled.
return false;
}
void InstantLoader::WebContentsDelegateImpl::HandleMouseUp() {
CommitFromPointerReleaseIfNecessary();
}
void InstantLoader::WebContentsDelegateImpl::HandlePointerActivate() {
is_pointer_down_from_activate_ = true;
}
void InstantLoader::WebContentsDelegateImpl::HandleGestureBegin() {
}
void InstantLoader::WebContentsDelegateImpl::HandleGestureEnd() {
CommitFromPointerReleaseIfNecessary();
}
void InstantLoader::WebContentsDelegateImpl::DragEnded() {
// 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.
CommitFromPointerReleaseIfNecessary();
}
bool InstantLoader::WebContentsDelegateImpl::OnGoToEntryOffset(int offset) {
return false;
}
void InstantLoader::WebContentsDelegateImpl::DidFinishLoad(
int64 frame_id,
const GURL& validated_url,
bool is_main_frame,
content::RenderViewHost* render_view_host) {
if (is_main_frame) {
if (!loader_->supports_instant_)
Send(new ChromeViewMsg_DetermineIfPageSupportsInstant(routing_id()));
loader_->loader_delegate_->InstantLoaderPreviewLoaded(loader_);
}
}
bool InstantLoader::WebContentsDelegateImpl::OnMessageReceived(
const IPC::Message& message) {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(WebContentsDelegateImpl, message)
IPC_MESSAGE_HANDLER(ChromeViewHostMsg_SetSuggestions, OnSetSuggestions)
IPC_MESSAGE_HANDLER(ChromeViewHostMsg_InstantSupportDetermined,
OnInstantSupportDetermined)
IPC_MESSAGE_HANDLER(ChromeViewHostMsg_SetInstantPreviewHeight,
OnSetInstantPreviewHeight);
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
}
void InstantLoader::WebContentsDelegateImpl::OnSetSuggestions(
int page_id,
const std::vector<InstantSuggestion>& suggestions) {
DCHECK(loader_->preview_contents() &&
loader_->preview_contents_->web_contents());
// TODO(sreeram): Remove this 'if' bandaid once bug 141875 is confirmed fixed.
if (!loader_->preview_contents() ||
!loader_->preview_contents_->web_contents())
return;
content::NavigationEntry* entry = loader_->preview_contents_->web_contents()->
GetController().GetActiveEntry();
if (entry && page_id == entry->GetPageID()) {
MaybeSetAndNotifyInstantSupportDetermined(true);
loader_->loader_delegate_->SetSuggestions(loader_, suggestions);
}
}
void InstantLoader::WebContentsDelegateImpl::OnInstantSupportDetermined(
int page_id,
bool result) {
DCHECK(loader_->preview_contents() &&
loader_->preview_contents_->web_contents());
// TODO(sreeram): Remove this 'if' bandaid once bug 141875 is confirmed fixed.
if (!loader_->preview_contents() ||
!loader_->preview_contents_->web_contents())
return;
content::NavigationEntry* entry = loader_->preview_contents_->web_contents()->
GetController().GetActiveEntry();
if (entry && page_id == entry->GetPageID())
MaybeSetAndNotifyInstantSupportDetermined(result);
}
void InstantLoader::WebContentsDelegateImpl::OnSetInstantPreviewHeight(
int page_id,
int height,
InstantSizeUnits units) {
DCHECK(loader_->preview_contents() &&
loader_->preview_contents_->web_contents());
// TODO(sreeram): Remove this 'if' bandaid once bug 141875 is confirmed fixed.
if (!loader_->preview_contents() ||
!loader_->preview_contents_->web_contents())
return;
content::NavigationEntry* entry = loader_->preview_contents_->web_contents()->
GetController().GetActiveEntry();
if (entry && page_id == entry->GetPageID()) {
MaybeSetAndNotifyInstantSupportDetermined(true);
loader_->loader_delegate_->SetInstantPreviewHeight(loader_, height, units);
}
}
void InstantLoader::WebContentsDelegateImpl
::CommitFromPointerReleaseIfNecessary() {
if (is_pointer_down_from_activate_) {
is_pointer_down_from_activate_ = false;
loader_->loader_delegate_->CommitInstantLoader(loader_);
}
}
void InstantLoader::WebContentsDelegateImpl
::MaybeSetAndNotifyInstantSupportDetermined(bool supports_instant) {
// If we already determined that the loader supports Instant, nothing to do.
if (loader_->supports_instant_)
return;
// If the page doesn't support the Instant API, InstantController schedules
// the loader for destruction. Stop sending the controller any more messages,
// by severing the connection from the WebContents to us (its delegate).
if (!supports_instant) {
loader_->preview_contents_->web_contents()->SetDelegate(NULL);
Observe(NULL);
}
loader_->supports_instant_ = supports_instant;
loader_->loader_delegate_->InstantSupportDetermined(loader_,
supports_instant);
}
// InstantLoader ---------------------------------------------------------------
// static
InstantLoader* InstantLoader::FromWebContents(
content::WebContents* web_contents) {
InstantLoaderUserData* data = static_cast<InstantLoaderUserData*>(
web_contents->GetUserData(&kUserDataKey));
return data ? data->loader() : NULL;
}
InstantLoader::InstantLoader(InstantLoaderDelegate* delegate,
const std::string& instant_url,
const TabContents* tab_contents)
: loader_delegate_(delegate),
preview_contents_(
TabContents::Factory::CreateTabContents(
content::WebContents::CreateWithSessionStorage(
tab_contents->profile(), NULL, MSG_ROUTING_NONE,
tab_contents->web_contents(),
tab_contents->web_contents()->GetController().
GetSessionStorageNamespaceMap()))),
supports_instant_(false),
instant_url_(instant_url) {
}
InstantLoader::~InstantLoader() {
if (preview_contents())
preview_contents_->web_contents()->SetDelegate(NULL);
}
void InstantLoader::Init() {
SetupPreviewContents();
// This HTTP header and value are set on loads that originate from instant.
const char* const kInstantHeader = "X-Purpose: Instant";
preview_contents_->web_contents()->GetController().LoadURL(GURL(instant_url_),
content::Referrer(), content::PAGE_TRANSITION_GENERATED, kInstantHeader);
preview_contents_->web_contents()->WasHidden();
}
void InstantLoader::Update(const string16& user_text, bool verbatim) {
// TODO: Support real cursor position.
last_navigation_ = history::HistoryAddPageArgs();
content::RenderViewHost* rvh =
preview_contents_->web_contents()->GetRenderViewHost();
rvh->Send(new ChromeViewMsg_SearchBoxChange(rvh->GetRoutingID(), user_text,
verbatim, user_text.size(), user_text.size()));
}
void InstantLoader::SetOmniboxBounds(const gfx::Rect& bounds) {
content::RenderViewHost* rvh =
preview_contents_->web_contents()->GetRenderViewHost();
rvh->Send(new ChromeViewMsg_SearchBoxResize(rvh->GetRoutingID(), bounds));
}
void InstantLoader::SendAutocompleteResults(
const std::vector<InstantAutocompleteResult>& results) {
content::RenderViewHost* rvh =
preview_contents_->web_contents()->GetRenderViewHost();
rvh->Send(new ChromeViewMsg_SearchBoxAutocompleteResults(rvh->GetRoutingID(),
results));
}
void InstantLoader::OnUpOrDownKeyPressed(int count) {
content::RenderViewHost* rvh =
preview_contents_->web_contents()->GetRenderViewHost();
rvh->Send(new ChromeViewMsg_SearchBoxUpOrDownKeyPressed(rvh->GetRoutingID(),
count));
}
void InstantLoader::DidNavigate(
const history::HistoryAddPageArgs& add_page_args) {
last_navigation_ = add_page_args;
}
TabContents* InstantLoader::ReleasePreviewContents(InstantCommitType type,
const string16& text) {
content::RenderViewHost* rvh =
preview_contents_->web_contents()->GetRenderViewHost();
if (type == INSTANT_COMMIT_PRESSED_ENTER)
rvh->Send(new ChromeViewMsg_SearchBoxSubmit(rvh->GetRoutingID(), text));
else
rvh->Send(new ChromeViewMsg_SearchBoxCancel(rvh->GetRoutingID(), text));
CleanupPreviewContents();
preview_contents_->web_contents()->RemoveUserData(&kUserDataKey);
return preview_contents_.release();
}
bool InstantLoader::IsPointerDownFromActivate() const {
return preview_delegate_->is_pointer_down_from_activate();
}
void InstantLoader::Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
#if defined(OS_MACOSX)
if (type == content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED) {
if (content::RenderWidgetHostView* rwhv =
preview_contents_->web_contents()->GetRenderWidgetHostView())
rwhv->SetTakesFocusOnlyOnMouseDown(true);
return;
}
NOTREACHED();
#endif
}
void InstantLoader::SetupPreviewContents() {
content::WebContents* new_contents = preview_contents_->web_contents();
new_contents->SetUserData(&kUserDataKey, new InstantLoaderUserData(this));
preview_delegate_.reset(new WebContentsDelegateImpl(this));
WebContentsDelegateImpl* new_delegate = preview_delegate_.get();
new_contents->SetDelegate(new_delegate);
// Disable popups and such (mainly to avoid losing focus and reverting the
// preview prematurely).
BlockedContentTabHelper::FromWebContents(new_contents)->
SetAllContentsBlocked(true);
preview_contents_->constrained_window_tab_helper()->
set_delegate(new_delegate);
preview_contents_->content_settings()->SetPopupsBlocked(true);
CoreTabHelper::FromWebContents(new_contents)->set_delegate(new_delegate);
if (ThumbnailGenerator* tg = preview_contents_->thumbnail_generator())
tg->set_enabled(false);
#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 (content::RenderWidgetHostView* rwhv =
new_contents->GetRenderWidgetHostView())
rwhv->SetTakesFocusOnlyOnMouseDown(true);
registrar_.Add(this, content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED,
content::Source<content::NavigationController>(
&new_contents->GetController()));
#endif
}
void InstantLoader::CleanupPreviewContents() {
content::WebContents* old_contents = preview_contents_->web_contents();
old_contents->SetDelegate(NULL);
preview_delegate_.reset();
BlockedContentTabHelper::FromWebContents(old_contents)->
SetAllContentsBlocked(false);
preview_contents_->constrained_window_tab_helper()->set_delegate(NULL);
preview_contents_->content_settings()->SetPopupsBlocked(false);
CoreTabHelper::FromWebContents(old_contents)->set_delegate(NULL);
if (ThumbnailGenerator* tg = preview_contents_->thumbnail_generator())
tg->set_enabled(true);
#if defined(OS_MACOSX)
if (content::RenderWidgetHostView* rwhv =
old_contents->GetRenderWidgetHostView())
rwhv->SetTakesFocusOnlyOnMouseDown(false);
registrar_.Remove(this, content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED,
content::Source<content::NavigationController>(
&old_contents->GetController()));
#endif
}
void InstantLoader::ReplacePreviewContents(content::WebContents* old_contents,
content::WebContents* new_contents) {
DCHECK(old_contents == preview_contents_->web_contents());
CleanupPreviewContents();
// We release here without deleting so that the caller still has the
// responsibility for deleting the TabContents.
ignore_result(preview_contents_.release());
preview_contents_.reset(TabContents::FromWebContents(new_contents));
SetupPreviewContents();
loader_delegate_->SwappedTabContents(this);
}