| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "content/browser/web_contents/web_contents_drag_win.h" | |
| 6 | |
| 7 #include <windows.h> | |
| 8 | |
| 9 #include <string> | |
| 10 | |
| 11 #include "base/bind.h" | |
| 12 #include "base/file_util.h" | |
| 13 #include "base/files/file_path.h" | |
| 14 #include "base/message_loop/message_loop.h" | |
| 15 #include "base/pickle.h" | |
| 16 #include "base/strings/utf_string_conversions.h" | |
| 17 #include "base/threading/platform_thread.h" | |
| 18 #include "base/threading/thread.h" | |
| 19 #include "content/browser/download/drag_download_file.h" | |
| 20 #include "content/browser/download/drag_download_util.h" | |
| 21 #include "content/browser/web_contents/web_drag_dest_win.h" | |
| 22 #include "content/browser/web_contents/web_drag_source_win.h" | |
| 23 #include "content/browser/web_contents/web_drag_utils_win.h" | |
| 24 #include "content/public/browser/browser_thread.h" | |
| 25 #include "content/public/browser/content_browser_client.h" | |
| 26 #include "content/public/browser/web_contents.h" | |
| 27 #include "content/public/browser/web_contents_view.h" | |
| 28 #include "content/public/browser/web_drag_dest_delegate.h" | |
| 29 #include "content/public/common/drop_data.h" | |
| 30 #include "net/base/net_util.h" | |
| 31 #include "third_party/skia/include/core/SkBitmap.h" | |
| 32 #include "ui/base/clipboard/clipboard.h" | |
| 33 #include "ui/base/clipboard/custom_data_helper.h" | |
| 34 #include "ui/base/dragdrop/drag_utils.h" | |
| 35 #include "ui/base/layout.h" | |
| 36 #include "ui/base/win/scoped_ole_initializer.h" | |
| 37 #include "ui/gfx/image/image_skia.h" | |
| 38 #include "ui/gfx/screen.h" | |
| 39 #include "ui/gfx/size.h" | |
| 40 | |
| 41 using blink::WebDragOperationsMask; | |
| 42 using blink::WebDragOperationCopy; | |
| 43 using blink::WebDragOperationLink; | |
| 44 using blink::WebDragOperationMove; | |
| 45 | |
| 46 namespace content { | |
| 47 namespace { | |
| 48 | |
| 49 bool run_do_drag_drop = true; | |
| 50 | |
| 51 HHOOK msg_hook = NULL; | |
| 52 DWORD drag_out_thread_id = 0; | |
| 53 bool mouse_up_received = false; | |
| 54 | |
| 55 LRESULT CALLBACK MsgFilterProc(int code, WPARAM wparam, LPARAM lparam) { | |
| 56 if (code == base::MessagePumpForUI::kMessageFilterCode && | |
| 57 !mouse_up_received) { | |
| 58 MSG* msg = reinterpret_cast<MSG*>(lparam); | |
| 59 // We do not care about WM_SYSKEYDOWN and WM_SYSKEYUP because when ALT key | |
| 60 // is pressed down on drag-and-drop, it means to create a link. | |
| 61 if (msg->message == WM_MOUSEMOVE || msg->message == WM_LBUTTONUP || | |
| 62 msg->message == WM_KEYDOWN || msg->message == WM_KEYUP) { | |
| 63 // Forward the message from the UI thread to the drag-and-drop thread. | |
| 64 PostThreadMessage(drag_out_thread_id, | |
| 65 msg->message, | |
| 66 msg->wParam, | |
| 67 msg->lParam); | |
| 68 | |
| 69 // If the left button is up, we do not need to forward the message any | |
| 70 // more. | |
| 71 if (msg->message == WM_LBUTTONUP || !(GetKeyState(VK_LBUTTON) & 0x8000)) | |
| 72 mouse_up_received = true; | |
| 73 | |
| 74 return TRUE; | |
| 75 } | |
| 76 } | |
| 77 return CallNextHookEx(msg_hook, code, wparam, lparam); | |
| 78 } | |
| 79 | |
| 80 void EnableBackgroundDraggingSupport(DWORD thread_id) { | |
| 81 // Install a hook procedure to monitor the messages so that we can forward | |
| 82 // the appropriate ones to the background thread. | |
| 83 drag_out_thread_id = thread_id; | |
| 84 mouse_up_received = false; | |
| 85 DCHECK(!msg_hook); | |
| 86 msg_hook = SetWindowsHookEx(WH_MSGFILTER, | |
| 87 MsgFilterProc, | |
| 88 NULL, | |
| 89 GetCurrentThreadId()); | |
| 90 | |
| 91 // Attach the input state of the background thread to the UI thread so that | |
| 92 // SetCursor can work from the background thread. | |
| 93 AttachThreadInput(drag_out_thread_id, GetCurrentThreadId(), TRUE); | |
| 94 } | |
| 95 | |
| 96 void DisableBackgroundDraggingSupport() { | |
| 97 DCHECK(msg_hook); | |
| 98 AttachThreadInput(drag_out_thread_id, GetCurrentThreadId(), FALSE); | |
| 99 UnhookWindowsHookEx(msg_hook); | |
| 100 msg_hook = NULL; | |
| 101 } | |
| 102 | |
| 103 bool IsBackgroundDraggingSupportEnabled() { | |
| 104 return msg_hook != NULL; | |
| 105 } | |
| 106 | |
| 107 } // namespace | |
| 108 | |
| 109 class DragDropThread : public base::Thread { | |
| 110 public: | |
| 111 explicit DragDropThread(WebContentsDragWin* drag_handler) | |
| 112 : Thread("Chrome_DragDropThread"), | |
| 113 drag_handler_(drag_handler) { | |
| 114 } | |
| 115 | |
| 116 virtual ~DragDropThread() { | |
| 117 Stop(); | |
| 118 } | |
| 119 | |
| 120 protected: | |
| 121 // base::Thread implementations: | |
| 122 virtual void Init() { | |
| 123 ole_initializer_.reset(new ui::ScopedOleInitializer()); | |
| 124 } | |
| 125 | |
| 126 virtual void CleanUp() { | |
| 127 ole_initializer_.reset(); | |
| 128 } | |
| 129 | |
| 130 private: | |
| 131 scoped_ptr<ui::ScopedOleInitializer> ole_initializer_; | |
| 132 | |
| 133 // Hold a reference count to WebContentsDragWin to make sure that it is always | |
| 134 // alive in the thread lifetime. | |
| 135 scoped_refptr<WebContentsDragWin> drag_handler_; | |
| 136 | |
| 137 DISALLOW_COPY_AND_ASSIGN(DragDropThread); | |
| 138 }; | |
| 139 | |
| 140 WebContentsDragWin::WebContentsDragWin( | |
| 141 gfx::NativeWindow source_window, | |
| 142 WebContents* web_contents, | |
| 143 WebDragDest* drag_dest, | |
| 144 const base::Callback<void()>& drag_end_callback) | |
| 145 : drag_drop_thread_id_(0), | |
| 146 source_window_(source_window), | |
| 147 web_contents_(web_contents), | |
| 148 drag_dest_(drag_dest), | |
| 149 drag_ended_(false), | |
| 150 drag_end_callback_(drag_end_callback) { | |
| 151 } | |
| 152 | |
| 153 WebContentsDragWin::~WebContentsDragWin() { | |
| 154 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 155 DCHECK(!drag_drop_thread_.get()); | |
| 156 } | |
| 157 | |
| 158 void WebContentsDragWin::StartDragging(const DropData& drop_data, | |
| 159 WebDragOperationsMask ops, | |
| 160 const gfx::ImageSkia& image, | |
| 161 const gfx::Vector2d& image_offset) { | |
| 162 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 163 | |
| 164 drag_source_ = new WebDragSource(source_window_, web_contents_); | |
| 165 | |
| 166 const GURL& page_url = web_contents_->GetLastCommittedURL(); | |
| 167 const std::string& page_encoding = web_contents_->GetEncoding(); | |
| 168 | |
| 169 // If it is not drag-out, do the drag-and-drop in the current UI thread. | |
| 170 if (drop_data.download_metadata.empty()) { | |
| 171 if (DoDragging(drop_data, ops, page_url, page_encoding, | |
| 172 image, image_offset)) | |
| 173 EndDragging(); | |
| 174 return; | |
| 175 } | |
| 176 | |
| 177 // Start a background thread to do the drag-and-drop. | |
| 178 DCHECK(!drag_drop_thread_.get()); | |
| 179 drag_drop_thread_.reset(new DragDropThread(this)); | |
| 180 base::Thread::Options options; | |
| 181 options.message_loop_type = base::MessageLoop::TYPE_UI; | |
| 182 if (drag_drop_thread_->StartWithOptions(options)) { | |
| 183 drag_drop_thread_->message_loop()->PostTask( | |
| 184 FROM_HERE, | |
| 185 base::Bind(&WebContentsDragWin::StartBackgroundDragging, this, | |
| 186 drop_data, ops, page_url, page_encoding, | |
| 187 image, image_offset)); | |
| 188 } | |
| 189 | |
| 190 EnableBackgroundDraggingSupport(drag_drop_thread_->thread_id()); | |
| 191 } | |
| 192 | |
| 193 void WebContentsDragWin::StartBackgroundDragging( | |
| 194 const DropData& drop_data, | |
| 195 WebDragOperationsMask ops, | |
| 196 const GURL& page_url, | |
| 197 const std::string& page_encoding, | |
| 198 const gfx::ImageSkia& image, | |
| 199 const gfx::Vector2d& image_offset) { | |
| 200 drag_drop_thread_id_ = base::PlatformThread::CurrentId(); | |
| 201 | |
| 202 if (DoDragging(drop_data, ops, page_url, page_encoding, | |
| 203 image, image_offset)) { | |
| 204 BrowserThread::PostTask( | |
| 205 BrowserThread::UI, | |
| 206 FROM_HERE, | |
| 207 base::Bind(&WebContentsDragWin::EndDragging, this)); | |
| 208 } else { | |
| 209 // When DoDragging returns false, the contents view window is gone and thus | |
| 210 // WebContentsViewWin instance becomes invalid though WebContentsDragWin | |
| 211 // instance is still alive because the task holds a reference count to it. | |
| 212 // We should not do more than the following cleanup items: | |
| 213 // 1) Remove the background dragging support. This is safe since it does not | |
| 214 // access the instance at all. | |
| 215 // 2) Stop the background thread. This is done in OnDataObjectDisposed. | |
| 216 // Only drag_drop_thread_ member is accessed. | |
| 217 BrowserThread::PostTask( | |
| 218 BrowserThread::UI, | |
| 219 FROM_HERE, | |
| 220 base::Bind(&DisableBackgroundDraggingSupport)); | |
| 221 } | |
| 222 } | |
| 223 | |
| 224 void WebContentsDragWin::PrepareDragForDownload( | |
| 225 const DropData& drop_data, | |
| 226 ui::OSExchangeData* data, | |
| 227 const GURL& page_url, | |
| 228 const std::string& page_encoding) { | |
| 229 // Parse the download metadata. | |
| 230 base::string16 mime_type; | |
| 231 base::FilePath file_name; | |
| 232 GURL download_url; | |
| 233 if (!ParseDownloadMetadata(drop_data.download_metadata, | |
| 234 &mime_type, | |
| 235 &file_name, | |
| 236 &download_url)) | |
| 237 return; | |
| 238 | |
| 239 // Generate the file name based on both mime type and proposed file name. | |
| 240 std::string default_name = | |
| 241 GetContentClient()->browser()->GetDefaultDownloadName(); | |
| 242 base::FilePath generated_download_file_name = | |
| 243 net::GenerateFileName(download_url, | |
| 244 std::string(), | |
| 245 std::string(), | |
| 246 base::UTF16ToUTF8(file_name.value()), | |
| 247 base::UTF16ToUTF8(mime_type), | |
| 248 default_name); | |
| 249 base::FilePath temp_dir_path; | |
| 250 if (!base::CreateNewTempDirectory(FILE_PATH_LITERAL("chrome_drag"), | |
| 251 &temp_dir_path)) | |
| 252 return; | |
| 253 base::FilePath download_path = | |
| 254 temp_dir_path.Append(generated_download_file_name); | |
| 255 | |
| 256 // We cannot know when the target application will be done using the temporary | |
| 257 // file, so schedule it to be deleted after rebooting. | |
| 258 base::DeleteFileAfterReboot(download_path); | |
| 259 base::DeleteFileAfterReboot(temp_dir_path); | |
| 260 | |
| 261 // Provide the data as file (CF_HDROP). A temporary download file with the | |
| 262 // Zone.Identifier ADS (Alternate Data Stream) attached will be created. | |
| 263 scoped_refptr<DragDownloadFile> download_file = | |
| 264 new DragDownloadFile( | |
| 265 download_path, | |
| 266 scoped_ptr<net::FileStream>(), | |
| 267 download_url, | |
| 268 Referrer(page_url, drop_data.referrer_policy), | |
| 269 page_encoding, | |
| 270 web_contents_); | |
| 271 ui::OSExchangeData::DownloadFileInfo file_download(base::FilePath(), | |
| 272 download_file.get()); | |
| 273 data->SetDownloadFileInfo(file_download); | |
| 274 | |
| 275 // Enable asynchronous operation. | |
| 276 ui::OSExchangeDataProviderWin::GetIAsyncOperation(*data)->SetAsyncMode(TRUE); | |
| 277 } | |
| 278 | |
| 279 void WebContentsDragWin::PrepareDragForFileContents( | |
| 280 const DropData& drop_data, ui::OSExchangeData* data) { | |
| 281 static const int kMaxFilenameLength = 255; // FAT and NTFS | |
| 282 base::FilePath file_name(drop_data.file_description_filename); | |
| 283 | |
| 284 // Images without ALT text will only have a file extension so we need to | |
| 285 // synthesize one from the provided extension and URL. | |
| 286 if (file_name.BaseName().RemoveExtension().empty()) { | |
| 287 const base::string16 extension = file_name.Extension(); | |
| 288 // Retrieve the name from the URL. | |
| 289 file_name = base::FilePath( | |
| 290 net::GetSuggestedFilename(drop_data.url, "", "", "", "", "")); | |
| 291 if (file_name.value().size() + extension.size() > kMaxFilenameLength) { | |
| 292 file_name = base::FilePath(file_name.value().substr( | |
| 293 0, kMaxFilenameLength - extension.size())); | |
| 294 } | |
| 295 file_name = file_name.ReplaceExtension(extension); | |
| 296 } | |
| 297 data->SetFileContents(file_name, drop_data.file_contents); | |
| 298 } | |
| 299 | |
| 300 void WebContentsDragWin::PrepareDragForUrl(const DropData& drop_data, | |
| 301 ui::OSExchangeData* data) { | |
| 302 if (drag_dest_->delegate() && | |
| 303 drag_dest_->delegate()->AddDragData(drop_data, data)) { | |
| 304 return; | |
| 305 } | |
| 306 | |
| 307 data->SetURL(drop_data.url, drop_data.url_title); | |
| 308 } | |
| 309 | |
| 310 bool WebContentsDragWin::DoDragging(const DropData& drop_data, | |
| 311 WebDragOperationsMask ops, | |
| 312 const GURL& page_url, | |
| 313 const std::string& page_encoding, | |
| 314 const gfx::ImageSkia& image, | |
| 315 const gfx::Vector2d& image_offset) { | |
| 316 ui::OSExchangeData data; | |
| 317 | |
| 318 if (!drop_data.download_metadata.empty()) { | |
| 319 PrepareDragForDownload(drop_data, &data, page_url, page_encoding); | |
| 320 | |
| 321 // Set the observer. | |
| 322 ui::OSExchangeDataProviderWin::GetDataObjectImpl(data)->set_observer(this); | |
| 323 } | |
| 324 | |
| 325 // We set the file contents before the URL because the URL also sets file | |
| 326 // contents (to a .URL shortcut). We want to prefer file content data over | |
| 327 // a shortcut so we add it first. | |
| 328 if (!drop_data.file_contents.empty()) | |
| 329 PrepareDragForFileContents(drop_data, &data); | |
| 330 if (!drop_data.html.string().empty()) | |
| 331 data.SetHtml(drop_data.html.string(), drop_data.html_base_url); | |
| 332 // We set the text contents before the URL because the URL also sets text | |
| 333 // content. | |
| 334 if (!drop_data.text.string().empty()) | |
| 335 data.SetString(drop_data.text.string()); | |
| 336 if (drop_data.url.is_valid()) | |
| 337 PrepareDragForUrl(drop_data, &data); | |
| 338 if (!drop_data.custom_data.empty()) { | |
| 339 Pickle pickle; | |
| 340 ui::WriteCustomDataToPickle(drop_data.custom_data, &pickle); | |
| 341 data.SetPickledData(ui::Clipboard::GetWebCustomDataFormatType(), pickle); | |
| 342 } | |
| 343 | |
| 344 // Set drag image. | |
| 345 if (!image.isNull()) { | |
| 346 drag_utils::SetDragImageOnDataObject(image, | |
| 347 gfx::Size(image.width(), image.height()), image_offset, &data); | |
| 348 } | |
| 349 | |
| 350 // Use a local variable to keep track of the contents view window handle. | |
| 351 // It might not be safe to access the instance after DoDragDrop returns | |
| 352 // because the window could be disposed in the nested message loop. | |
| 353 HWND native_window = web_contents_->GetView()->GetNativeView(); | |
| 354 | |
| 355 // We need to enable recursive tasks on the message loop so we can get | |
| 356 // updates while in the system DoDragDrop loop. | |
| 357 DWORD effect = DROPEFFECT_NONE; | |
| 358 if (run_do_drag_drop) { | |
| 359 // Keep a reference count such that |drag_source_| will not get deleted | |
| 360 // if the contents view window is gone in the nested message loop invoked | |
| 361 // from DoDragDrop. | |
| 362 scoped_refptr<WebDragSource> retain_source(drag_source_); | |
| 363 retain_source->set_data(&data); | |
| 364 data.SetInDragLoop(true); | |
| 365 | |
| 366 base::MessageLoop::ScopedNestableTaskAllower allow( | |
| 367 base::MessageLoop::current()); | |
| 368 DoDragDrop(ui::OSExchangeDataProviderWin::GetIDataObject(data), | |
| 369 drag_source_, | |
| 370 WebDragOpMaskToWinDragOpMask(ops), | |
| 371 &effect); | |
| 372 retain_source->set_data(NULL); | |
| 373 } | |
| 374 | |
| 375 // Bail out immediately if the contents view window is gone. | |
| 376 if (!IsWindow(native_window)) | |
| 377 return false; | |
| 378 | |
| 379 // Normally, the drop and dragend events get dispatched in the system | |
| 380 // DoDragDrop message loop so it'd be too late to set the effect to send back | |
| 381 // to the renderer here. However, we use PostTask to delay the execution of | |
| 382 // WebDragSource::OnDragSourceDrop, which means that the delayed dragend | |
| 383 // callback to the renderer doesn't run until this has been set to the correct | |
| 384 // value. | |
| 385 drag_source_->set_effect(effect); | |
| 386 | |
| 387 return true; | |
| 388 } | |
| 389 | |
| 390 void WebContentsDragWin::EndDragging() { | |
| 391 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 392 | |
| 393 if (drag_ended_) | |
| 394 return; | |
| 395 drag_ended_ = true; | |
| 396 | |
| 397 if (IsBackgroundDraggingSupportEnabled()) | |
| 398 DisableBackgroundDraggingSupport(); | |
| 399 | |
| 400 drag_end_callback_.Run(); | |
| 401 } | |
| 402 | |
| 403 void WebContentsDragWin::CancelDrag() { | |
| 404 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 405 | |
| 406 drag_source_->CancelDrag(); | |
| 407 } | |
| 408 | |
| 409 void WebContentsDragWin::CloseThread() { | |
| 410 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 411 | |
| 412 drag_drop_thread_.reset(); | |
| 413 } | |
| 414 | |
| 415 void WebContentsDragWin::OnWaitForData() { | |
| 416 DCHECK(drag_drop_thread_id_ == base::PlatformThread::CurrentId()); | |
| 417 | |
| 418 // When the left button is release and we start to wait for the data, end | |
| 419 // the dragging before DoDragDrop returns. This makes the page leave the drag | |
| 420 // mode so that it can start to process the normal input events. | |
| 421 BrowserThread::PostTask( | |
| 422 BrowserThread::UI, | |
| 423 FROM_HERE, | |
| 424 base::Bind(&WebContentsDragWin::EndDragging, this)); | |
| 425 } | |
| 426 | |
| 427 void WebContentsDragWin::OnDataObjectDisposed() { | |
| 428 DCHECK(drag_drop_thread_id_ == base::PlatformThread::CurrentId()); | |
| 429 | |
| 430 // The drag-and-drop thread is only closed after OLE is done with | |
| 431 // DataObjectImpl. | |
| 432 BrowserThread::PostTask( | |
| 433 BrowserThread::UI, | |
| 434 FROM_HERE, | |
| 435 base::Bind(&WebContentsDragWin::CloseThread, this)); | |
| 436 } | |
| 437 | |
| 438 // static | |
| 439 void WebContentsDragWin::DisableDragDropForTesting() { | |
| 440 run_do_drag_drop = false; | |
| 441 } | |
| 442 | |
| 443 } // namespace content | |
| OLD | NEW |