// Copyright (c) 2011 The Chromium Embedded Framework Authors. // Portions copyright (c) 2011 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 "libcef/browser_drag_delegate_win.h" #include #include #include "libcef/browser_impl.h" #include "libcef/browser_webview_delegate.h" #include "libcef/cef_thread.h" #include "libcef/drag_download_file.h" #include "libcef/drag_download_util.h" #include "libcef/download_util.h" #include "libcef/web_drag_source_win.h" #include "libcef/web_drag_utils_win.h" #include "libcef/web_drop_target_win.h" #include "base/bind.h" #include "base/pickle.h" #include "base/utf_string_conversions.h" #include "net/base/file_stream.h" #include "net/base/net_util.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebDocument.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h" #include "ui/base/clipboard/clipboard_util_win.h" #include "ui/base/clipboard/custom_data_helper.h" #include "ui/base/dragdrop/drag_utils.h" #include "ui/gfx/image/image_skia.h" #include "webkit/glue/webdropdata.h" using WebKit::WebDragOperationsMask; using WebKit::WebDragOperationCopy; using WebKit::WebDragOperationLink; using WebKit::WebDragOperationMove; using WebKit::WebView; namespace { 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); } } // namespace class DragDropThread : public base::Thread { public: explicit DragDropThread(BrowserDragDelegate* drag_handler) : base::Thread("Chrome_DragDropThread"), drag_handler_(drag_handler) { } virtual ~DragDropThread() { Thread::Stop(); } protected: // base::Thread implementations: virtual void Init() { int ole_result = OleInitialize(NULL); DCHECK(ole_result == S_OK); } virtual void CleanUp() { OleUninitialize(); } private: // Hold a reference count to BrowserDragDelegate to make sure that it is // always alive in the thread lifetime. scoped_refptr drag_handler_; DISALLOW_COPY_AND_ASSIGN(DragDropThread); }; BrowserDragDelegate::BrowserDragDelegate(BrowserWebViewDelegate* view) : drag_drop_thread_id_(0), view_(view), drag_ended_(false) { } BrowserDragDelegate::~BrowserDragDelegate() { DCHECK(CefThread::CurrentlyOn(CefThread::UI)); DCHECK(!drag_drop_thread_.get()); } void BrowserDragDelegate::StartDragging(const WebDropData& drop_data, WebDragOperationsMask ops, const gfx::ImageSkia& image, const gfx::Vector2d& image_offset) { DCHECK(CefThread::CurrentlyOn(CefThread::UI)); CefBrowserImpl* browser = view_->GetBrowser(); WebView* web_view = browser->UIT_GetWebView(); drag_source_ = new WebDragSource(browser->UIT_GetWebViewWndHandle(), web_view); const GURL& page_url = web_view->mainFrame()->document().url(); const std::string& page_encoding = web_view->mainFrame()->document().encoding().utf8(); // If it is not drag-out, do the drag-and-drop in the current UI thread. if (drop_data.download_metadata.empty()) { DoDragging(drop_data, ops, page_url, page_encoding, image, image_offset); CefThread::PostTask( CefThread::UI, FROM_HERE, base::Bind(&BrowserDragDelegate::EndDragging, this)); 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 = MessageLoop::TYPE_UI; if (drag_drop_thread_->StartWithOptions(options)) { drag_drop_thread_->message_loop()->PostTask( FROM_HERE, base::Bind(&BrowserDragDelegate::StartBackgroundDragging, this, drop_data, ops, page_url, page_encoding, image, image_offset)); } // Install a hook procedure to monitor the messages so that we can forward // the appropriate ones to the background thread. drag_out_thread_id = drag_drop_thread_->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 BrowserDragDelegate::StartBackgroundDragging( const WebDropData& 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(); DoDragging(drop_data, ops, page_url, page_encoding, image, image_offset); CefThread::PostTask( CefThread::UI, FROM_HERE, base::Bind(&BrowserDragDelegate::EndDragging, this)); } void BrowserDragDelegate::PrepareDragForDownload( const WebDropData& 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 (!drag_download_util::ParseDownloadMetadata(drop_data.download_metadata, &mime_type, &file_name, &download_url)) return; // Generate the download filename. std::string content_disposition = "attachment; filename=" + UTF16ToUTF8(file_name.value()); base::FilePath generated_file_name; download_util::GenerateFileName(download_url, content_disposition, std::string(), UTF16ToUTF8(mime_type), std::string(), &generated_file_name); // Provide the data as file (CF_HDROP). A temporary download file with the // Zone.Identifier ADS (Alternate Data Stream) attached will be created. scoped_ptr empty_file_stream; scoped_refptr download_file = new DragDownloadFile(generated_file_name, empty_file_stream.Pass(), download_url, page_url, page_encoding, view_); ui::OSExchangeData::DownloadFileInfo file_download(base::FilePath(), download_file.get()); data->SetDownloadFileInfo(file_download); // Enable asynchronous operation. ui::OSExchangeDataProviderWin::GetIAsyncOperation(*data)->SetAsyncMode(TRUE); } void BrowserDragDelegate::PrepareDragForFileContents( const WebDropData& drop_data, ui::OSExchangeData* data) { static const int kMaxFilenameLength = 255; // FAT and NTFS base::FilePath file_name(drop_data.file_description_filename); string16 extension = file_name.Extension(); file_name = file_name.BaseName().RemoveExtension(); // 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.value().empty()) { // 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 BrowserDragDelegate::PrepareDragForUrl(const WebDropData& drop_data, ui::OSExchangeData* data) { if (drop_data.url.SchemeIs("javascript")) { // We don't want to allow javascript URLs to be dragged to the desktop. } else { data->SetURL(drop_data.url, drop_data.url_title); } } void BrowserDragDelegate::DoDragging(const WebDropData& 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::ClipboardUtil::GetWebCustomDataFormat()->cfFormat, pickle); } // Set drag image. if (!image.isNull()) { drag_utils::SetDragImageOnDataObject( gfx::ImageSkia(image), gfx::Size(image.width(), image.height()), image_offset, &data); } // We need to enable recursive tasks on the message loop so we can get // updates while in the system DoDragDrop loop. DWORD effect; { MessageLoop::ScopedNestableTaskAllower allow(MessageLoop::current()); DoDragDrop(ui::OSExchangeDataProviderWin::GetIDataObject(data), drag_source_, web_drag_utils_win::WebDragOpMaskToWinDragOpMask(ops), &effect); } // 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); } void BrowserDragDelegate::EndDragging() { DCHECK(CefThread::CurrentlyOn(CefThread::UI)); if (drag_ended_) return; drag_ended_ = true; if (msg_hook) { AttachThreadInput(drag_out_thread_id, GetCurrentThreadId(), FALSE); UnhookWindowsHookEx(msg_hook); msg_hook = NULL; } view_->EndDragging(); } void BrowserDragDelegate::CancelDrag() { DCHECK(CefThread::CurrentlyOn(CefThread::UI)); drag_source_->CancelDrag(); } void BrowserDragDelegate::CloseThread() { DCHECK(CefThread::CurrentlyOn(CefThread::UI)); drag_drop_thread_.reset(); } void BrowserDragDelegate::OnWaitForData() { DCHECK(drag_drop_thread_id_ == base::PlatformThread::CurrentId()); // When the left button is released 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. CefThread::PostTask( CefThread::UI, FROM_HERE, base::Bind(&BrowserDragDelegate::EndDragging, this)); } void BrowserDragDelegate::OnDataObjectDisposed() { DCHECK(drag_drop_thread_id_ == base::PlatformThread::CurrentId()); // The drag-and-drop thread is only closed after OLE is done with // DataObjectImpl. CefThread::PostTask( CefThread::UI, FROM_HERE, base::Bind(&BrowserDragDelegate::CloseThread, this)); }