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.
5 #include "content/browser/web_contents/web_contents_drag_win.h"
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"
41 using WebKit::WebDragOperationsMask;
42 using WebKit::WebDragOperationCopy;
43 using WebKit::WebDragOperationLink;
44 using WebKit::WebDragOperationMove;
49 bool run_do_drag_drop = true;
51 HHOOK msg_hook = NULL;
52 DWORD drag_out_thread_id = 0;
53 bool mouse_up_received = false;
55 LRESULT CALLBACK MsgFilterProc(int code, WPARAM wparam, LPARAM lparam) {
56 if (code == base::MessagePumpForUI::kMessageFilterCode &&
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,
69 // If the left button is up, we do not need to forward the message any
71 if (msg->message == WM_LBUTTONUP || !(GetKeyState(VK_LBUTTON) & 0x8000))
72 mouse_up_received = true;
77 return CallNextHookEx(msg_hook, code, wparam, lparam);
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;
86 msg_hook = SetWindowsHookEx(WH_MSGFILTER,
89 GetCurrentThreadId());
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);
96 void DisableBackgroundDraggingSupport() {
98 AttachThreadInput(drag_out_thread_id, GetCurrentThreadId(), FALSE);
99 UnhookWindowsHookEx(msg_hook);
103 bool IsBackgroundDraggingSupportEnabled() {
104 return msg_hook != NULL;
109 class DragDropThread : public base::Thread {
111 explicit DragDropThread(WebContentsDragWin* drag_handler)
112 : Thread("Chrome_DragDropThread"),
113 drag_handler_(drag_handler) {
116 virtual ~DragDropThread() {
121 // base::Thread implementations:
122 virtual void Init() {
123 ole_initializer_.reset(new ui::ScopedOleInitializer());
126 virtual void CleanUp() {
127 ole_initializer_.reset();
131 scoped_ptr<ui::ScopedOleInitializer> ole_initializer_;
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_;
137 DISALLOW_COPY_AND_ASSIGN(DragDropThread);
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),
150 drag_end_callback_(drag_end_callback) {
153 WebContentsDragWin::~WebContentsDragWin() {
154 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
155 DCHECK(!drag_drop_thread_.get());
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));
164 drag_source_ = new WebDragSource(source_window_, web_contents_);
166 const GURL& page_url = web_contents_->GetLastCommittedURL();
167 const std::string& page_encoding = web_contents_->GetEncoding();
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))
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(
185 base::Bind(&WebContentsDragWin::StartBackgroundDragging, this,
186 drop_data, ops, page_url, page_encoding,
187 image, image_offset));
190 EnableBackgroundDraggingSupport(drag_drop_thread_->thread_id());
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();
202 if (DoDragging(drop_data, ops, page_url, page_encoding,
203 image, image_offset)) {
204 BrowserThread::PostTask(
207 base::Bind(&WebContentsDragWin::EndDragging, this));
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(
220 base::Bind(&DisableBackgroundDraggingSupport));
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.
231 base::FilePath file_name;
233 if (!ParseDownloadMetadata(drop_data.download_metadata,
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,
246 UTF16ToUTF8(file_name.value()),
247 UTF16ToUTF8(mime_type),
249 base::FilePath temp_dir_path;
250 if (!file_util::CreateNewTempDirectory(
251 FILE_PATH_LITERAL("chrome_drag"), &temp_dir_path))
253 base::FilePath download_path =
254 temp_dir_path.Append(generated_download_file_name);
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);
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(
266 scoped_ptr<net::FileStream>(),
268 Referrer(page_url, drop_data.referrer_policy),
271 ui::OSExchangeData::DownloadFileInfo file_download(base::FilePath(),
272 download_file.get());
273 data->SetDownloadFileInfo(file_download);
275 // Enable asynchronous operation.
276 ui::OSExchangeDataProviderWin::GetIAsyncOperation(*data)->SetAsyncMode(TRUE);
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);
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()));
295 file_name = file_name.ReplaceExtension(extension);
297 data->SetFileContents(file_name, drop_data.file_contents);
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)) {
307 data->SetURL(drop_data.url, drop_data.url_title);
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;
318 if (!drop_data.download_metadata.empty()) {
319 PrepareDragForDownload(drop_data, &data, page_url, page_encoding);
322 ui::OSExchangeDataProviderWin::GetDataObjectImpl(data)->set_observer(this);
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
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()) {
340 ui::WriteCustomDataToPickle(drop_data.custom_data, &pickle);
341 data.SetPickledData(ui::Clipboard::GetWebCustomDataFormatType(), pickle);
345 if (!image.isNull()) {
346 drag_utils::SetDragImageOnDataObject(image,
347 gfx::Size(image.width(), image.height()), image_offset, &data);
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();
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
362 scoped_refptr<WebDragSource> retain_source(drag_source_);
363 retain_source->set_data(&data);
364 data.SetInDragLoop(true);
366 base::MessageLoop::ScopedNestableTaskAllower allow(
367 base::MessageLoop::current());
368 DoDragDrop(ui::OSExchangeDataProviderWin::GetIDataObject(data),
370 WebDragOpMaskToWinDragOpMask(ops),
372 retain_source->set_data(NULL);
375 // Bail out immediately if the contents view window is gone.
376 if (!IsWindow(native_window))
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
385 drag_source_->set_effect(effect);
390 void WebContentsDragWin::EndDragging() {
391 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
397 if (IsBackgroundDraggingSupportEnabled())
398 DisableBackgroundDraggingSupport();
400 drag_end_callback_.Run();
403 void WebContentsDragWin::CancelDrag() {
404 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
406 drag_source_->CancelDrag();
409 void WebContentsDragWin::CloseThread() {
410 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
412 drag_drop_thread_.reset();
415 void WebContentsDragWin::OnWaitForData() {
416 DCHECK(drag_drop_thread_id_ == base::PlatformThread::CurrentId());
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(
424 base::Bind(&WebContentsDragWin::EndDragging, this));
427 void WebContentsDragWin::OnDataObjectDisposed() {
428 DCHECK(drag_drop_thread_id_ == base::PlatformThread::CurrentId());
430 // The drag-and-drop thread is only closed after OLE is done with
432 BrowserThread::PostTask(
435 base::Bind(&WebContentsDragWin::CloseThread, this));
439 void WebContentsDragWin::DisableDragDropForTesting() {
440 run_do_drag_drop = false;
443 } // namespace content