// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "content/browser/web_contents/web_contents_drag_win.h" #include #include #include "base/bind.h" #include "base/file_util.h" #include "base/files/file_path.h" #include "base/message_loop/message_loop.h" #include "base/pickle.h" #include "base/strings/utf_string_conversions.h" #include "base/threading/platform_thread.h" #include "base/threading/thread.h" #include "content/browser/download/drag_download_file.h" #include "content/browser/download/drag_download_util.h" #include "content/browser/web_contents/web_drag_dest_win.h" #include "content/browser/web_contents/web_drag_source_win.h" #include "content/browser/web_contents/web_drag_utils_win.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/content_browser_client.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents_view.h" #include "content/public/browser/web_drag_dest_delegate.h" #include "content/public/common/drop_data.h" #include "net/base/net_util.h" #include "third_party/skia/include/core/SkBitmap.h" #include "ui/base/clipboard/clipboard.h" #include "ui/base/clipboard/custom_data_helper.h" #include "ui/base/dragdrop/drag_utils.h" #include "ui/base/layout.h" #include "ui/base/win/scoped_ole_initializer.h" #include "ui/gfx/image/image_skia.h" #include "ui/gfx/screen.h" #include "ui/gfx/size.h" using blink::WebDragOperationsMask; using blink::WebDragOperationCopy; using blink::WebDragOperationLink; using blink::WebDragOperationMove; namespace content { namespace { bool run_do_drag_drop = true; HHOOK msg_hook = NULL; DWORD drag_out_thread_id = 0; bool mouse_up_received = false; LRESULT CALLBACK MsgFilterProc(int code, WPARAM wparam, LPARAM lparam) { if (code == base::MessagePumpForUI::kMessageFilterCode && !mouse_up_received) { MSG* msg = reinterpret_cast(lparam); // We do not care about WM_SYSKEYDOWN and WM_SYSKEYUP because when ALT key // is pressed down on drag-and-drop, it means to create a link. if (msg->message == WM_MOUSEMOVE || msg->message == WM_LBUTTONUP || msg->message == WM_KEYDOWN || msg->message == WM_KEYUP) { // Forward the message from the UI thread to the drag-and-drop thread. PostThreadMessage(drag_out_thread_id, msg->message, msg->wParam, msg->lParam); // If the left button is up, we do not need to forward the message any // more. if (msg->message == WM_LBUTTONUP || !(GetKeyState(VK_LBUTTON) & 0x8000)) mouse_up_received = true; return TRUE; } } return CallNextHookEx(msg_hook, code, wparam, lparam); } void EnableBackgroundDraggingSupport(DWORD thread_id) { // Install a hook procedure to monitor the messages so that we can forward // the appropriate ones to the background thread. drag_out_thread_id = thread_id; mouse_up_received = false; DCHECK(!msg_hook); msg_hook = SetWindowsHookEx(WH_MSGFILTER, MsgFilterProc, NULL, GetCurrentThreadId()); // Attach the input state of the background thread to the UI thread so that // SetCursor can work from the background thread. AttachThreadInput(drag_out_thread_id, GetCurrentThreadId(), TRUE); } void DisableBackgroundDraggingSupport() { DCHECK(msg_hook); AttachThreadInput(drag_out_thread_id, GetCurrentThreadId(), FALSE); UnhookWindowsHookEx(msg_hook); msg_hook = NULL; } bool IsBackgroundDraggingSupportEnabled() { return msg_hook != NULL; } } // namespace class DragDropThread : public base::Thread { public: explicit DragDropThread(WebContentsDragWin* drag_handler) : Thread("Chrome_DragDropThread"), drag_handler_(drag_handler) { } virtual ~DragDropThread() { Stop(); } protected: // base::Thread implementations: virtual void Init() { ole_initializer_.reset(new ui::ScopedOleInitializer()); } virtual void CleanUp() { ole_initializer_.reset(); } private: scoped_ptr ole_initializer_; // Hold a reference count to WebContentsDragWin to make sure that it is always // alive in the thread lifetime. scoped_refptr drag_handler_; DISALLOW_COPY_AND_ASSIGN(DragDropThread); }; WebContentsDragWin::WebContentsDragWin( gfx::NativeWindow source_window, WebContents* web_contents, WebDragDest* drag_dest, const base::Callback& drag_end_callback) : drag_drop_thread_id_(0), source_window_(source_window), web_contents_(web_contents), drag_dest_(drag_dest), drag_ended_(false), drag_end_callback_(drag_end_callback) { } WebContentsDragWin::~WebContentsDragWin() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); DCHECK(!drag_drop_thread_.get()); } void WebContentsDragWin::StartDragging(const DropData& drop_data, WebDragOperationsMask ops, const gfx::ImageSkia& image, const gfx::Vector2d& image_offset) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); drag_source_ = new WebDragSource(source_window_, web_contents_); const GURL& page_url = web_contents_->GetLastCommittedURL(); const std::string& page_encoding = web_contents_->GetEncoding(); // If it is not drag-out, do the drag-and-drop in the current UI thread. if (drop_data.download_metadata.empty()) { if (DoDragging(drop_data, ops, page_url, page_encoding, image, image_offset)) EndDragging(); return; } // Start a background thread to do the drag-and-drop. DCHECK(!drag_drop_thread_.get()); drag_drop_thread_.reset(new DragDropThread(this)); base::Thread::Options options; options.message_loop_type = base::MessageLoop::TYPE_UI; if (drag_drop_thread_->StartWithOptions(options)) { drag_drop_thread_->message_loop()->PostTask( FROM_HERE, base::Bind(&WebContentsDragWin::StartBackgroundDragging, this, drop_data, ops, page_url, page_encoding, image, image_offset)); } EnableBackgroundDraggingSupport(drag_drop_thread_->thread_id()); } void WebContentsDragWin::StartBackgroundDragging( const DropData& drop_data, WebDragOperationsMask ops, const GURL& page_url, const std::string& page_encoding, const gfx::ImageSkia& image, const gfx::Vector2d& image_offset) { drag_drop_thread_id_ = base::PlatformThread::CurrentId(); if (DoDragging(drop_data, ops, page_url, page_encoding, image, image_offset)) { BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&WebContentsDragWin::EndDragging, this)); } else { // When DoDragging returns false, the contents view window is gone and thus // WebContentsViewWin instance becomes invalid though WebContentsDragWin // instance is still alive because the task holds a reference count to it. // We should not do more than the following cleanup items: // 1) Remove the background dragging support. This is safe since it does not // access the instance at all. // 2) Stop the background thread. This is done in OnDataObjectDisposed. // Only drag_drop_thread_ member is accessed. BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&DisableBackgroundDraggingSupport)); } } void WebContentsDragWin::PrepareDragForDownload( const DropData& drop_data, ui::OSExchangeData* data, const GURL& page_url, const std::string& page_encoding) { // Parse the download metadata. string16 mime_type; base::FilePath file_name; GURL download_url; if (!ParseDownloadMetadata(drop_data.download_metadata, &mime_type, &file_name, &download_url)) return; // Generate the file name based on both mime type and proposed file name. std::string default_name = GetContentClient()->browser()->GetDefaultDownloadName(); base::FilePath generated_download_file_name = net::GenerateFileName(download_url, std::string(), std::string(), UTF16ToUTF8(file_name.value()), UTF16ToUTF8(mime_type), default_name); base::FilePath temp_dir_path; if (!base::CreateNewTempDirectory(FILE_PATH_LITERAL("chrome_drag"), &temp_dir_path)) return; base::FilePath download_path = temp_dir_path.Append(generated_download_file_name); // We cannot know when the target application will be done using the temporary // file, so schedule it to be deleted after rebooting. base::DeleteFileAfterReboot(download_path); base::DeleteFileAfterReboot(temp_dir_path); // Provide the data as file (CF_HDROP). A temporary download file with the // Zone.Identifier ADS (Alternate Data Stream) attached will be created. scoped_refptr download_file = new DragDownloadFile( download_path, scoped_ptr(), download_url, Referrer(page_url, drop_data.referrer_policy), page_encoding, web_contents_); ui::OSExchangeData::DownloadFileInfo file_download(base::FilePath(), download_file.get()); data->SetDownloadFileInfo(file_download); // Enable asynchronous operation. ui::OSExchangeDataProviderWin::GetIAsyncOperation(*data)->SetAsyncMode(TRUE); } void WebContentsDragWin::PrepareDragForFileContents( const DropData& drop_data, ui::OSExchangeData* data) { static const int kMaxFilenameLength = 255; // FAT and NTFS base::FilePath file_name(drop_data.file_description_filename); // Images without ALT text will only have a file extension so we need to // synthesize one from the provided extension and URL. if (file_name.BaseName().RemoveExtension().empty()) { const string16 extension = file_name.Extension(); // Retrieve the name from the URL. file_name = base::FilePath( net::GetSuggestedFilename(drop_data.url, "", "", "", "", "")); if (file_name.value().size() + extension.size() > kMaxFilenameLength) { file_name = base::FilePath(file_name.value().substr( 0, kMaxFilenameLength - extension.size())); } file_name = file_name.ReplaceExtension(extension); } data->SetFileContents(file_name, drop_data.file_contents); } void WebContentsDragWin::PrepareDragForUrl(const DropData& drop_data, ui::OSExchangeData* data) { if (drag_dest_->delegate() && drag_dest_->delegate()->AddDragData(drop_data, data)) { return; } data->SetURL(drop_data.url, drop_data.url_title); } bool WebContentsDragWin::DoDragging(const DropData& drop_data, WebDragOperationsMask ops, const GURL& page_url, const std::string& page_encoding, const gfx::ImageSkia& image, const gfx::Vector2d& image_offset) { ui::OSExchangeData data; if (!drop_data.download_metadata.empty()) { PrepareDragForDownload(drop_data, &data, page_url, page_encoding); // Set the observer. ui::OSExchangeDataProviderWin::GetDataObjectImpl(data)->set_observer(this); } // We set the file contents before the URL because the URL also sets file // contents (to a .URL shortcut). We want to prefer file content data over // a shortcut so we add it first. if (!drop_data.file_contents.empty()) PrepareDragForFileContents(drop_data, &data); if (!drop_data.html.string().empty()) data.SetHtml(drop_data.html.string(), drop_data.html_base_url); // We set the text contents before the URL because the URL also sets text // content. if (!drop_data.text.string().empty()) data.SetString(drop_data.text.string()); if (drop_data.url.is_valid()) PrepareDragForUrl(drop_data, &data); if (!drop_data.custom_data.empty()) { Pickle pickle; ui::WriteCustomDataToPickle(drop_data.custom_data, &pickle); data.SetPickledData(ui::Clipboard::GetWebCustomDataFormatType(), pickle); } // Set drag image. if (!image.isNull()) { drag_utils::SetDragImageOnDataObject(image, gfx::Size(image.width(), image.height()), image_offset, &data); } // Use a local variable to keep track of the contents view window handle. // It might not be safe to access the instance after DoDragDrop returns // because the window could be disposed in the nested message loop. HWND native_window = web_contents_->GetView()->GetNativeView(); // We need to enable recursive tasks on the message loop so we can get // updates while in the system DoDragDrop loop. DWORD effect = DROPEFFECT_NONE; if (run_do_drag_drop) { // Keep a reference count such that |drag_source_| will not get deleted // if the contents view window is gone in the nested message loop invoked // from DoDragDrop. scoped_refptr retain_source(drag_source_); retain_source->set_data(&data); data.SetInDragLoop(true); base::MessageLoop::ScopedNestableTaskAllower allow( base::MessageLoop::current()); DoDragDrop(ui::OSExchangeDataProviderWin::GetIDataObject(data), drag_source_, WebDragOpMaskToWinDragOpMask(ops), &effect); retain_source->set_data(NULL); } // Bail out immediately if the contents view window is gone. if (!IsWindow(native_window)) return false; // Normally, the drop and dragend events get dispatched in the system // DoDragDrop message loop so it'd be too late to set the effect to send back // to the renderer here. However, we use PostTask to delay the execution of // WebDragSource::OnDragSourceDrop, which means that the delayed dragend // callback to the renderer doesn't run until this has been set to the correct // value. drag_source_->set_effect(effect); return true; } void WebContentsDragWin::EndDragging() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); if (drag_ended_) return; drag_ended_ = true; if (IsBackgroundDraggingSupportEnabled()) DisableBackgroundDraggingSupport(); drag_end_callback_.Run(); } void WebContentsDragWin::CancelDrag() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); drag_source_->CancelDrag(); } void WebContentsDragWin::CloseThread() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); drag_drop_thread_.reset(); } void WebContentsDragWin::OnWaitForData() { DCHECK(drag_drop_thread_id_ == base::PlatformThread::CurrentId()); // When the left button is release and we start to wait for the data, end // the dragging before DoDragDrop returns. This makes the page leave the drag // mode so that it can start to process the normal input events. BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&WebContentsDragWin::EndDragging, this)); } void WebContentsDragWin::OnDataObjectDisposed() { DCHECK(drag_drop_thread_id_ == base::PlatformThread::CurrentId()); // The drag-and-drop thread is only closed after OLE is done with // DataObjectImpl. BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&WebContentsDragWin::CloseThread, this)); } // static void WebContentsDragWin::DisableDragDropForTesting() { run_do_drag_drop = false; } } // namespace content