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