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 WebKit::WebDragOperationsMask; 42 using WebKit::WebDragOperationCopy; 43 using WebKit::WebDragOperationLink; 44 using WebKit::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_->GetURL(); 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 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 UTF16ToUTF8(file_name.value()), 247 UTF16ToUTF8(mime_type), 248 default_name); 249 base::FilePath temp_dir_path; 250 if (!file_util::CreateNewTempDirectory( 251 FILE_PATH_LITERAL("chrome_drag"), &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 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 444