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.
5 #include "content/browser/web_contents/web_drag_source_gtk.h"
9 #include "base/nix/mime_util_xdg.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "base/threading/thread_restrictions.h"
12 #include "content/browser/download/drag_download_file.h"
13 #include "content/browser/download/drag_download_util.h"
14 #include "content/browser/renderer_host/render_view_host_delegate.h"
15 #include "content/browser/renderer_host/render_view_host_impl.h"
16 #include "content/browser/web_contents/drag_utils_gtk.h"
17 #include "content/browser/web_contents/web_contents_impl.h"
18 #include "content/public/browser/content_browser_client.h"
19 #include "content/public/browser/web_contents_view.h"
20 #include "content/public/common/content_client.h"
21 #include "content/public/common/drop_data.h"
22 #include "net/base/file_stream.h"
23 #include "net/base/net_util.h"
24 #include "third_party/skia/include/core/SkBitmap.h"
25 #include "ui/base/clipboard/custom_data_helper.h"
26 #include "ui/base/dragdrop/gtk_dnd_util.h"
27 #include "ui/base/gtk/gtk_screen_util.h"
28 #include "ui/gfx/gtk_compat.h"
29 #include "ui/gfx/gtk_util.h"
31 using blink::WebDragOperation
;
32 using blink::WebDragOperationsMask
;
33 using blink::WebDragOperationNone
;
37 WebDragSourceGtk::WebDragSourceGtk(WebContents
* web_contents
)
38 : web_contents_(static_cast<WebContentsImpl
*>(web_contents
)),
41 drag_widget_(gtk_invisible_new()),
43 drag_icon_(gtk_window_new(GTK_WINDOW_POPUP
)) {
44 signals_
.Connect(drag_widget_
, "drag-failed",
45 G_CALLBACK(OnDragFailedThunk
), this);
46 signals_
.Connect(drag_widget_
, "drag-begin",
47 G_CALLBACK(OnDragBeginThunk
),
49 signals_
.Connect(drag_widget_
, "drag-end",
50 G_CALLBACK(OnDragEndThunk
), this);
51 signals_
.Connect(drag_widget_
, "drag-data-get",
52 G_CALLBACK(OnDragDataGetThunk
), this);
54 signals_
.Connect(drag_icon_
, "expose-event",
55 G_CALLBACK(OnDragIconExposeThunk
), this);
58 WebDragSourceGtk::~WebDragSourceGtk() {
59 // Break the current drag, if any.
61 gtk_grab_add(drag_widget_
);
62 gtk_grab_remove(drag_widget_
);
63 base::MessageLoopForUI::current()->RemoveObserver(this);
67 gtk_widget_destroy(drag_widget_
);
68 gtk_widget_destroy(drag_icon_
);
71 bool WebDragSourceGtk::StartDragging(const DropData
& drop_data
,
72 WebDragOperationsMask allowed_ops
,
73 GdkEventButton
* last_mouse_down
,
74 const SkBitmap
& image
,
75 const gfx::Vector2d
& image_offset
) {
76 // Guard against re-starting before previous drag completed.
82 int targets_mask
= ui::RENDERER_TAINT
;
84 if (!drop_data
.text
.string().empty())
85 targets_mask
|= ui::TEXT_PLAIN
;
86 if (drop_data
.url
.is_valid()) {
87 targets_mask
|= ui::TEXT_URI_LIST
;
88 targets_mask
|= ui::CHROME_NAMED_URL
;
89 targets_mask
|= ui::NETSCAPE_URL
;
91 if (!drop_data
.html
.string().empty())
92 targets_mask
|= ui::TEXT_HTML
;
93 if (!drop_data
.file_contents
.empty())
94 targets_mask
|= ui::CHROME_WEBDROP_FILE_CONTENTS
;
95 if (!drop_data
.download_metadata
.empty() &&
96 ParseDownloadMetadata(drop_data
.download_metadata
,
97 &wide_download_mime_type_
,
100 targets_mask
|= ui::DIRECT_SAVE_FILE
;
102 if (!drop_data
.custom_data
.empty())
103 targets_mask
|= ui::CUSTOM_DATA
;
105 // NOTE: Begin a drag even if no targets present. Otherwise, things like
106 // draggable list elements will not work.
108 drop_data_
.reset(new DropData(drop_data
));
110 // The image we get from WebKit makes heavy use of alpha-shading. This looks
111 // bad on non-compositing WMs. Fall back to the default drag icon.
112 if (!image
.isNull() && ui::IsScreenComposited())
113 drag_pixbuf_
= gfx::GdkPixbufFromSkBitmap(image
);
114 image_offset_
= image_offset
;
116 GtkTargetList
* list
= ui::GetTargetListFromCodeMask(targets_mask
);
117 if (targets_mask
& ui::CHROME_WEBDROP_FILE_CONTENTS
) {
118 // Looking up the mime type can hit the disk. http://crbug.com/84896
119 base::ThreadRestrictions::ScopedAllowIO allow_io
;
120 drag_file_mime_type_
= gdk_atom_intern(
121 base::nix::GetDataMimeType(drop_data
.file_contents
).c_str(), FALSE
);
122 gtk_target_list_add(list
, drag_file_mime_type_
,
123 0, ui::CHROME_WEBDROP_FILE_CONTENTS
);
126 drag_failed_
= false;
127 // If we don't pass an event, GDK won't know what event time to start grabbing
128 // mouse events. Technically it's the mouse motion event and not the mouse
129 // down event that causes the drag, but there's no reliable way to know
130 // *which* motion event initiated the drag, so this will have to do.
131 // TODO(estade): This can sometimes be very far off, e.g. if the user clicks
132 // and holds and doesn't start dragging for a long time. I doubt it matters
133 // much, but we should probably look into the possibility of getting the
134 // initiating event from webkit.
135 drag_context_
= gtk_drag_begin(drag_widget_
, list
,
136 WebDragOpToGdkDragAction(allowed_ops
),
137 1, // Drags are always initiated by the left button.
138 reinterpret_cast<GdkEvent
*>(last_mouse_down
));
139 // The drag adds a ref; let it own the list.
140 gtk_target_list_unref(list
);
142 // Sometimes the drag fails to start; |context| will be NULL and we won't
143 // get a drag-end signal.
144 if (!drag_context_
) {
150 base::MessageLoopForUI::current()->AddObserver(this);
154 void WebDragSourceGtk::WillProcessEvent(GdkEvent
* event
) {
158 void WebDragSourceGtk::DidProcessEvent(GdkEvent
* event
) {
159 if (event
->type
!= GDK_MOTION_NOTIFY
)
162 GdkEventMotion
* event_motion
= reinterpret_cast<GdkEventMotion
*>(event
);
163 gfx::Point client
= ui::ClientPoint(GetContentNativeView());
166 web_contents_
->DragSourceMovedTo(
167 client
.x(), client
.y(),
168 static_cast<int>(event_motion
->x_root
),
169 static_cast<int>(event_motion
->y_root
));
173 void WebDragSourceGtk::OnDragDataGet(GtkWidget
* sender
,
174 GdkDragContext
* context
,
175 GtkSelectionData
* selection_data
,
178 const int kBitsPerByte
= 8;
180 switch (target_type
) {
181 case ui::TEXT_PLAIN
: {
182 std::string utf8_text
= base::UTF16ToUTF8(drop_data_
->text
.string());
183 gtk_selection_data_set_text(selection_data
, utf8_text
.c_str(),
188 case ui::TEXT_HTML
: {
189 // TODO(estade): change relative links to be absolute using
191 std::string utf8_text
= base::UTF16ToUTF8(drop_data_
->html
.string());
192 gtk_selection_data_set(selection_data
,
193 ui::GetAtomForTarget(ui::TEXT_HTML
),
195 reinterpret_cast<const guchar
*>(utf8_text
.c_str()),
200 case ui::TEXT_URI_LIST
:
201 case ui::CHROME_NAMED_URL
:
202 case ui::NETSCAPE_URL
: {
203 ui::WriteURLWithName(selection_data
, drop_data_
->url
,
204 drop_data_
->url_title
, target_type
);
208 case ui::CHROME_WEBDROP_FILE_CONTENTS
: {
209 gtk_selection_data_set(
211 drag_file_mime_type_
, kBitsPerByte
,
212 reinterpret_cast<const guchar
*>(drop_data_
->file_contents
.data()),
213 drop_data_
->file_contents
.length());
217 case ui::DIRECT_SAVE_FILE
: {
218 char status_code
= 'E';
220 // Retrieves the full file path (in file URL format) provided by the
221 // drop target by reading from the source window's XdndDirectSave0
223 gint file_url_len
= 0;
224 guchar
* file_url_value
= NULL
;
225 if (gdk_property_get(context
->source_window
,
226 ui::GetAtomForTarget(ui::DIRECT_SAVE_FILE
),
227 ui::GetAtomForTarget(ui::TEXT_PLAIN_NO_CHARSET
),
236 // Convert from the file url to the file path.
237 GURL
file_url(std::string(reinterpret_cast<char*>(file_url_value
),
239 g_free(file_url_value
);
240 base::FilePath file_path
;
241 if (net::FileURLToFilePath(file_url
, &file_path
)) {
242 // Open the file as a stream.
243 scoped_ptr
<net::FileStream
> file_stream(
244 CreateFileStreamForDrop(
246 GetContentClient()->browser()->GetNetLog()));
248 // Start downloading the file to the stream.
249 scoped_refptr
<DragDownloadFile
> drag_file_downloader
=
250 new DragDownloadFile(
254 Referrer(web_contents_
->GetURL(),
255 drop_data_
->referrer_policy
),
256 web_contents_
->GetEncoding(),
258 drag_file_downloader
->Start(
259 new PromiseFileFinalizer(drag_file_downloader
.get()));
261 // Set the status code to success.
266 // Return the status code to the file manager.
267 gtk_selection_data_set(selection_data
,
268 gtk_selection_data_get_target(selection_data
),
270 reinterpret_cast<guchar
*>(&status_code
),
276 case ui::CUSTOM_DATA
: {
278 ui::WriteCustomDataToPickle(drop_data_
->custom_data
, &custom_data
);
279 gtk_selection_data_set(
281 ui::GetAtomForTarget(ui::CUSTOM_DATA
),
283 reinterpret_cast<const guchar
*>(custom_data
.data()),
288 case ui::RENDERER_TAINT
: {
289 static const char kPlaceholder
[] = "x";
290 gtk_selection_data_set(
292 ui::GetAtomForTarget(ui::RENDERER_TAINT
),
294 reinterpret_cast<const guchar
*>(kPlaceholder
),
295 strlen(kPlaceholder
));
304 gboolean
WebDragSourceGtk::OnDragFailed(GtkWidget
* sender
,
305 GdkDragContext
* context
,
306 GtkDragResult result
) {
309 gfx::Point root
= ui::ScreenPoint(GetContentNativeView());
310 gfx::Point client
= ui::ClientPoint(GetContentNativeView());
313 web_contents_
->DragSourceEndedAt(
314 client
.x(), client
.y(), root
.x(), root
.y(),
315 WebDragOperationNone
);
318 // Let the native failure animation run.
322 void WebDragSourceGtk::OnDragBegin(GtkWidget
* sender
,
323 GdkDragContext
* drag_context
) {
324 if (!download_url_
.is_empty()) {
325 // Generate the file name based on both mime type and proposed file name.
326 std::string default_name
=
327 GetContentClient()->browser()->GetDefaultDownloadName();
328 base::FilePath generated_download_file_name
=
329 net::GenerateFileName(download_url_
,
332 download_file_name_
.value(),
333 base::UTF16ToUTF8(wide_download_mime_type_
),
336 // Pass the file name to the drop target by setting the source window's
337 // XdndDirectSave0 property.
338 gdk_property_change(drag_context
->source_window
,
339 ui::GetAtomForTarget(ui::DIRECT_SAVE_FILE
),
340 ui::GetAtomForTarget(ui::TEXT_PLAIN_NO_CHARSET
),
342 GDK_PROP_MODE_REPLACE
,
343 reinterpret_cast<const guchar
*>(
344 generated_download_file_name
.value().c_str()),
345 generated_download_file_name
.value().length());
349 gtk_widget_set_size_request(drag_icon_
,
350 gdk_pixbuf_get_width(drag_pixbuf_
),
351 gdk_pixbuf_get_height(drag_pixbuf_
));
353 // We only need to do this once.
354 if (!gtk_widget_get_realized(drag_icon_
)) {
355 GdkScreen
* screen
= gtk_widget_get_screen(drag_icon_
);
356 GdkColormap
* rgba
= gdk_screen_get_rgba_colormap(screen
);
358 gtk_widget_set_colormap(drag_icon_
, rgba
);
361 gtk_drag_set_icon_widget(drag_context
, drag_icon_
,
362 image_offset_
.x(), image_offset_
.y());
366 void WebDragSourceGtk::OnDragEnd(GtkWidget
* sender
,
367 GdkDragContext
* drag_context
) {
369 g_object_unref(drag_pixbuf_
);
373 base::MessageLoopForUI::current()->RemoveObserver(this);
375 if (!download_url_
.is_empty()) {
376 gdk_property_delete(drag_context
->source_window
,
377 ui::GetAtomForTarget(ui::DIRECT_SAVE_FILE
));
381 gfx::Point root
= ui::ScreenPoint(GetContentNativeView());
382 gfx::Point client
= ui::ClientPoint(GetContentNativeView());
385 web_contents_
->DragSourceEndedAt(
386 client
.x(), client
.y(), root
.x(), root
.y(),
387 GdkDragActionToWebDragOp(drag_context
->action
));
391 web_contents_
->SystemDragEnded();
394 drag_context_
= NULL
;
397 gfx::NativeView
WebDragSourceGtk::GetContentNativeView() const {
398 return web_contents_
->GetView()->GetContentNativeView();
401 gboolean
WebDragSourceGtk::OnDragIconExpose(GtkWidget
* sender
,
402 GdkEventExpose
* event
) {
403 cairo_t
* cr
= gdk_cairo_create(event
->window
);
404 gdk_cairo_rectangle(cr
, &event
->area
);
406 cairo_set_operator(cr
, CAIRO_OPERATOR_SOURCE
);
407 gdk_cairo_set_source_pixbuf(cr
, drag_pixbuf_
, 0, 0);
414 } // namespace content