blob: ffc8713f8606ca05b045337e8ae36008131986b5 [file] [log] [blame]
// 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_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<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);
}
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<ui::ScopedOleInitializer> ole_initializer_;
// 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,
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),
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.
base::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<DragDownloadFile> download_file =
new DragDownloadFile(
download_path,
scoped_ptr<net::FileStream>(),
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 base::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<WebDragSource> 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