diff options
Diffstat (limited to 'content/browser/web_contents/web_contents_drag_win.cc')
-rw-r--r-- | content/browser/web_contents/web_contents_drag_win.cc | 371 |
1 files changed, 371 insertions, 0 deletions
diff --git a/content/browser/web_contents/web_contents_drag_win.cc b/content/browser/web_contents/web_contents_drag_win.cc new file mode 100644 index 0000000..5ef13e5 --- /dev/null +++ b/content/browser/web_contents/web_contents_drag_win.cc @@ -0,0 +1,371 @@ +// 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 <windows.h> + +#include <string> + +#include "base/bind.h" +#include "base/file_path.h" +#include "base/message_loop.h" +#include "base/pickle.h" +#include "base/threading/platform_thread.h" +#include "base/threading/thread.h" +#include "base/utf_string_conversions.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_drag_dest_delegate.h" +#include "net/base/net_util.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/size.h" +#include "webkit/glue/webdropdata.h" + +using content::BrowserThread; +using WebKit::WebDragOperationsMask; +using WebKit::WebDragOperationCopy; +using WebKit::WebDragOperationLink; +using WebKit::WebDragOperationMove; + +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<MSG*>(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(WebContentsDragWin* 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 WebContentsDragWin to make sure that it is always + // alive in the thread lifetime. + scoped_refptr<WebContentsDragWin> drag_handler_; + + DISALLOW_COPY_AND_ASSIGN(DragDropThread); +}; + +WebContentsDragWin::WebContentsDragWin( + gfx::NativeWindow source_window, + content::WebContents* web_contents, + WebDragDest* drag_dest, + const base::Callback<void()>& drag_end_callback) + : drag_drop_thread_id_(0), + source_window_(source_window), + web_contents_(web_contents), + drag_dest_(drag_dest), + drag_ended_(false), + old_drop_target_suspended_state_(false), + drag_end_callback_(drag_end_callback) { +} + +WebContentsDragWin::~WebContentsDragWin() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + DCHECK(!drag_drop_thread_.get()); +} + +void WebContentsDragWin::StartDragging(const WebDropData& drop_data, + WebDragOperationsMask ops, + const SkBitmap& image, + const gfx::Point& image_offset) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + drag_source_ = new WebDragSource(source_window_, web_contents_); + + const GURL& page_url = web_contents_->GetURL(); + 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()) { + DoDragging(drop_data, ops, page_url, page_encoding, image, image_offset); + EndDragging(false); + return; + } + + // We do not want to drag and drop the download to itself. + old_drop_target_suspended_state_ = drag_dest_->suspended(); + drag_dest_->set_suspended(true); + + // 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(&WebContentsDragWin::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 WebContentsDragWin::StartBackgroundDragging( + const WebDropData& drop_data, + WebDragOperationsMask ops, + const GURL& page_url, + const std::string& page_encoding, + const SkBitmap& image, + const gfx::Point& image_offset) { + drag_drop_thread_id_ = base::PlatformThread::CurrentId(); + + DoDragging(drop_data, ops, page_url, page_encoding, image, image_offset); + BrowserThread::PostTask( + BrowserThread::UI, + FROM_HERE, + base::Bind(&WebContentsDragWin::EndDragging, this, true)); +} + +void WebContentsDragWin::PrepareDragForDownload( + const WebDropData& drop_data, + ui::OSExchangeData* data, + const GURL& page_url, + const std::string& page_encoding) { + // Parse the download metadata. + string16 mime_type; + FilePath file_name; + GURL download_url; + if (!drag_download_util::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 = + content::GetContentClient()->browser()->GetDefaultDownloadName(); + FilePath generated_download_file_name = + net::GenerateFileName(download_url, + std::string(), + std::string(), + UTF16ToUTF8(file_name.value()), + UTF16ToUTF8(mime_type), + default_name); + + // Provide the data as file (CF_HDROP). A temporary download file with the + // Zone.Identifier ADS (Alternate Data Stream) attached will be created. + linked_ptr<net::FileStream> empty_file_stream; + scoped_refptr<DragDownloadFile> download_file = + new DragDownloadFile(generated_download_file_name, + empty_file_stream, + download_url, + page_url, + page_encoding, + web_contents_); + ui::OSExchangeData::DownloadFileInfo file_download(FilePath(), + download_file.get()); + data->SetDownloadFileInfo(file_download); + + // Enable asynchronous operation. + ui::OSExchangeDataProviderWin::GetIAsyncOperation(*data)->SetAsyncMode(TRUE); +} + +void WebContentsDragWin::PrepareDragForFileContents( + const WebDropData& drop_data, ui::OSExchangeData* data) { + static const int kMaxFilenameLength = 255; // FAT and NTFS + 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 = FilePath( + net::GetSuggestedFilename(drop_data.url, "", "", "", "", "")); + if (file_name.value().size() + extension.size() > kMaxFilenameLength) { + file_name = 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 WebDropData& drop_data, + ui::OSExchangeData* data) { + if (drag_dest_->delegate()->AddDragData(drop_data, data)) + return; + + data->SetURL(drop_data.url, drop_data.url_title); +} + +void WebContentsDragWin::DoDragging(const WebDropData& drop_data, + WebDragOperationsMask ops, + const GURL& page_url, + const std::string& page_encoding, + const SkBitmap& image, + const gfx::Point& image_offset) { + ui::OSExchangeData data; + + // TODO(dcheng): Figure out why this is mutually exclusive. + if (!drop_data.download_metadata.empty()) { + PrepareDragForDownload(drop_data, &data, page_url, page_encoding); + + // Set the observer. + ui::OSExchangeDataProviderWin::GetDataObjectImpl(data)->set_observer(this); + } else { + // 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.text_html.empty()) + data.SetHtml(drop_data.text_html, drop_data.html_base_url); + // We set the text contents before the URL because the URL also sets text + // content. + if (!drop_data.plain_text.empty()) + data.SetString(drop_data.plain_text); + 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( + 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); + } + + // This works because WebDragSource::OnDragSourceDrop uses PostTask to + // dispatch the actual event. + drag_source_->set_effect(effect); +} + +void WebContentsDragWin::EndDragging(bool restore_suspended_state) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + if (drag_ended_) + return; + drag_ended_ = true; + + if (restore_suspended_state) + drag_dest_->set_suspended(old_drop_target_suspended_state_); + + if (msg_hook) { + AttachThreadInput(drag_out_thread_id, GetCurrentThreadId(), FALSE); + UnhookWindowsHookEx(msg_hook); + msg_hook = NULL; + } + + 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, true)); +} + +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)); +} |