// 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/tab_contents/web_drop_target_win.h"
#include <windows.h>
#include <shlobj.h>
#include "chrome/browser/bookmarks/bookmark_node_data.h"
#include "chrome/browser/tab_contents/web_drag_utils_win.h"
#include "content/browser/renderer_host/render_view_host.h"
#include "content/browser/tab_contents/tab_contents.h"
#include "googleurl/src/gurl.h"
#include "net/base/net_util.h"
#include "ui/base/clipboard/clipboard_util_win.h"
#include "ui/base/dragdrop/os_exchange_data.h"
#include "ui/base/dragdrop/os_exchange_data_provider_win.h"
#include "ui/gfx/point.h"
#include "webkit/glue/webdropdata.h"
#include "webkit/glue/window_open_disposition.h"
using WebKit::WebDragOperationNone;
using WebKit::WebDragOperationCopy;
using WebKit::WebDragOperationLink;
using WebKit::WebDragOperationMove;
using WebKit::WebDragOperationGeneric;
namespace {
// A helper method for getting the preferred drop effect.
DWORD GetPreferredDropEffect(DWORD effect) {
if (effect & DROPEFFECT_COPY)
return DROPEFFECT_COPY;
if (effect & DROPEFFECT_LINK)
return DROPEFFECT_LINK;
if (effect & DROPEFFECT_MOVE)
return DROPEFFECT_MOVE;
return DROPEFFECT_NONE;
}
} // namespace
// InterstitialDropTarget is like a app::win::DropTarget implementation that
// WebDropTarget passes through to if an interstitial is showing. Rather than
// passing messages on to the renderer, we just check to see if there's a link
// in the drop data and handle links as navigations.
class InterstitialDropTarget {
public:
explicit InterstitialDropTarget(TabContents* tab_contents)
: tab_contents_(tab_contents) {}
DWORD OnDragEnter(IDataObject* data_object, DWORD effect) {
return ui::ClipboardUtil::HasUrl(data_object) ?
GetPreferredDropEffect(effect) : DROPEFFECT_NONE;
}
DWORD OnDragOver(IDataObject* data_object, DWORD effect) {
return ui::ClipboardUtil::HasUrl(data_object) ?
GetPreferredDropEffect(effect) : DROPEFFECT_NONE;
}
void OnDragLeave(IDataObject* data_object) {
}
DWORD OnDrop(IDataObject* data_object, DWORD effect) {
if (ui::ClipboardUtil::HasUrl(data_object)) {
std::wstring url;
std::wstring title;
ui::ClipboardUtil::GetUrl(data_object, &url, &title, true);
tab_contents_->OpenURL(GURL(url), GURL(), CURRENT_TAB,
PageTransition::AUTO_BOOKMARK);
return GetPreferredDropEffect(effect);
}
return DROPEFFECT_NONE;
}
private:
TabContents* tab_contents_;
DISALLOW_COPY_AND_ASSIGN(InterstitialDropTarget);
};
WebDropTarget::WebDropTarget(HWND source_hwnd, TabContents* tab_contents)
: ui::DropTarget(source_hwnd),
tab_contents_(tab_contents),
current_rvh_(NULL),
drag_cursor_(WebDragOperationNone),
interstitial_drop_target_(new InterstitialDropTarget(tab_contents)) {
}
WebDropTarget::~WebDropTarget() {
}
DWORD WebDropTarget::OnDragEnter(IDataObject* data_object,
DWORD key_state,
POINT cursor_position,
DWORD effects) {
current_rvh_ = tab_contents_->render_view_host();
// Don't pass messages to the renderer if an interstitial page is showing
// because we don't want the interstitial page to navigate. Instead,
// pass the messages on to a separate interstitial DropTarget handler.
if (tab_contents_->showing_interstitial_page())
return interstitial_drop_target_->OnDragEnter(data_object, effects);
// TODO(tc): PopulateWebDropData can be slow depending on what is in the
// IDataObject. Maybe we can do this in a background thread.
WebDropData drop_data;
WebDropData::PopulateWebDropData(data_object, &drop_data);
if (drop_data.url.is_empty())
ui::OSExchangeDataProviderWin::GetPlainTextURL(data_object, &drop_data.url);
drag_cursor_ = WebDragOperationNone;
POINT client_pt = cursor_position;
ScreenToClient(GetHWND(), &client_pt);
tab_contents_->render_view_host()->DragTargetDragEnter(drop_data,
gfx::Point(client_pt.x, client_pt.y),
gfx::Point(cursor_position.x, cursor_position.y),
web_drag_utils_win::WinDragOpMaskToWebDragOpMask(effects));
// This is non-null if tab_contents_ is showing an ExtensionWebUI with
// support for (at the moment experimental) drag and drop extensions.
if (tab_contents_->GetBookmarkDragDelegate()) {
ui::OSExchangeData os_exchange_data(
new ui::OSExchangeDataProviderWin(data_object));
BookmarkNodeData bookmark_drag_data;
if (bookmark_drag_data.Read(os_exchange_data))
tab_contents_->GetBookmarkDragDelegate()->OnDragEnter(bookmark_drag_data);
}
// We lie here and always return a DROPEFFECT because we don't want to
// wait for the IPC call to return.
return web_drag_utils_win::WebDragOpToWinDragOp(drag_cursor_);
}
DWORD WebDropTarget::OnDragOver(IDataObject* data_object,
DWORD key_state,
POINT cursor_position,
DWORD effects) {
DCHECK(current_rvh_);
if (current_rvh_ != tab_contents_->render_view_host())
OnDragEnter(data_object, key_state, cursor_position, effects);
if (tab_contents_->showing_interstitial_page())
return interstitial_drop_target_->OnDragOver(data_object, effects);
POINT client_pt = cursor_position;
ScreenToClient(GetHWND(), &client_pt);
tab_contents_->render_view_host()->DragTargetDragOver(
gfx::Point(client_pt.x, client_pt.y),
gfx::Point(cursor_position.x, cursor_position.y),
web_drag_utils_win::WinDragOpMaskToWebDragOpMask(effects));
if (tab_contents_->GetBookmarkDragDelegate()) {
ui::OSExchangeData os_exchange_data(
new ui::OSExchangeDataProviderWin(data_object));
BookmarkNodeData bookmark_drag_data;
if (bookmark_drag_data.Read(os_exchange_data))
tab_contents_->GetBookmarkDragDelegate()->OnDragOver(bookmark_drag_data);
}
return web_drag_utils_win::WebDragOpToWinDragOp(drag_cursor_);
}
void WebDropTarget::OnDragLeave(IDataObject* data_object) {
DCHECK(current_rvh_);
if (current_rvh_ != tab_contents_->render_view_host())
return;
if (tab_contents_->showing_interstitial_page()) {
interstitial_drop_target_->OnDragLeave(data_object);
} else {
tab_contents_->render_view_host()->DragTargetDragLeave();
}
if (tab_contents_->GetBookmarkDragDelegate()) {
ui::OSExchangeData os_exchange_data(
new ui::OSExchangeDataProviderWin(data_object));
BookmarkNodeData bookmark_drag_data;
if (bookmark_drag_data.Read(os_exchange_data))
tab_contents_->GetBookmarkDragDelegate()->OnDragLeave(bookmark_drag_data);
}
}
DWORD WebDropTarget::OnDrop(IDataObject* data_object,
DWORD key_state,
POINT cursor_position,
DWORD effect) {
DCHECK(current_rvh_);
if (current_rvh_ != tab_contents_->render_view_host())
OnDragEnter(data_object, key_state, cursor_position, effect);
if (tab_contents_->showing_interstitial_page())
interstitial_drop_target_->OnDragOver(data_object, effect);
if (tab_contents_->showing_interstitial_page())
return interstitial_drop_target_->OnDrop(data_object, effect);
POINT client_pt = cursor_position;
ScreenToClient(GetHWND(), &client_pt);
tab_contents_->render_view_host()->DragTargetDrop(
gfx::Point(client_pt.x, client_pt.y),
gfx::Point(cursor_position.x, cursor_position.y));
if (tab_contents_->GetBookmarkDragDelegate()) {
ui::OSExchangeData os_exchange_data(
new ui::OSExchangeDataProviderWin(data_object));
BookmarkNodeData bookmark_drag_data;
if (bookmark_drag_data.Read(os_exchange_data))
tab_contents_->GetBookmarkDragDelegate()->OnDrop(bookmark_drag_data);
}
current_rvh_ = NULL;
// This isn't always correct, but at least it's a close approximation.
// For now, we always map a move to a copy to prevent potential data loss.
DWORD drop_effect = web_drag_utils_win::WebDragOpToWinDragOp(drag_cursor_);
return drop_effect != DROPEFFECT_MOVE ? drop_effect : DROPEFFECT_COPY;
}