1// Copyright (c) 2012 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 "content/browser/web_contents/web_drag_dest_win.h"
6
7#include <windows.h>
8#include <shlobj.h>
9
10#include "base/win/win_util.h"
11#include "content/browser/renderer_host/render_view_host_impl.h"
12#include "content/browser/web_contents/web_drag_utils_win.h"
13#include "content/public/browser/web_contents.h"
14#include "content/public/browser/web_contents_delegate.h"
15#include "content/public/browser/web_drag_dest_delegate.h"
16#include "content/public/common/drop_data.h"
17#include "net/base/net_util.h"
18#include "third_party/WebKit/public/web/WebInputEvent.h"
19#include "ui/base/clipboard/clipboard_util_win.h"
20#include "ui/base/dragdrop/os_exchange_data.h"
21#include "ui/base/dragdrop/os_exchange_data_provider_win.h"
22#include "ui/base/window_open_disposition.h"
23#include "ui/gfx/point.h"
24#include "url/gurl.h"
25
26using WebKit::WebDragOperationNone;
27using WebKit::WebDragOperationCopy;
28using WebKit::WebDragOperationLink;
29using WebKit::WebDragOperationMove;
30using WebKit::WebDragOperationGeneric;
31
32namespace content {
33namespace {
34
35const unsigned short kHighBitMaskShort = 0x8000;
36
37// A helper method for getting the preferred drop effect.
38DWORD GetPreferredDropEffect(DWORD effect) {
39  if (effect & DROPEFFECT_COPY)
40    return DROPEFFECT_COPY;
41  if (effect & DROPEFFECT_LINK)
42    return DROPEFFECT_LINK;
43  if (effect & DROPEFFECT_MOVE)
44    return DROPEFFECT_MOVE;
45  return DROPEFFECT_NONE;
46}
47
48int GetModifierFlags() {
49  int modifier_state = 0;
50  if (base::win::IsShiftPressed())
51    modifier_state |= WebKit::WebInputEvent::ShiftKey;
52  if (base::win::IsCtrlPressed())
53    modifier_state |= WebKit::WebInputEvent::ControlKey;
54  if (base::win::IsAltPressed())
55    modifier_state |= WebKit::WebInputEvent::AltKey;
56  if (::GetKeyState(VK_LWIN) & kHighBitMaskShort)
57    modifier_state |= WebKit::WebInputEvent::MetaKey;
58  if (::GetKeyState(VK_RWIN) & kHighBitMaskShort)
59    modifier_state |= WebKit::WebInputEvent::MetaKey;
60  return modifier_state;
61}
62
63// Helper method for converting Window's specific IDataObject to a DropData
64// object.
65void PopulateDropData(IDataObject* data_object, DropData* drop_data) {
66  base::string16 url_str;
67  if (ui::ClipboardUtil::GetUrl(
68          data_object, &url_str, &drop_data->url_title, false)) {
69    GURL test_url(url_str);
70    if (test_url.is_valid())
71      drop_data->url = test_url;
72  }
73  std::vector<base::string16> filenames;
74  ui::ClipboardUtil::GetFilenames(data_object, &filenames);
75  for (size_t i = 0; i < filenames.size(); ++i)
76    drop_data->filenames.push_back(
77        DropData::FileInfo(filenames[i], base::string16()));
78  base::string16 text;
79  ui::ClipboardUtil::GetPlainText(data_object, &text);
80  if (!text.empty()) {
81    drop_data->text = base::NullableString16(text, false);
82  }
83  base::string16 html;
84  std::string html_base_url;
85  ui::ClipboardUtil::GetHtml(data_object, &html, &html_base_url);
86  if (!html.empty()) {
87    drop_data->html = base::NullableString16(html, false);
88  }
89  if (!html_base_url.empty()) {
90    drop_data->html_base_url = GURL(html_base_url);
91  }
92  ui::ClipboardUtil::GetWebCustomData(data_object, &drop_data->custom_data);
93}
94
95}  // namespace
96
97// InterstitialDropTarget is like a ui::DropTargetWin implementation that
98// WebDragDest passes through to if an interstitial is showing.  Rather than
99// passing messages on to the renderer, we just check to see if there's a link
100// in the drop data and handle links as navigations.
101class InterstitialDropTarget {
102 public:
103  explicit InterstitialDropTarget(WebContents* web_contents)
104      : web_contents_(web_contents) {}
105
106  DWORD OnDragEnter(IDataObject* data_object, DWORD effect) {
107    return ui::ClipboardUtil::HasUrl(data_object) ?
108        GetPreferredDropEffect(effect) : DROPEFFECT_NONE;
109  }
110
111  DWORD OnDragOver(IDataObject* data_object, DWORD effect) {
112    return ui::ClipboardUtil::HasUrl(data_object) ?
113        GetPreferredDropEffect(effect) : DROPEFFECT_NONE;
114  }
115
116  void OnDragLeave(IDataObject* data_object) {
117  }
118
119  DWORD OnDrop(IDataObject* data_object, DWORD effect) {
120    if (!ui::ClipboardUtil::HasUrl(data_object))
121      return DROPEFFECT_NONE;
122
123    std::wstring url;
124    std::wstring title;
125    ui::ClipboardUtil::GetUrl(data_object, &url, &title, true);
126    OpenURLParams params(GURL(url), Referrer(), CURRENT_TAB,
127                         PAGE_TRANSITION_AUTO_BOOKMARK, false);
128    web_contents_->OpenURL(params);
129    return GetPreferredDropEffect(effect);
130  }
131
132 private:
133  WebContents* web_contents_;
134
135  DISALLOW_COPY_AND_ASSIGN(InterstitialDropTarget);
136};
137
138WebDragDest::WebDragDest(HWND source_hwnd, WebContents* web_contents)
139    : ui::DropTargetWin(source_hwnd),
140      web_contents_(web_contents),
141      current_rvh_(NULL),
142      drag_cursor_(WebDragOperationNone),
143      interstitial_drop_target_(new InterstitialDropTarget(web_contents)),
144      delegate_(NULL),
145      canceled_(false) {
146}
147
148WebDragDest::~WebDragDest() {
149}
150
151DWORD WebDragDest::OnDragEnter(IDataObject* data_object,
152                               DWORD key_state,
153                               POINT cursor_position,
154                               DWORD effects) {
155  current_rvh_ = web_contents_->GetRenderViewHost();
156
157  // TODO(tc): PopulateDropData can be slow depending on what is in the
158  // IDataObject.  Maybe we can do this in a background thread.
159  scoped_ptr<DropData> drop_data;
160  drop_data.reset(new DropData());
161  PopulateDropData(data_object, drop_data.get());
162
163  if (drop_data->url.is_empty())
164    ui::OSExchangeDataProviderWin::GetPlainTextURL(data_object,
165                                                   &drop_data->url);
166
167  // Give the delegate an opportunity to cancel the drag.
168  canceled_ = !web_contents_->GetDelegate()->CanDragEnter(
169      web_contents_,
170      *drop_data,
171      WinDragOpMaskToWebDragOpMask(effects));
172  if (canceled_)
173    return DROPEFFECT_NONE;
174
175  if (delegate_)
176    delegate_->DragInitialize(web_contents_);
177
178  // Don't pass messages to the renderer if an interstitial page is showing
179  // because we don't want the interstitial page to navigate.  Instead,
180  // pass the messages on to a separate interstitial DropTarget handler.
181  if (web_contents_->ShowingInterstitialPage())
182    return interstitial_drop_target_->OnDragEnter(data_object, effects);
183
184  drop_data_.swap(drop_data);
185  drag_cursor_ = WebDragOperationNone;
186
187  POINT client_pt = cursor_position;
188  ScreenToClient(GetHWND(), &client_pt);
189  web_contents_->GetRenderViewHost()->DragTargetDragEnter(*drop_data_,
190      gfx::Point(client_pt.x, client_pt.y),
191      gfx::Point(cursor_position.x, cursor_position.y),
192      WinDragOpMaskToWebDragOpMask(effects),
193      GetModifierFlags());
194
195  if (delegate_)
196      delegate_->OnDragEnter(data_object);
197
198  // We lie here and always return a DROPEFFECT because we don't want to
199  // wait for the IPC call to return.
200  return WebDragOpToWinDragOp(drag_cursor_);
201}
202
203DWORD WebDragDest::OnDragOver(IDataObject* data_object,
204                              DWORD key_state,
205                              POINT cursor_position,
206                              DWORD effects) {
207  DCHECK(current_rvh_);
208  if (current_rvh_ != web_contents_->GetRenderViewHost())
209    OnDragEnter(data_object, key_state, cursor_position, effects);
210
211  if (canceled_)
212    return DROPEFFECT_NONE;
213
214  if (web_contents_->ShowingInterstitialPage())
215    return interstitial_drop_target_->OnDragOver(data_object, effects);
216
217  POINT client_pt = cursor_position;
218  ScreenToClient(GetHWND(), &client_pt);
219  web_contents_->GetRenderViewHost()->DragTargetDragOver(
220      gfx::Point(client_pt.x, client_pt.y),
221      gfx::Point(cursor_position.x, cursor_position.y),
222      WinDragOpMaskToWebDragOpMask(effects),
223      GetModifierFlags());
224
225  if (delegate_)
226    delegate_->OnDragOver(data_object);
227
228  return WebDragOpToWinDragOp(drag_cursor_);
229}
230
231void WebDragDest::OnDragLeave(IDataObject* data_object) {
232  DCHECK(current_rvh_);
233  if (current_rvh_ != web_contents_->GetRenderViewHost())
234    return;
235
236  if (canceled_)
237    return;
238
239  if (web_contents_->ShowingInterstitialPage()) {
240    interstitial_drop_target_->OnDragLeave(data_object);
241  } else {
242    web_contents_->GetRenderViewHost()->DragTargetDragLeave();
243  }
244
245  if (delegate_)
246    delegate_->OnDragLeave(data_object);
247
248  drop_data_.reset();
249}
250
251DWORD WebDragDest::OnDrop(IDataObject* data_object,
252                          DWORD key_state,
253                          POINT cursor_position,
254                          DWORD effect) {
255  DCHECK(current_rvh_);
256  if (current_rvh_ != web_contents_->GetRenderViewHost())
257    OnDragEnter(data_object, key_state, cursor_position, effect);
258
259  if (web_contents_->ShowingInterstitialPage())
260    interstitial_drop_target_->OnDragOver(data_object, effect);
261
262  if (web_contents_->ShowingInterstitialPage())
263    return interstitial_drop_target_->OnDrop(data_object, effect);
264
265  POINT client_pt = cursor_position;
266  ScreenToClient(GetHWND(), &client_pt);
267  web_contents_->GetRenderViewHost()->DragTargetDrop(
268      gfx::Point(client_pt.x, client_pt.y),
269      gfx::Point(cursor_position.x, cursor_position.y),
270      GetModifierFlags());
271
272  if (delegate_)
273    delegate_->OnDrop(data_object);
274
275  current_rvh_ = NULL;
276
277  // This isn't always correct, but at least it's a close approximation.
278  // For now, we always map a move to a copy to prevent potential data loss.
279  DWORD drop_effect = WebDragOpToWinDragOp(drag_cursor_);
280  DWORD result = drop_effect != DROPEFFECT_MOVE ? drop_effect : DROPEFFECT_COPY;
281
282  drop_data_.reset();
283  return result;
284}
285
286}  // namespace content
287