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/ui/views/tab_contents/tab_contents_drag_win.h"
6
7#include <windows.h>
8
9#include <string>
10
11#include "base/file_path.h"
12#include "base/message_loop.h"
13#include "base/task.h"
14#include "base/threading/platform_thread.h"
15#include "base/threading/thread.h"
16#include "base/utf_string_conversions.h"
17#include "chrome/browser/bookmarks/bookmark_node_data.h"
18#include "chrome/browser/download/download_util.h"
19#include "chrome/browser/download/drag_download_file.h"
20#include "chrome/browser/download/drag_download_util.h"
21#include "chrome/browser/tab_contents/web_drag_source_win.h"
22#include "chrome/browser/tab_contents/web_drag_utils_win.h"
23#include "chrome/browser/tab_contents/web_drop_target_win.h"
24#include "chrome/browser/ui/views/tab_contents/native_tab_contents_view_win.h"
25#include "chrome/common/url_constants.h"
26#include "content/browser/browser_thread.h"
27#include "content/browser/tab_contents/tab_contents.h"
28#include "net/base/net_util.h"
29#include "views/drag_utils.h"
30#include "webkit/glue/webdropdata.h"
31
32using WebKit::WebDragOperationsMask;
33using WebKit::WebDragOperationCopy;
34using WebKit::WebDragOperationLink;
35using WebKit::WebDragOperationMove;
36
37namespace {
38
39HHOOK msg_hook = NULL;
40DWORD drag_out_thread_id = 0;
41bool mouse_up_received = false;
42
43LRESULT CALLBACK MsgFilterProc(int code, WPARAM wparam, LPARAM lparam) {
44  if (code == base::MessagePumpForUI::kMessageFilterCode &&
45      !mouse_up_received) {
46    MSG* msg = reinterpret_cast<MSG*>(lparam);
47    // We do not care about WM_SYSKEYDOWN and WM_SYSKEYUP because when ALT key
48    // is pressed down on drag-and-drop, it means to create a link.
49    if (msg->message == WM_MOUSEMOVE || msg->message == WM_LBUTTONUP ||
50        msg->message == WM_KEYDOWN || msg->message == WM_KEYUP) {
51      // Forward the message from the UI thread to the drag-and-drop thread.
52      PostThreadMessage(drag_out_thread_id,
53                        msg->message,
54                        msg->wParam,
55                        msg->lParam);
56
57      // If the left button is up, we do not need to forward the message any
58      // more.
59      if (msg->message == WM_LBUTTONUP || !(GetKeyState(VK_LBUTTON) & 0x8000))
60        mouse_up_received = true;
61
62      return TRUE;
63    }
64  }
65  return CallNextHookEx(msg_hook, code, wparam, lparam);
66}
67
68}  // namespace
69
70class DragDropThread : public base::Thread {
71 public:
72  explicit DragDropThread(TabContentsDragWin* drag_handler)
73       : base::Thread("Chrome_DragDropThread"),
74         drag_handler_(drag_handler) {
75  }
76
77  virtual ~DragDropThread() {
78    Thread::Stop();
79  }
80
81 protected:
82  // base::Thread implementations:
83  virtual void Init() {
84    int ole_result = OleInitialize(NULL);
85    DCHECK(ole_result == S_OK);
86  }
87
88  virtual void CleanUp() {
89    OleUninitialize();
90  }
91
92 private:
93  // Hold a reference count to TabContentsDragWin to make sure that it is always
94  // alive in the thread lifetime.
95  scoped_refptr<TabContentsDragWin> drag_handler_;
96
97  DISALLOW_COPY_AND_ASSIGN(DragDropThread);
98};
99
100TabContentsDragWin::TabContentsDragWin(NativeTabContentsViewWin* view)
101    : drag_drop_thread_id_(0),
102      view_(view),
103      drag_ended_(false),
104      old_drop_target_suspended_state_(false) {
105}
106
107TabContentsDragWin::~TabContentsDragWin() {
108  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
109  DCHECK(!drag_drop_thread_.get());
110}
111
112void TabContentsDragWin::StartDragging(const WebDropData& drop_data,
113                                       WebDragOperationsMask ops,
114                                       const SkBitmap& image,
115                                       const gfx::Point& image_offset) {
116  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
117
118  drag_source_ = new WebDragSource(view_->GetNativeView(),
119                                   view_->GetTabContents());
120
121  const GURL& page_url = view_->GetTabContents()->GetURL();
122  const std::string& page_encoding = view_->GetTabContents()->encoding();
123
124  // If it is not drag-out, do the drag-and-drop in the current UI thread.
125  if (drop_data.download_metadata.empty()) {
126    DoDragging(drop_data, ops, page_url, page_encoding, image, image_offset);
127    EndDragging(false);
128    return;
129  }
130
131  // We do not want to drag and drop the download to itself.
132  old_drop_target_suspended_state_ = view_->drop_target()->suspended();
133  view_->drop_target()->set_suspended(true);
134
135  // Start a background thread to do the drag-and-drop.
136  DCHECK(!drag_drop_thread_.get());
137  drag_drop_thread_.reset(new DragDropThread(this));
138  base::Thread::Options options;
139  options.message_loop_type = MessageLoop::TYPE_UI;
140  if (drag_drop_thread_->StartWithOptions(options)) {
141    drag_drop_thread_->message_loop()->PostTask(
142        FROM_HERE,
143        NewRunnableMethod(this,
144                          &TabContentsDragWin::StartBackgroundDragging,
145                          drop_data,
146                          ops,
147                          page_url,
148                          page_encoding,
149                          image,
150                          image_offset));
151  }
152
153  // Install a hook procedure to monitor the messages so that we can forward
154  // the appropriate ones to the background thread.
155  drag_out_thread_id = drag_drop_thread_->thread_id();
156  mouse_up_received = false;
157  DCHECK(!msg_hook);
158  msg_hook = SetWindowsHookEx(WH_MSGFILTER,
159                              MsgFilterProc,
160                              NULL,
161                              GetCurrentThreadId());
162
163  // Attach the input state of the background thread to the UI thread so that
164  // SetCursor can work from the background thread.
165  AttachThreadInput(drag_out_thread_id, GetCurrentThreadId(), TRUE);
166}
167
168void TabContentsDragWin::StartBackgroundDragging(
169    const WebDropData& drop_data,
170    WebDragOperationsMask ops,
171    const GURL& page_url,
172    const std::string& page_encoding,
173    const SkBitmap& image,
174    const gfx::Point& image_offset) {
175  drag_drop_thread_id_ = base::PlatformThread::CurrentId();
176
177  DoDragging(drop_data, ops, page_url, page_encoding, image, image_offset);
178  BrowserThread::PostTask(
179      BrowserThread::UI, FROM_HERE,
180      NewRunnableMethod(this, &TabContentsDragWin::EndDragging, true));
181}
182
183void TabContentsDragWin::PrepareDragForDownload(
184    const WebDropData& drop_data,
185    ui::OSExchangeData* data,
186    const GURL& page_url,
187    const std::string& page_encoding) {
188  // Parse the download metadata.
189  string16 mime_type;
190  FilePath file_name;
191  GURL download_url;
192  if (!drag_download_util::ParseDownloadMetadata(drop_data.download_metadata,
193                                                 &mime_type,
194                                                 &file_name,
195                                                 &download_url))
196    return;
197
198  // Generate the download filename.
199  std::string content_disposition =
200      "attachment; filename=" + UTF16ToUTF8(file_name.value());
201  FilePath generated_file_name;
202  download_util::GenerateFileName(download_url,
203                                  content_disposition,
204                                  std::string(),
205                                  UTF16ToUTF8(mime_type),
206                                  &generated_file_name);
207
208  // Provide the data as file (CF_HDROP). A temporary download file with the
209  // Zone.Identifier ADS (Alternate Data Stream) attached will be created.
210  linked_ptr<net::FileStream> empty_file_stream;
211  scoped_refptr<DragDownloadFile> download_file =
212      new DragDownloadFile(generated_file_name,
213                           empty_file_stream,
214                           download_url,
215                           page_url,
216                           page_encoding,
217                           view_->GetTabContents());
218  ui::OSExchangeData::DownloadFileInfo file_download(FilePath(),
219                                                     download_file.get());
220  data->SetDownloadFileInfo(file_download);
221
222  // Enable asynchronous operation.
223  ui::OSExchangeDataProviderWin::GetIAsyncOperation(*data)->SetAsyncMode(TRUE);
224}
225
226void TabContentsDragWin::PrepareDragForFileContents(
227    const WebDropData& drop_data, ui::OSExchangeData* data) {
228  // Images without ALT text will only have a file extension so we need to
229  // synthesize one from the provided extension and URL.
230  FilePath file_name(drop_data.file_description_filename);
231  file_name = file_name.BaseName().RemoveExtension();
232  if (file_name.value().empty()) {
233    // Retrieve the name from the URL.
234    file_name = FilePath(
235        net::GetSuggestedFilename(drop_data.url, "", "", string16()));
236    if (file_name.value().size() + drop_data.file_extension.size() + 1 >
237        MAX_PATH) {
238      file_name = FilePath(file_name.value().substr(
239          0, MAX_PATH - drop_data.file_extension.size() - 2));
240    }
241  }
242  file_name = file_name.ReplaceExtension(drop_data.file_extension);
243  data->SetFileContents(file_name, drop_data.file_contents);
244}
245
246void TabContentsDragWin::PrepareDragForUrl(const WebDropData& drop_data,
247                                           ui::OSExchangeData* data) {
248  if (drop_data.url.SchemeIs(chrome::kJavaScriptScheme)) {
249    // We don't want to allow javascript URLs to be dragged to the desktop,
250    // but we do want to allow them to be added to the bookmarks bar
251    // (bookmarklets). So we create a fake bookmark entry (BookmarkNodeData
252    // object) which explorer.exe cannot handle, and write the entry to data.
253    BookmarkNodeData::Element bm_elt;
254    bm_elt.is_url = true;
255    bm_elt.url = drop_data.url;
256    bm_elt.title = drop_data.url_title;
257
258    BookmarkNodeData bm_drag_data;
259    bm_drag_data.elements.push_back(bm_elt);
260
261    // Pass in NULL as the profile so that the bookmark always adds the url
262    // rather than trying to move an existing url.
263    bm_drag_data.Write(NULL, data);
264  } else {
265    data->SetURL(drop_data.url, drop_data.url_title);
266  }
267}
268
269void TabContentsDragWin::DoDragging(const WebDropData& drop_data,
270                                    WebDragOperationsMask ops,
271                                    const GURL& page_url,
272                                    const std::string& page_encoding,
273                                    const SkBitmap& image,
274                                    const gfx::Point& image_offset) {
275  ui::OSExchangeData data;
276
277  if (!drop_data.download_metadata.empty()) {
278    PrepareDragForDownload(drop_data, &data, page_url, page_encoding);
279
280    // Set the observer.
281    ui::OSExchangeDataProviderWin::GetDataObjectImpl(data)->set_observer(this);
282  } else {
283    // We set the file contents before the URL because the URL also sets file
284    // contents (to a .URL shortcut).  We want to prefer file content data over
285    // a shortcut so we add it first.
286    if (!drop_data.file_contents.empty())
287      PrepareDragForFileContents(drop_data, &data);
288    if (!drop_data.text_html.empty())
289      data.SetHtml(drop_data.text_html, drop_data.html_base_url);
290    // We set the text contents before the URL because the URL also sets text
291    // content.
292    if (!drop_data.plain_text.empty())
293      data.SetString(drop_data.plain_text);
294    if (drop_data.url.is_valid())
295      PrepareDragForUrl(drop_data, &data);
296  }
297
298  // Set drag image.
299  if (!image.isNull()) {
300    drag_utils::SetDragImageOnDataObject(
301        image, gfx::Size(image.width(), image.height()), image_offset, &data);
302  }
303
304  // We need to enable recursive tasks on the message loop so we can get
305  // updates while in the system DoDragDrop loop.
306  bool old_state = MessageLoop::current()->NestableTasksAllowed();
307  MessageLoop::current()->SetNestableTasksAllowed(true);
308  DWORD effect;
309  DoDragDrop(ui::OSExchangeDataProviderWin::GetIDataObject(data), drag_source_,
310             web_drag_utils_win::WebDragOpMaskToWinDragOpMask(ops), &effect);
311  MessageLoop::current()->SetNestableTasksAllowed(old_state);
312
313  // This works because WebDragSource::OnDragSourceDrop uses PostTask to
314  // dispatch the actual event.
315  drag_source_->set_effect(effect);
316}
317
318void TabContentsDragWin::EndDragging(bool restore_suspended_state) {
319  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
320
321  if (drag_ended_)
322    return;
323  drag_ended_ = true;
324
325  if (restore_suspended_state)
326    view_->drop_target()->set_suspended(old_drop_target_suspended_state_);
327
328  if (msg_hook) {
329    AttachThreadInput(drag_out_thread_id, GetCurrentThreadId(), FALSE);
330    UnhookWindowsHookEx(msg_hook);
331    msg_hook = NULL;
332  }
333
334  view_->EndDragging();
335}
336
337void TabContentsDragWin::CancelDrag() {
338  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
339
340  drag_source_->CancelDrag();
341}
342
343void TabContentsDragWin::CloseThread() {
344  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
345
346  drag_drop_thread_.reset();
347}
348
349void TabContentsDragWin::OnWaitForData() {
350  DCHECK(drag_drop_thread_id_ == base::PlatformThread::CurrentId());
351
352  // When the left button is release and we start to wait for the data, end
353  // the dragging before DoDragDrop returns. This makes the page leave the drag
354  // mode so that it can start to process the normal input events.
355  BrowserThread::PostTask(
356      BrowserThread::UI, FROM_HERE,
357      NewRunnableMethod(this, &TabContentsDragWin::EndDragging, true));
358}
359
360void TabContentsDragWin::OnDataObjectDisposed() {
361  DCHECK(drag_drop_thread_id_ == base::PlatformThread::CurrentId());
362
363  // The drag-and-drop thread is only closed after OLE is done with
364  // DataObjectImpl.
365  BrowserThread::PostTask(
366      BrowserThread::UI, FROM_HERE,
367      NewRunnableMethod(this, &TabContentsDragWin::CloseThread));
368}
369