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