1// Copyright (c) 2011 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/web_drop_target_win.h"
6
7#include <windows.h>
8#include <shlobj.h>
9
10#include "chrome/browser/bookmarks/bookmark_node_data.h"
11#include "chrome/browser/tab_contents/web_drag_utils_win.h"
12#include "content/browser/renderer_host/render_view_host.h"
13#include "content/browser/tab_contents/tab_contents.h"
14#include "googleurl/src/gurl.h"
15#include "net/base/net_util.h"
16#include "ui/base/clipboard/clipboard_util_win.h"
17#include "ui/base/dragdrop/os_exchange_data.h"
18#include "ui/base/dragdrop/os_exchange_data_provider_win.h"
19#include "ui/gfx/point.h"
20#include "webkit/glue/webdropdata.h"
21#include "webkit/glue/window_open_disposition.h"
22
23using WebKit::WebDragOperationNone;
24using WebKit::WebDragOperationCopy;
25using WebKit::WebDragOperationLink;
26using WebKit::WebDragOperationMove;
27using WebKit::WebDragOperationGeneric;
28
29namespace {
30
31// A helper method for getting the preferred drop effect.
32DWORD GetPreferredDropEffect(DWORD effect) {
33  if (effect & DROPEFFECT_COPY)
34    return DROPEFFECT_COPY;
35  if (effect & DROPEFFECT_LINK)
36    return DROPEFFECT_LINK;
37  if (effect & DROPEFFECT_MOVE)
38    return DROPEFFECT_MOVE;
39  return DROPEFFECT_NONE;
40}
41
42}  // namespace
43
44// InterstitialDropTarget is like a app::win::DropTarget implementation that
45// WebDropTarget passes through to if an interstitial is showing.  Rather than
46// passing messages on to the renderer, we just check to see if there's a link
47// in the drop data and handle links as navigations.
48class InterstitialDropTarget {
49 public:
50  explicit InterstitialDropTarget(TabContents* tab_contents)
51      : tab_contents_(tab_contents) {}
52
53  DWORD OnDragEnter(IDataObject* data_object, DWORD effect) {
54    return ui::ClipboardUtil::HasUrl(data_object) ?
55        GetPreferredDropEffect(effect) : DROPEFFECT_NONE;
56  }
57
58  DWORD OnDragOver(IDataObject* data_object, DWORD effect) {
59    return ui::ClipboardUtil::HasUrl(data_object) ?
60        GetPreferredDropEffect(effect) : DROPEFFECT_NONE;
61  }
62
63  void OnDragLeave(IDataObject* data_object) {
64  }
65
66  DWORD OnDrop(IDataObject* data_object, DWORD effect) {
67    if (ui::ClipboardUtil::HasUrl(data_object)) {
68      std::wstring url;
69      std::wstring title;
70      ui::ClipboardUtil::GetUrl(data_object, &url, &title, true);
71      tab_contents_->OpenURL(GURL(url), GURL(), CURRENT_TAB,
72                             PageTransition::AUTO_BOOKMARK);
73      return GetPreferredDropEffect(effect);
74    }
75    return DROPEFFECT_NONE;
76  }
77
78 private:
79  TabContents* tab_contents_;
80
81  DISALLOW_COPY_AND_ASSIGN(InterstitialDropTarget);
82};
83
84WebDropTarget::WebDropTarget(HWND source_hwnd, TabContents* tab_contents)
85    : ui::DropTarget(source_hwnd),
86      tab_contents_(tab_contents),
87      current_rvh_(NULL),
88      drag_cursor_(WebDragOperationNone),
89      interstitial_drop_target_(new InterstitialDropTarget(tab_contents)) {
90}
91
92WebDropTarget::~WebDropTarget() {
93}
94
95DWORD WebDropTarget::OnDragEnter(IDataObject* data_object,
96                                 DWORD key_state,
97                                 POINT cursor_position,
98                                 DWORD effects) {
99  current_rvh_ = tab_contents_->render_view_host();
100
101  // Don't pass messages to the renderer if an interstitial page is showing
102  // because we don't want the interstitial page to navigate.  Instead,
103  // pass the messages on to a separate interstitial DropTarget handler.
104  if (tab_contents_->showing_interstitial_page())
105    return interstitial_drop_target_->OnDragEnter(data_object, effects);
106
107  // TODO(tc): PopulateWebDropData can be slow depending on what is in the
108  // IDataObject.  Maybe we can do this in a background thread.
109  WebDropData drop_data;
110  WebDropData::PopulateWebDropData(data_object, &drop_data);
111
112  if (drop_data.url.is_empty())
113    ui::OSExchangeDataProviderWin::GetPlainTextURL(data_object, &drop_data.url);
114
115  drag_cursor_ = WebDragOperationNone;
116
117  POINT client_pt = cursor_position;
118  ScreenToClient(GetHWND(), &client_pt);
119  tab_contents_->render_view_host()->DragTargetDragEnter(drop_data,
120      gfx::Point(client_pt.x, client_pt.y),
121      gfx::Point(cursor_position.x, cursor_position.y),
122      web_drag_utils_win::WinDragOpMaskToWebDragOpMask(effects));
123
124  // This is non-null if tab_contents_ is showing an ExtensionWebUI with
125  // support for (at the moment experimental) drag and drop extensions.
126  if (tab_contents_->GetBookmarkDragDelegate()) {
127    ui::OSExchangeData os_exchange_data(
128        new ui::OSExchangeDataProviderWin(data_object));
129    BookmarkNodeData bookmark_drag_data;
130    if (bookmark_drag_data.Read(os_exchange_data))
131      tab_contents_->GetBookmarkDragDelegate()->OnDragEnter(bookmark_drag_data);
132  }
133
134  // We lie here and always return a DROPEFFECT because we don't want to
135  // wait for the IPC call to return.
136  return web_drag_utils_win::WebDragOpToWinDragOp(drag_cursor_);
137}
138
139DWORD WebDropTarget::OnDragOver(IDataObject* data_object,
140                                DWORD key_state,
141                                POINT cursor_position,
142                                DWORD effects) {
143  DCHECK(current_rvh_);
144  if (current_rvh_ != tab_contents_->render_view_host())
145    OnDragEnter(data_object, key_state, cursor_position, effects);
146
147  if (tab_contents_->showing_interstitial_page())
148    return interstitial_drop_target_->OnDragOver(data_object, effects);
149
150  POINT client_pt = cursor_position;
151  ScreenToClient(GetHWND(), &client_pt);
152  tab_contents_->render_view_host()->DragTargetDragOver(
153      gfx::Point(client_pt.x, client_pt.y),
154      gfx::Point(cursor_position.x, cursor_position.y),
155      web_drag_utils_win::WinDragOpMaskToWebDragOpMask(effects));
156
157  if (tab_contents_->GetBookmarkDragDelegate()) {
158    ui::OSExchangeData os_exchange_data(
159        new ui::OSExchangeDataProviderWin(data_object));
160    BookmarkNodeData bookmark_drag_data;
161    if (bookmark_drag_data.Read(os_exchange_data))
162      tab_contents_->GetBookmarkDragDelegate()->OnDragOver(bookmark_drag_data);
163  }
164
165  return web_drag_utils_win::WebDragOpToWinDragOp(drag_cursor_);
166}
167
168void WebDropTarget::OnDragLeave(IDataObject* data_object) {
169  DCHECK(current_rvh_);
170  if (current_rvh_ != tab_contents_->render_view_host())
171    return;
172
173  if (tab_contents_->showing_interstitial_page()) {
174    interstitial_drop_target_->OnDragLeave(data_object);
175  } else {
176    tab_contents_->render_view_host()->DragTargetDragLeave();
177  }
178
179  if (tab_contents_->GetBookmarkDragDelegate()) {
180    ui::OSExchangeData os_exchange_data(
181        new ui::OSExchangeDataProviderWin(data_object));
182    BookmarkNodeData bookmark_drag_data;
183    if (bookmark_drag_data.Read(os_exchange_data))
184      tab_contents_->GetBookmarkDragDelegate()->OnDragLeave(bookmark_drag_data);
185  }
186}
187
188DWORD WebDropTarget::OnDrop(IDataObject* data_object,
189                            DWORD key_state,
190                            POINT cursor_position,
191                            DWORD effect) {
192  DCHECK(current_rvh_);
193  if (current_rvh_ != tab_contents_->render_view_host())
194    OnDragEnter(data_object, key_state, cursor_position, effect);
195
196  if (tab_contents_->showing_interstitial_page())
197    interstitial_drop_target_->OnDragOver(data_object, effect);
198
199  if (tab_contents_->showing_interstitial_page())
200    return interstitial_drop_target_->OnDrop(data_object, effect);
201
202  POINT client_pt = cursor_position;
203  ScreenToClient(GetHWND(), &client_pt);
204  tab_contents_->render_view_host()->DragTargetDrop(
205      gfx::Point(client_pt.x, client_pt.y),
206      gfx::Point(cursor_position.x, cursor_position.y));
207
208  if (tab_contents_->GetBookmarkDragDelegate()) {
209    ui::OSExchangeData os_exchange_data(
210        new ui::OSExchangeDataProviderWin(data_object));
211    BookmarkNodeData bookmark_drag_data;
212    if (bookmark_drag_data.Read(os_exchange_data))
213      tab_contents_->GetBookmarkDragDelegate()->OnDrop(bookmark_drag_data);
214  }
215
216  current_rvh_ = NULL;
217
218  // This isn't always correct, but at least it's a close approximation.
219  // For now, we always map a move to a copy to prevent potential data loss.
220  DWORD drop_effect = web_drag_utils_win::WebDragOpToWinDragOp(drag_cursor_);
221  return drop_effect != DROPEFFECT_MOVE ? drop_effect : DROPEFFECT_COPY;
222}
223