summaryrefslogtreecommitdiffstats
path: root/content/browser/web_contents/web_contents_drag_win.cc
diff options
context:
space:
mode:
Diffstat (limited to 'content/browser/web_contents/web_contents_drag_win.cc')
-rw-r--r--content/browser/web_contents/web_contents_drag_win.cc371
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));
+}