1 // Copyright (c) 2011 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 "chrome/browser/ui/views/tab_contents/tab_contents_drag_win.h" 6 7 #include <windows.h> 8 9 #include <string> 10 11 #include "base/file_path.h" 12 #include "base/message_loop.h" 13 #include "base/task.h" 14 #include "base/threading/platform_thread.h" 15 #include "base/threading/thread.h" 16 #include "base/utf_string_conversions.h" 17 #include "chrome/browser/bookmarks/bookmark_node_data.h" 18 #include "chrome/browser/download/download_util.h" 19 #include "chrome/browser/download/drag_download_file.h" 20 #include "chrome/browser/download/drag_download_util.h" 21 #include "chrome/browser/tab_contents/web_drag_source_win.h" 22 #include "chrome/browser/tab_contents/web_drag_utils_win.h" 23 #include "chrome/browser/tab_contents/web_drop_target_win.h" 24 #include "chrome/browser/ui/views/tab_contents/native_tab_contents_view_win.h" 25 #include "chrome/common/url_constants.h" 26 #include "content/browser/browser_thread.h" 27 #include "content/browser/tab_contents/tab_contents.h" 28 #include "net/base/net_util.h" 29 #include "views/drag_utils.h" 30 #include "webkit/glue/webdropdata.h" 31 32 using WebKit::WebDragOperationsMask; 33 using WebKit::WebDragOperationCopy; 34 using WebKit::WebDragOperationLink; 35 using WebKit::WebDragOperationMove; 36 37 namespace { 38 39 HHOOK msg_hook = NULL; 40 DWORD drag_out_thread_id = 0; 41 bool mouse_up_received = false; 42 43 LRESULT CALLBACK MsgFilterProc(int code, WPARAM wparam, LPARAM lparam) { 44 if (code == base::MessagePumpForUI::kMessageFilterCode && 45 !mouse_up_received) { 46 MSG* msg = reinterpret_cast<MSG*>(lparam); 47 // We do not care about WM_SYSKEYDOWN and WM_SYSKEYUP because when ALT key 48 // is pressed down on drag-and-drop, it means to create a link. 49 if (msg->message == WM_MOUSEMOVE || msg->message == WM_LBUTTONUP || 50 msg->message == WM_KEYDOWN || msg->message == WM_KEYUP) { 51 // Forward the message from the UI thread to the drag-and-drop thread. 52 PostThreadMessage(drag_out_thread_id, 53 msg->message, 54 msg->wParam, 55 msg->lParam); 56 57 // If the left button is up, we do not need to forward the message any 58 // more. 59 if (msg->message == WM_LBUTTONUP || !(GetKeyState(VK_LBUTTON) & 0x8000)) 60 mouse_up_received = true; 61 62 return TRUE; 63 } 64 } 65 return CallNextHookEx(msg_hook, code, wparam, lparam); 66 } 67 68 } // namespace 69 70 class DragDropThread : public base::Thread { 71 public: 72 explicit DragDropThread(TabContentsDragWin* drag_handler) 73 : base::Thread("Chrome_DragDropThread"), 74 drag_handler_(drag_handler) { 75 } 76 77 virtual ~DragDropThread() { 78 Thread::Stop(); 79 } 80 81 protected: 82 // base::Thread implementations: 83 virtual void Init() { 84 int ole_result = OleInitialize(NULL); 85 DCHECK(ole_result == S_OK); 86 } 87 88 virtual void CleanUp() { 89 OleUninitialize(); 90 } 91 92 private: 93 // Hold a reference count to TabContentsDragWin to make sure that it is always 94 // alive in the thread lifetime. 95 scoped_refptr<TabContentsDragWin> drag_handler_; 96 97 DISALLOW_COPY_AND_ASSIGN(DragDropThread); 98 }; 99 100 TabContentsDragWin::TabContentsDragWin(NativeTabContentsViewWin* view) 101 : drag_drop_thread_id_(0), 102 view_(view), 103 drag_ended_(false), 104 old_drop_target_suspended_state_(false) { 105 } 106 107 TabContentsDragWin::~TabContentsDragWin() { 108 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 109 DCHECK(!drag_drop_thread_.get()); 110 } 111 112 void TabContentsDragWin::StartDragging(const WebDropData& drop_data, 113 WebDragOperationsMask ops, 114 const SkBitmap& image, 115 const gfx::Point& image_offset) { 116 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 117 118 drag_source_ = new WebDragSource(view_->GetNativeView(), 119 view_->GetTabContents()); 120 121 const GURL& page_url = view_->GetTabContents()->GetURL(); 122 const std::string& page_encoding = view_->GetTabContents()->encoding(); 123 124 // If it is not drag-out, do the drag-and-drop in the current UI thread. 125 if (drop_data.download_metadata.empty()) { 126 DoDragging(drop_data, ops, page_url, page_encoding, image, image_offset); 127 EndDragging(false); 128 return; 129 } 130 131 // We do not want to drag and drop the download to itself. 132 old_drop_target_suspended_state_ = view_->drop_target()->suspended(); 133 view_->drop_target()->set_suspended(true); 134 135 // Start a background thread to do the drag-and-drop. 136 DCHECK(!drag_drop_thread_.get()); 137 drag_drop_thread_.reset(new DragDropThread(this)); 138 base::Thread::Options options; 139 options.message_loop_type = MessageLoop::TYPE_UI; 140 if (drag_drop_thread_->StartWithOptions(options)) { 141 drag_drop_thread_->message_loop()->PostTask( 142 FROM_HERE, 143 NewRunnableMethod(this, 144 &TabContentsDragWin::StartBackgroundDragging, 145 drop_data, 146 ops, 147 page_url, 148 page_encoding, 149 image, 150 image_offset)); 151 } 152 153 // Install a hook procedure to monitor the messages so that we can forward 154 // the appropriate ones to the background thread. 155 drag_out_thread_id = drag_drop_thread_->thread_id(); 156 mouse_up_received = false; 157 DCHECK(!msg_hook); 158 msg_hook = SetWindowsHookEx(WH_MSGFILTER, 159 MsgFilterProc, 160 NULL, 161 GetCurrentThreadId()); 162 163 // Attach the input state of the background thread to the UI thread so that 164 // SetCursor can work from the background thread. 165 AttachThreadInput(drag_out_thread_id, GetCurrentThreadId(), TRUE); 166 } 167 168 void TabContentsDragWin::StartBackgroundDragging( 169 const WebDropData& drop_data, 170 WebDragOperationsMask ops, 171 const GURL& page_url, 172 const std::string& page_encoding, 173 const SkBitmap& image, 174 const gfx::Point& image_offset) { 175 drag_drop_thread_id_ = base::PlatformThread::CurrentId(); 176 177 DoDragging(drop_data, ops, page_url, page_encoding, image, image_offset); 178 BrowserThread::PostTask( 179 BrowserThread::UI, FROM_HERE, 180 NewRunnableMethod(this, &TabContentsDragWin::EndDragging, true)); 181 } 182 183 void TabContentsDragWin::PrepareDragForDownload( 184 const WebDropData& drop_data, 185 ui::OSExchangeData* data, 186 const GURL& page_url, 187 const std::string& page_encoding) { 188 // Parse the download metadata. 189 string16 mime_type; 190 FilePath file_name; 191 GURL download_url; 192 if (!drag_download_util::ParseDownloadMetadata(drop_data.download_metadata, 193 &mime_type, 194 &file_name, 195 &download_url)) 196 return; 197 198 // Generate the download filename. 199 std::string content_disposition = 200 "attachment; filename=" + UTF16ToUTF8(file_name.value()); 201 FilePath generated_file_name; 202 download_util::GenerateFileName(download_url, 203 content_disposition, 204 std::string(), 205 UTF16ToUTF8(mime_type), 206 &generated_file_name); 207 208 // Provide the data as file (CF_HDROP). A temporary download file with the 209 // Zone.Identifier ADS (Alternate Data Stream) attached will be created. 210 linked_ptr<net::FileStream> empty_file_stream; 211 scoped_refptr<DragDownloadFile> download_file = 212 new DragDownloadFile(generated_file_name, 213 empty_file_stream, 214 download_url, 215 page_url, 216 page_encoding, 217 view_->GetTabContents()); 218 ui::OSExchangeData::DownloadFileInfo file_download(FilePath(), 219 download_file.get()); 220 data->SetDownloadFileInfo(file_download); 221 222 // Enable asynchronous operation. 223 ui::OSExchangeDataProviderWin::GetIAsyncOperation(*data)->SetAsyncMode(TRUE); 224 } 225 226 void TabContentsDragWin::PrepareDragForFileContents( 227 const WebDropData& drop_data, ui::OSExchangeData* data) { 228 // Images without ALT text will only have a file extension so we need to 229 // synthesize one from the provided extension and URL. 230 FilePath file_name(drop_data.file_description_filename); 231 file_name = file_name.BaseName().RemoveExtension(); 232 if (file_name.value().empty()) { 233 // Retrieve the name from the URL. 234 file_name = FilePath( 235 net::GetSuggestedFilename(drop_data.url, "", "", string16())); 236 if (file_name.value().size() + drop_data.file_extension.size() + 1 > 237 MAX_PATH) { 238 file_name = FilePath(file_name.value().substr( 239 0, MAX_PATH - drop_data.file_extension.size() - 2)); 240 } 241 } 242 file_name = file_name.ReplaceExtension(drop_data.file_extension); 243 data->SetFileContents(file_name, drop_data.file_contents); 244 } 245 246 void TabContentsDragWin::PrepareDragForUrl(const WebDropData& drop_data, 247 ui::OSExchangeData* data) { 248 if (drop_data.url.SchemeIs(chrome::kJavaScriptScheme)) { 249 // We don't want to allow javascript URLs to be dragged to the desktop, 250 // but we do want to allow them to be added to the bookmarks bar 251 // (bookmarklets). So we create a fake bookmark entry (BookmarkNodeData 252 // object) which explorer.exe cannot handle, and write the entry to data. 253 BookmarkNodeData::Element bm_elt; 254 bm_elt.is_url = true; 255 bm_elt.url = drop_data.url; 256 bm_elt.title = drop_data.url_title; 257 258 BookmarkNodeData bm_drag_data; 259 bm_drag_data.elements.push_back(bm_elt); 260 261 // Pass in NULL as the profile so that the bookmark always adds the url 262 // rather than trying to move an existing url. 263 bm_drag_data.Write(NULL, data); 264 } else { 265 data->SetURL(drop_data.url, drop_data.url_title); 266 } 267 } 268 269 void TabContentsDragWin::DoDragging(const WebDropData& drop_data, 270 WebDragOperationsMask ops, 271 const GURL& page_url, 272 const std::string& page_encoding, 273 const SkBitmap& image, 274 const gfx::Point& image_offset) { 275 ui::OSExchangeData data; 276 277 if (!drop_data.download_metadata.empty()) { 278 PrepareDragForDownload(drop_data, &data, page_url, page_encoding); 279 280 // Set the observer. 281 ui::OSExchangeDataProviderWin::GetDataObjectImpl(data)->set_observer(this); 282 } else { 283 // We set the file contents before the URL because the URL also sets file 284 // contents (to a .URL shortcut). We want to prefer file content data over 285 // a shortcut so we add it first. 286 if (!drop_data.file_contents.empty()) 287 PrepareDragForFileContents(drop_data, &data); 288 if (!drop_data.text_html.empty()) 289 data.SetHtml(drop_data.text_html, drop_data.html_base_url); 290 // We set the text contents before the URL because the URL also sets text 291 // content. 292 if (!drop_data.plain_text.empty()) 293 data.SetString(drop_data.plain_text); 294 if (drop_data.url.is_valid()) 295 PrepareDragForUrl(drop_data, &data); 296 } 297 298 // Set drag image. 299 if (!image.isNull()) { 300 drag_utils::SetDragImageOnDataObject( 301 image, gfx::Size(image.width(), image.height()), image_offset, &data); 302 } 303 304 // We need to enable recursive tasks on the message loop so we can get 305 // updates while in the system DoDragDrop loop. 306 bool old_state = MessageLoop::current()->NestableTasksAllowed(); 307 MessageLoop::current()->SetNestableTasksAllowed(true); 308 DWORD effect; 309 DoDragDrop(ui::OSExchangeDataProviderWin::GetIDataObject(data), drag_source_, 310 web_drag_utils_win::WebDragOpMaskToWinDragOpMask(ops), &effect); 311 MessageLoop::current()->SetNestableTasksAllowed(old_state); 312 313 // This works because WebDragSource::OnDragSourceDrop uses PostTask to 314 // dispatch the actual event. 315 drag_source_->set_effect(effect); 316 } 317 318 void TabContentsDragWin::EndDragging(bool restore_suspended_state) { 319 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 320 321 if (drag_ended_) 322 return; 323 drag_ended_ = true; 324 325 if (restore_suspended_state) 326 view_->drop_target()->set_suspended(old_drop_target_suspended_state_); 327 328 if (msg_hook) { 329 AttachThreadInput(drag_out_thread_id, GetCurrentThreadId(), FALSE); 330 UnhookWindowsHookEx(msg_hook); 331 msg_hook = NULL; 332 } 333 334 view_->EndDragging(); 335 } 336 337 void TabContentsDragWin::CancelDrag() { 338 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 339 340 drag_source_->CancelDrag(); 341 } 342 343 void TabContentsDragWin::CloseThread() { 344 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 345 346 drag_drop_thread_.reset(); 347 } 348 349 void TabContentsDragWin::OnWaitForData() { 350 DCHECK(drag_drop_thread_id_ == base::PlatformThread::CurrentId()); 351 352 // When the left button is release and we start to wait for the data, end 353 // the dragging before DoDragDrop returns. This makes the page leave the drag 354 // mode so that it can start to process the normal input events. 355 BrowserThread::PostTask( 356 BrowserThread::UI, FROM_HERE, 357 NewRunnableMethod(this, &TabContentsDragWin::EndDragging, true)); 358 } 359 360 void TabContentsDragWin::OnDataObjectDisposed() { 361 DCHECK(drag_drop_thread_id_ == base::PlatformThread::CurrentId()); 362 363 // The drag-and-drop thread is only closed after OLE is done with 364 // DataObjectImpl. 365 BrowserThread::PostTask( 366 BrowserThread::UI, FROM_HERE, 367 NewRunnableMethod(this, &TabContentsDragWin::CloseThread)); 368 } 369