- add sources.
[platform/framework/web/crosswalk.git] / src / content / browser / web_contents / web_contents_drag_win.cc
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_contents_drag_win.h"
6
7 #include <windows.h>
8
9 #include <string>
10
11 #include "base/bind.h"
12 #include "base/file_util.h"
13 #include "base/files/file_path.h"
14 #include "base/message_loop/message_loop.h"
15 #include "base/pickle.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "base/threading/platform_thread.h"
18 #include "base/threading/thread.h"
19 #include "content/browser/download/drag_download_file.h"
20 #include "content/browser/download/drag_download_util.h"
21 #include "content/browser/web_contents/web_drag_dest_win.h"
22 #include "content/browser/web_contents/web_drag_source_win.h"
23 #include "content/browser/web_contents/web_drag_utils_win.h"
24 #include "content/public/browser/browser_thread.h"
25 #include "content/public/browser/content_browser_client.h"
26 #include "content/public/browser/web_contents.h"
27 #include "content/public/browser/web_contents_view.h"
28 #include "content/public/browser/web_drag_dest_delegate.h"
29 #include "content/public/common/drop_data.h"
30 #include "net/base/net_util.h"
31 #include "third_party/skia/include/core/SkBitmap.h"
32 #include "ui/base/clipboard/clipboard.h"
33 #include "ui/base/clipboard/custom_data_helper.h"
34 #include "ui/base/dragdrop/drag_utils.h"
35 #include "ui/base/layout.h"
36 #include "ui/base/win/scoped_ole_initializer.h"
37 #include "ui/gfx/image/image_skia.h"
38 #include "ui/gfx/screen.h"
39 #include "ui/gfx/size.h"
40
41 using WebKit::WebDragOperationsMask;
42 using WebKit::WebDragOperationCopy;
43 using WebKit::WebDragOperationLink;
44 using WebKit::WebDragOperationMove;
45
46 namespace content {
47 namespace {
48
49 bool run_do_drag_drop = true;
50
51 HHOOK msg_hook = NULL;
52 DWORD drag_out_thread_id = 0;
53 bool mouse_up_received = false;
54
55 LRESULT CALLBACK MsgFilterProc(int code, WPARAM wparam, LPARAM lparam) {
56   if (code == base::MessagePumpForUI::kMessageFilterCode &&
57       !mouse_up_received) {
58     MSG* msg = reinterpret_cast<MSG*>(lparam);
59     // We do not care about WM_SYSKEYDOWN and WM_SYSKEYUP because when ALT key
60     // is pressed down on drag-and-drop, it means to create a link.
61     if (msg->message == WM_MOUSEMOVE || msg->message == WM_LBUTTONUP ||
62         msg->message == WM_KEYDOWN || msg->message == WM_KEYUP) {
63       // Forward the message from the UI thread to the drag-and-drop thread.
64       PostThreadMessage(drag_out_thread_id,
65                         msg->message,
66                         msg->wParam,
67                         msg->lParam);
68
69       // If the left button is up, we do not need to forward the message any
70       // more.
71       if (msg->message == WM_LBUTTONUP || !(GetKeyState(VK_LBUTTON) & 0x8000))
72         mouse_up_received = true;
73
74       return TRUE;
75     }
76   }
77   return CallNextHookEx(msg_hook, code, wparam, lparam);
78 }
79
80 void EnableBackgroundDraggingSupport(DWORD thread_id) {
81   // Install a hook procedure to monitor the messages so that we can forward
82   // the appropriate ones to the background thread.
83   drag_out_thread_id = thread_id;
84   mouse_up_received = false;
85   DCHECK(!msg_hook);
86   msg_hook = SetWindowsHookEx(WH_MSGFILTER,
87                               MsgFilterProc,
88                               NULL,
89                               GetCurrentThreadId());
90
91   // Attach the input state of the background thread to the UI thread so that
92   // SetCursor can work from the background thread.
93   AttachThreadInput(drag_out_thread_id, GetCurrentThreadId(), TRUE);
94 }
95
96 void DisableBackgroundDraggingSupport() {
97   DCHECK(msg_hook);
98   AttachThreadInput(drag_out_thread_id, GetCurrentThreadId(), FALSE);
99   UnhookWindowsHookEx(msg_hook);
100   msg_hook = NULL;
101 }
102
103 bool IsBackgroundDraggingSupportEnabled() {
104   return msg_hook != NULL;
105 }
106
107 }  // namespace
108
109 class DragDropThread : public base::Thread {
110  public:
111   explicit DragDropThread(WebContentsDragWin* drag_handler)
112        : Thread("Chrome_DragDropThread"),
113          drag_handler_(drag_handler) {
114   }
115
116   virtual ~DragDropThread() {
117     Stop();
118   }
119
120  protected:
121   // base::Thread implementations:
122   virtual void Init() {
123     ole_initializer_.reset(new ui::ScopedOleInitializer());
124   }
125
126   virtual void CleanUp() {
127     ole_initializer_.reset();
128   }
129
130  private:
131   scoped_ptr<ui::ScopedOleInitializer> ole_initializer_;
132
133   // Hold a reference count to WebContentsDragWin to make sure that it is always
134   // alive in the thread lifetime.
135   scoped_refptr<WebContentsDragWin> drag_handler_;
136
137   DISALLOW_COPY_AND_ASSIGN(DragDropThread);
138 };
139
140 WebContentsDragWin::WebContentsDragWin(
141     gfx::NativeWindow source_window,
142     WebContents* web_contents,
143     WebDragDest* drag_dest,
144     const base::Callback<void()>& drag_end_callback)
145     : drag_drop_thread_id_(0),
146       source_window_(source_window),
147       web_contents_(web_contents),
148       drag_dest_(drag_dest),
149       drag_ended_(false),
150       drag_end_callback_(drag_end_callback) {
151 }
152
153 WebContentsDragWin::~WebContentsDragWin() {
154   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
155   DCHECK(!drag_drop_thread_.get());
156 }
157
158 void WebContentsDragWin::StartDragging(const DropData& drop_data,
159                                        WebDragOperationsMask ops,
160                                        const gfx::ImageSkia& image,
161                                        const gfx::Vector2d& image_offset) {
162   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
163
164   drag_source_ = new WebDragSource(source_window_, web_contents_);
165
166   const GURL& page_url = web_contents_->GetLastCommittedURL();
167   const std::string& page_encoding = web_contents_->GetEncoding();
168
169   // If it is not drag-out, do the drag-and-drop in the current UI thread.
170   if (drop_data.download_metadata.empty()) {
171     if (DoDragging(drop_data, ops, page_url, page_encoding,
172                    image, image_offset))
173       EndDragging();
174     return;
175   }
176
177   // Start a background thread to do the drag-and-drop.
178   DCHECK(!drag_drop_thread_.get());
179   drag_drop_thread_.reset(new DragDropThread(this));
180   base::Thread::Options options;
181   options.message_loop_type = base::MessageLoop::TYPE_UI;
182   if (drag_drop_thread_->StartWithOptions(options)) {
183     drag_drop_thread_->message_loop()->PostTask(
184         FROM_HERE,
185         base::Bind(&WebContentsDragWin::StartBackgroundDragging, this,
186                    drop_data, ops, page_url, page_encoding,
187                    image, image_offset));
188   }
189
190   EnableBackgroundDraggingSupport(drag_drop_thread_->thread_id());
191 }
192
193 void WebContentsDragWin::StartBackgroundDragging(
194     const DropData& drop_data,
195     WebDragOperationsMask ops,
196     const GURL& page_url,
197     const std::string& page_encoding,
198     const gfx::ImageSkia& image,
199     const gfx::Vector2d& image_offset) {
200   drag_drop_thread_id_ = base::PlatformThread::CurrentId();
201
202   if (DoDragging(drop_data, ops, page_url, page_encoding,
203                  image, image_offset)) {
204     BrowserThread::PostTask(
205         BrowserThread::UI,
206         FROM_HERE,
207         base::Bind(&WebContentsDragWin::EndDragging, this));
208   } else {
209     // When DoDragging returns false, the contents view window is gone and thus
210     // WebContentsViewWin instance becomes invalid though WebContentsDragWin
211     // instance is still alive because the task holds a reference count to it.
212     // We should not do more than the following cleanup items:
213     // 1) Remove the background dragging support. This is safe since it does not
214     //    access the instance at all.
215     // 2) Stop the background thread. This is done in OnDataObjectDisposed.
216     //    Only drag_drop_thread_ member is accessed.
217     BrowserThread::PostTask(
218         BrowserThread::UI,
219         FROM_HERE,
220         base::Bind(&DisableBackgroundDraggingSupport));
221   }
222 }
223
224 void WebContentsDragWin::PrepareDragForDownload(
225     const DropData& drop_data,
226     ui::OSExchangeData* data,
227     const GURL& page_url,
228     const std::string& page_encoding) {
229   // Parse the download metadata.
230   string16 mime_type;
231   base::FilePath file_name;
232   GURL download_url;
233   if (!ParseDownloadMetadata(drop_data.download_metadata,
234                              &mime_type,
235                              &file_name,
236                              &download_url))
237     return;
238
239   // Generate the file name based on both mime type and proposed file name.
240   std::string default_name =
241       GetContentClient()->browser()->GetDefaultDownloadName();
242   base::FilePath generated_download_file_name =
243       net::GenerateFileName(download_url,
244                             std::string(),
245                             std::string(),
246                             UTF16ToUTF8(file_name.value()),
247                             UTF16ToUTF8(mime_type),
248                             default_name);
249   base::FilePath temp_dir_path;
250   if (!file_util::CreateNewTempDirectory(
251           FILE_PATH_LITERAL("chrome_drag"), &temp_dir_path))
252     return;
253   base::FilePath download_path =
254       temp_dir_path.Append(generated_download_file_name);
255
256   // We cannot know when the target application will be done using the temporary
257   // file, so schedule it to be deleted after rebooting.
258   base::DeleteFileAfterReboot(download_path);
259   base::DeleteFileAfterReboot(temp_dir_path);
260
261   // Provide the data as file (CF_HDROP). A temporary download file with the
262   // Zone.Identifier ADS (Alternate Data Stream) attached will be created.
263   scoped_refptr<DragDownloadFile> download_file =
264       new DragDownloadFile(
265           download_path,
266           scoped_ptr<net::FileStream>(),
267           download_url,
268           Referrer(page_url, drop_data.referrer_policy),
269           page_encoding,
270           web_contents_);
271   ui::OSExchangeData::DownloadFileInfo file_download(base::FilePath(),
272                                                      download_file.get());
273   data->SetDownloadFileInfo(file_download);
274
275   // Enable asynchronous operation.
276   ui::OSExchangeDataProviderWin::GetIAsyncOperation(*data)->SetAsyncMode(TRUE);
277 }
278
279 void WebContentsDragWin::PrepareDragForFileContents(
280     const DropData& drop_data, ui::OSExchangeData* data) {
281   static const int kMaxFilenameLength = 255;  // FAT and NTFS
282   base::FilePath file_name(drop_data.file_description_filename);
283
284   // Images without ALT text will only have a file extension so we need to
285   // synthesize one from the provided extension and URL.
286   if (file_name.BaseName().RemoveExtension().empty()) {
287     const string16 extension = file_name.Extension();
288     // Retrieve the name from the URL.
289     file_name = base::FilePath(
290         net::GetSuggestedFilename(drop_data.url, "", "", "", "", ""));
291     if (file_name.value().size() + extension.size() > kMaxFilenameLength) {
292       file_name = base::FilePath(file_name.value().substr(
293           0, kMaxFilenameLength - extension.size()));
294     }
295     file_name = file_name.ReplaceExtension(extension);
296   }
297   data->SetFileContents(file_name, drop_data.file_contents);
298 }
299
300 void WebContentsDragWin::PrepareDragForUrl(const DropData& drop_data,
301                                            ui::OSExchangeData* data) {
302   if (drag_dest_->delegate() &&
303       drag_dest_->delegate()->AddDragData(drop_data, data)) {
304     return;
305   }
306
307   data->SetURL(drop_data.url, drop_data.url_title);
308 }
309
310 bool WebContentsDragWin::DoDragging(const DropData& drop_data,
311                                     WebDragOperationsMask ops,
312                                     const GURL& page_url,
313                                     const std::string& page_encoding,
314                                     const gfx::ImageSkia& image,
315                                     const gfx::Vector2d& image_offset) {
316   ui::OSExchangeData data;
317
318   if (!drop_data.download_metadata.empty()) {
319     PrepareDragForDownload(drop_data, &data, page_url, page_encoding);
320
321     // Set the observer.
322     ui::OSExchangeDataProviderWin::GetDataObjectImpl(data)->set_observer(this);
323   }
324
325   // We set the file contents before the URL because the URL also sets file
326   // contents (to a .URL shortcut).  We want to prefer file content data over
327   // a shortcut so we add it first.
328   if (!drop_data.file_contents.empty())
329     PrepareDragForFileContents(drop_data, &data);
330   if (!drop_data.html.string().empty())
331     data.SetHtml(drop_data.html.string(), drop_data.html_base_url);
332   // We set the text contents before the URL because the URL also sets text
333   // content.
334   if (!drop_data.text.string().empty())
335     data.SetString(drop_data.text.string());
336   if (drop_data.url.is_valid())
337     PrepareDragForUrl(drop_data, &data);
338   if (!drop_data.custom_data.empty()) {
339     Pickle pickle;
340     ui::WriteCustomDataToPickle(drop_data.custom_data, &pickle);
341     data.SetPickledData(ui::Clipboard::GetWebCustomDataFormatType(), pickle);
342   }
343
344   // Set drag image.
345   if (!image.isNull()) {
346     drag_utils::SetDragImageOnDataObject(image,
347         gfx::Size(image.width(), image.height()), image_offset, &data);
348   }
349
350   // Use a local variable to keep track of the contents view window handle.
351   // It might not be safe to access the instance after DoDragDrop returns
352   // because the window could be disposed in the nested message loop.
353   HWND native_window = web_contents_->GetView()->GetNativeView();
354
355   // We need to enable recursive tasks on the message loop so we can get
356   // updates while in the system DoDragDrop loop.
357   DWORD effect = DROPEFFECT_NONE;
358   if (run_do_drag_drop) {
359     // Keep a reference count such that |drag_source_| will not get deleted
360     // if the contents view window is gone in the nested message loop invoked
361     // from DoDragDrop.
362     scoped_refptr<WebDragSource> retain_source(drag_source_);
363     retain_source->set_data(&data);
364     data.SetInDragLoop(true);
365
366     base::MessageLoop::ScopedNestableTaskAllower allow(
367         base::MessageLoop::current());
368     DoDragDrop(ui::OSExchangeDataProviderWin::GetIDataObject(data),
369                drag_source_,
370                WebDragOpMaskToWinDragOpMask(ops),
371                &effect);
372     retain_source->set_data(NULL);
373   }
374
375   // Bail out immediately if the contents view window is gone.
376   if (!IsWindow(native_window))
377     return false;
378
379   // Normally, the drop and dragend events get dispatched in the system
380   // DoDragDrop message loop so it'd be too late to set the effect to send back
381   // to the renderer here. However, we use PostTask to delay the execution of
382   // WebDragSource::OnDragSourceDrop, which means that the delayed dragend
383   // callback to the renderer doesn't run until this has been set to the correct
384   // value.
385   drag_source_->set_effect(effect);
386
387   return true;
388 }
389
390 void WebContentsDragWin::EndDragging() {
391   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
392
393   if (drag_ended_)
394     return;
395   drag_ended_ = true;
396
397   if (IsBackgroundDraggingSupportEnabled())
398     DisableBackgroundDraggingSupport();
399
400   drag_end_callback_.Run();
401 }
402
403 void WebContentsDragWin::CancelDrag() {
404   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
405
406   drag_source_->CancelDrag();
407 }
408
409 void WebContentsDragWin::CloseThread() {
410   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
411
412   drag_drop_thread_.reset();
413 }
414
415 void WebContentsDragWin::OnWaitForData() {
416   DCHECK(drag_drop_thread_id_ == base::PlatformThread::CurrentId());
417
418   // When the left button is release and we start to wait for the data, end
419   // the dragging before DoDragDrop returns. This makes the page leave the drag
420   // mode so that it can start to process the normal input events.
421   BrowserThread::PostTask(
422       BrowserThread::UI,
423       FROM_HERE,
424       base::Bind(&WebContentsDragWin::EndDragging, this));
425 }
426
427 void WebContentsDragWin::OnDataObjectDisposed() {
428   DCHECK(drag_drop_thread_id_ == base::PlatformThread::CurrentId());
429
430   // The drag-and-drop thread is only closed after OLE is done with
431   // DataObjectImpl.
432   BrowserThread::PostTask(
433       BrowserThread::UI,
434       FROM_HERE,
435       base::Bind(&WebContentsDragWin::CloseThread, this));
436 }
437
438 // static
439 void WebContentsDragWin::DisableDragDropForTesting() {
440   run_do_drag_drop = false;
441 }
442
443 }  // namespace content