• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/gtk/tab_contents_drag_source.h"
6 
7 #include <string>
8 
9 #include "base/file_util.h"
10 #include "base/mime_util.h"
11 #include "base/utf_string_conversions.h"
12 #include "chrome/browser/download/download_util.h"
13 #include "chrome/browser/download/drag_download_file.h"
14 #include "chrome/browser/download/drag_download_util.h"
15 #include "chrome/browser/ui/gtk/gtk_util.h"
16 #include "content/browser/renderer_host/render_view_host.h"
17 #include "content/browser/renderer_host/render_view_host_delegate.h"
18 #include "content/browser/tab_contents/tab_contents.h"
19 #include "content/browser/tab_contents/tab_contents_view.h"
20 #include "net/base/file_stream.h"
21 #include "net/base/net_util.h"
22 #include "ui/base/dragdrop/gtk_dnd_util.h"
23 #include "ui/gfx/gtk_util.h"
24 #include "webkit/glue/webdropdata.h"
25 
26 using WebKit::WebDragOperation;
27 using WebKit::WebDragOperationsMask;
28 using WebKit::WebDragOperationNone;
29 
TabContentsDragSource(TabContentsView * tab_contents_view)30 TabContentsDragSource::TabContentsDragSource(
31     TabContentsView* tab_contents_view)
32     : tab_contents_view_(tab_contents_view),
33       drag_pixbuf_(NULL),
34       drag_failed_(false),
35       drag_widget_(gtk_invisible_new()),
36       drag_context_(NULL),
37       drag_icon_(gtk_window_new(GTK_WINDOW_POPUP)) {
38   signals_.Connect(drag_widget_, "drag-failed",
39                    G_CALLBACK(OnDragFailedThunk), this);
40   signals_.Connect(drag_widget_, "drag-begin",
41                    G_CALLBACK(OnDragBeginThunk),
42                    this);
43   signals_.Connect(drag_widget_, "drag-end",
44                    G_CALLBACK(OnDragEndThunk), this);
45   signals_.Connect(drag_widget_, "drag-data-get",
46                    G_CALLBACK(OnDragDataGetThunk), this);
47 
48   signals_.Connect(drag_icon_, "expose-event",
49                    G_CALLBACK(OnDragIconExposeThunk), this);
50 }
51 
~TabContentsDragSource()52 TabContentsDragSource::~TabContentsDragSource() {
53   // Break the current drag, if any.
54   if (drop_data_.get()) {
55     gtk_grab_add(drag_widget_);
56     gtk_grab_remove(drag_widget_);
57     MessageLoopForUI::current()->RemoveObserver(this);
58     drop_data_.reset();
59   }
60 
61   gtk_widget_destroy(drag_widget_);
62   gtk_widget_destroy(drag_icon_);
63 }
64 
tab_contents() const65 TabContents* TabContentsDragSource::tab_contents() const {
66   return tab_contents_view_->tab_contents();
67 }
68 
StartDragging(const WebDropData & drop_data,WebDragOperationsMask allowed_ops,GdkEventButton * last_mouse_down,const SkBitmap & image,const gfx::Point & image_offset)69 void TabContentsDragSource::StartDragging(const WebDropData& drop_data,
70                                           WebDragOperationsMask allowed_ops,
71                                           GdkEventButton* last_mouse_down,
72                                           const SkBitmap& image,
73                                           const gfx::Point& image_offset) {
74   // Guard against re-starting before previous drag completed.
75   if (drag_context_) {
76     NOTREACHED();
77     tab_contents()->SystemDragEnded();
78     return;
79   }
80 
81   int targets_mask = 0;
82 
83   if (!drop_data.plain_text.empty())
84     targets_mask |= ui::TEXT_PLAIN;
85   if (drop_data.url.is_valid()) {
86     targets_mask |= ui::TEXT_URI_LIST;
87     targets_mask |= ui::CHROME_NAMED_URL;
88     targets_mask |= ui::NETSCAPE_URL;
89   }
90   if (!drop_data.text_html.empty())
91     targets_mask |= ui::TEXT_HTML;
92   if (!drop_data.file_contents.empty())
93     targets_mask |= ui::CHROME_WEBDROP_FILE_CONTENTS;
94   if (!drop_data.download_metadata.empty() &&
95       drag_download_util::ParseDownloadMetadata(drop_data.download_metadata,
96                                                 &wide_download_mime_type_,
97                                                 &download_file_name_,
98                                                 &download_url_)) {
99     targets_mask |= ui::DIRECT_SAVE_FILE;
100   }
101 
102   // NOTE: Begin a drag even if no targets present. Otherwise, things like
103   // draggable list elements will not work.
104 
105   drop_data_.reset(new WebDropData(drop_data));
106 
107   // The image we get from WebKit makes heavy use of alpha-shading. This looks
108   // bad on non-compositing WMs. Fall back to the default drag icon.
109   if (!image.isNull() && gtk_util::IsScreenComposited())
110     drag_pixbuf_ = gfx::GdkPixbufFromSkBitmap(&image);
111   image_offset_ = image_offset;
112 
113   GtkTargetList* list = ui::GetTargetListFromCodeMask(targets_mask);
114   if (targets_mask & ui::CHROME_WEBDROP_FILE_CONTENTS) {
115     drag_file_mime_type_ = gdk_atom_intern(
116         mime_util::GetDataMimeType(drop_data.file_contents).c_str(), FALSE);
117     gtk_target_list_add(list, drag_file_mime_type_,
118                         0, ui::CHROME_WEBDROP_FILE_CONTENTS);
119   }
120 
121   drag_failed_ = false;
122   // If we don't pass an event, GDK won't know what event time to start grabbing
123   // mouse events. Technically it's the mouse motion event and not the mouse
124   // down event that causes the drag, but there's no reliable way to know
125   // *which* motion event initiated the drag, so this will have to do.
126   // TODO(estade): This can sometimes be very far off, e.g. if the user clicks
127   // and holds and doesn't start dragging for a long time. I doubt it matters
128   // much, but we should probably look into the possibility of getting the
129   // initiating event from webkit.
130   drag_context_ = gtk_drag_begin(drag_widget_, list,
131       gtk_util::WebDragOpToGdkDragAction(allowed_ops),
132       1,  // Drags are always initiated by the left button.
133       reinterpret_cast<GdkEvent*>(last_mouse_down));
134   // The drag adds a ref; let it own the list.
135   gtk_target_list_unref(list);
136 
137   // Sometimes the drag fails to start; |context| will be NULL and we won't
138   // get a drag-end signal.
139   if (!drag_context_) {
140     drag_failed_ = true;
141     drop_data_.reset();
142     tab_contents()->SystemDragEnded();
143     return;
144   }
145 
146   MessageLoopForUI::current()->AddObserver(this);
147 }
148 
WillProcessEvent(GdkEvent * event)149 void TabContentsDragSource::WillProcessEvent(GdkEvent* event) {
150   // No-op.
151 }
152 
DidProcessEvent(GdkEvent * event)153 void TabContentsDragSource::DidProcessEvent(GdkEvent* event) {
154   if (event->type != GDK_MOTION_NOTIFY)
155     return;
156 
157   GdkEventMotion* event_motion = reinterpret_cast<GdkEventMotion*>(event);
158   gfx::Point client = gtk_util::ClientPoint(GetContentNativeView());
159 
160   if (tab_contents()->render_view_host()) {
161     tab_contents()->render_view_host()->DragSourceMovedTo(
162         client.x(), client.y(),
163         static_cast<int>(event_motion->x_root),
164         static_cast<int>(event_motion->y_root));
165   }
166 }
167 
OnDragDataGet(GtkWidget * sender,GdkDragContext * context,GtkSelectionData * selection_data,guint target_type,guint time)168 void TabContentsDragSource::OnDragDataGet(GtkWidget* sender,
169     GdkDragContext* context, GtkSelectionData* selection_data,
170     guint target_type, guint time) {
171   const int kBitsPerByte = 8;
172 
173   switch (target_type) {
174     case ui::TEXT_PLAIN: {
175       std::string utf8_text = UTF16ToUTF8(drop_data_->plain_text);
176       gtk_selection_data_set_text(selection_data, utf8_text.c_str(),
177                                   utf8_text.length());
178       break;
179     }
180 
181     case ui::TEXT_HTML: {
182       // TODO(estade): change relative links to be absolute using
183       // |html_base_url|.
184       std::string utf8_text = UTF16ToUTF8(drop_data_->text_html);
185       gtk_selection_data_set(selection_data,
186                              ui::GetAtomForTarget(ui::TEXT_HTML),
187                              kBitsPerByte,
188                              reinterpret_cast<const guchar*>(utf8_text.c_str()),
189                              utf8_text.length());
190       break;
191     }
192 
193     case ui::TEXT_URI_LIST:
194     case ui::CHROME_NAMED_URL:
195     case ui::NETSCAPE_URL: {
196       ui::WriteURLWithName(selection_data, drop_data_->url,
197                            drop_data_->url_title, target_type);
198       break;
199     }
200 
201     case ui::CHROME_WEBDROP_FILE_CONTENTS: {
202       gtk_selection_data_set(
203           selection_data,
204           drag_file_mime_type_, kBitsPerByte,
205           reinterpret_cast<const guchar*>(drop_data_->file_contents.data()),
206           drop_data_->file_contents.length());
207       break;
208     }
209 
210     case ui::DIRECT_SAVE_FILE: {
211       char status_code = 'E';
212 
213       // Retrieves the full file path (in file URL format) provided by the
214       // drop target by reading from the source window's XdndDirectSave0
215       // property.
216       gint file_url_len = 0;
217       guchar* file_url_value = NULL;
218       if (gdk_property_get(context->source_window,
219                            ui::GetAtomForTarget(ui::DIRECT_SAVE_FILE),
220                            ui::GetAtomForTarget(ui::TEXT_PLAIN_NO_CHARSET),
221                            0,
222                            1024,
223                            FALSE,
224                            NULL,
225                            NULL,
226                            &file_url_len,
227                            &file_url_value) &&
228           file_url_value) {
229         // Convert from the file url to the file path.
230         GURL file_url(std::string(reinterpret_cast<char*>(file_url_value),
231                                   file_url_len));
232         g_free(file_url_value);
233         FilePath file_path;
234         if (net::FileURLToFilePath(file_url, &file_path)) {
235           // Open the file as a stream.
236           net::FileStream* file_stream =
237               drag_download_util::CreateFileStreamForDrop(&file_path);
238           if (file_stream) {
239               // Start downloading the file to the stream.
240               TabContents* tab_contents = tab_contents_view_->tab_contents();
241               scoped_refptr<DragDownloadFile> drag_file_downloader =
242                   new DragDownloadFile(file_path,
243                                        linked_ptr<net::FileStream>(file_stream),
244                                        download_url_,
245                                        tab_contents->GetURL(),
246                                        tab_contents->encoding(),
247                                        tab_contents);
248               drag_file_downloader->Start(
249                   new drag_download_util::PromiseFileFinalizer(
250                       drag_file_downloader));
251 
252               // Set the status code to success.
253               status_code = 'S';
254           }
255         }
256 
257         // Return the status code to the file manager.
258         gtk_selection_data_set(selection_data,
259                                selection_data->target,
260                                8,
261                                reinterpret_cast<guchar*>(&status_code),
262                                1);
263       }
264       break;
265     }
266 
267     default:
268       NOTREACHED();
269   }
270 }
271 
OnDragFailed(GtkWidget * sender,GdkDragContext * context,GtkDragResult result)272 gboolean TabContentsDragSource::OnDragFailed(GtkWidget* sender,
273                                              GdkDragContext* context,
274                                              GtkDragResult result) {
275   drag_failed_ = true;
276 
277   gfx::Point root = gtk_util::ScreenPoint(GetContentNativeView());
278   gfx::Point client = gtk_util::ClientPoint(GetContentNativeView());
279 
280   if (tab_contents()->render_view_host()) {
281     tab_contents()->render_view_host()->DragSourceEndedAt(
282         client.x(), client.y(), root.x(), root.y(),
283         WebDragOperationNone);
284   }
285 
286   // Let the native failure animation run.
287   return FALSE;
288 }
289 
OnDragBegin(GtkWidget * sender,GdkDragContext * drag_context)290 void TabContentsDragSource::OnDragBegin(GtkWidget* sender,
291                                         GdkDragContext* drag_context) {
292   if (!download_url_.is_empty()) {
293     // Generate the file name based on both mime type and proposed file name.
294     std::string download_mime_type = UTF16ToUTF8(wide_download_mime_type_);
295     std::string content_disposition("attachment; filename=");
296     content_disposition += download_file_name_.value();
297     FilePath generated_download_file_name;
298     download_util::GenerateFileName(download_url_,
299                                     content_disposition,
300                                     std::string(),
301                                     download_mime_type,
302                                     &generated_download_file_name);
303 
304     // Pass the file name to the drop target by setting the source window's
305     // XdndDirectSave0 property.
306     gdk_property_change(drag_context->source_window,
307                         ui::GetAtomForTarget(ui::DIRECT_SAVE_FILE),
308                         ui::GetAtomForTarget(ui::TEXT_PLAIN_NO_CHARSET),
309                         8,
310                         GDK_PROP_MODE_REPLACE,
311                         reinterpret_cast<const guchar*>(
312                             generated_download_file_name.value().c_str()),
313                         generated_download_file_name.value().length());
314   }
315 
316   if (drag_pixbuf_) {
317     gtk_widget_set_size_request(drag_icon_,
318                                 gdk_pixbuf_get_width(drag_pixbuf_),
319                                 gdk_pixbuf_get_height(drag_pixbuf_));
320 
321     // We only need to do this once.
322     if (!GTK_WIDGET_REALIZED(drag_icon_)) {
323       GdkScreen* screen = gtk_widget_get_screen(drag_icon_);
324       GdkColormap* rgba = gdk_screen_get_rgba_colormap(screen);
325       if (rgba)
326         gtk_widget_set_colormap(drag_icon_, rgba);
327     }
328 
329     gtk_drag_set_icon_widget(drag_context, drag_icon_,
330                              image_offset_.x(), image_offset_.y());
331   }
332 }
333 
OnDragEnd(GtkWidget * sender,GdkDragContext * drag_context)334 void TabContentsDragSource::OnDragEnd(GtkWidget* sender,
335                                       GdkDragContext* drag_context) {
336   if (drag_pixbuf_) {
337     g_object_unref(drag_pixbuf_);
338     drag_pixbuf_ = NULL;
339   }
340 
341   MessageLoopForUI::current()->RemoveObserver(this);
342 
343   if (!download_url_.is_empty()) {
344     gdk_property_delete(drag_context->source_window,
345                         ui::GetAtomForTarget(ui::DIRECT_SAVE_FILE));
346   }
347 
348   if (!drag_failed_) {
349     gfx::Point root = gtk_util::ScreenPoint(GetContentNativeView());
350     gfx::Point client = gtk_util::ClientPoint(GetContentNativeView());
351 
352     if (tab_contents()->render_view_host()) {
353       tab_contents()->render_view_host()->DragSourceEndedAt(
354           client.x(), client.y(), root.x(), root.y(),
355           gtk_util::GdkDragActionToWebDragOp(drag_context->action));
356     }
357   }
358 
359   tab_contents()->SystemDragEnded();
360 
361   drop_data_.reset();
362   drag_context_ = NULL;
363 }
364 
GetContentNativeView() const365 gfx::NativeView TabContentsDragSource::GetContentNativeView() const {
366   return tab_contents_view_->GetContentNativeView();
367 }
368 
OnDragIconExpose(GtkWidget * sender,GdkEventExpose * event)369 gboolean TabContentsDragSource::OnDragIconExpose(GtkWidget* sender,
370                                                  GdkEventExpose* event) {
371   cairo_t* cr = gdk_cairo_create(event->window);
372   gdk_cairo_rectangle(cr, &event->area);
373   cairo_clip(cr);
374   cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
375   gdk_cairo_set_source_pixbuf(cr, drag_pixbuf_, 0, 0);
376   cairo_paint(cr);
377   cairo_destroy(cr);
378 
379   return TRUE;
380 }
381