Broke ContentSettingBubbleModelTest.Plugins on Android.
[chromium-blink-merge.git] / content / browser / web_contents / web_drag_source_gtk.cc
blobb1dda3222b0366a9e7d4f93bf934fd895bfedd24
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"
7 #include <string>
9 #include "base/file_util.h"
10 #include "base/nix/mime_util_xdg.h"
11 #include "base/threading/thread_restrictions.h"
12 #include "base/utf_string_conversions.h"
13 #include "content/browser/download/drag_download_file.h"
14 #include "content/browser/download/drag_download_util.h"
15 #include "content/browser/renderer_host/render_view_host_delegate.h"
16 #include "content/browser/renderer_host/render_view_host_impl.h"
17 #include "content/browser/web_contents/drag_utils_gtk.h"
18 #include "content/browser/web_contents/web_contents_impl.h"
19 #include "content/public/browser/content_browser_client.h"
20 #include "content/public/browser/web_contents_view.h"
21 #include "content/public/common/content_client.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_compat.h"
28 #include "ui/base/gtk/gtk_screen_util.h"
29 #include "ui/gfx/gtk_util.h"
30 #include "webkit/glue/webdropdata.h"
32 using WebKit::WebDragOperation;
33 using WebKit::WebDragOperationsMask;
34 using WebKit::WebDragOperationNone;
36 namespace content {
38 WebDragSourceGtk::WebDragSourceGtk(WebContents* web_contents)
39 : web_contents_(web_contents),
40 drag_pixbuf_(NULL),
41 drag_failed_(false),
42 drag_widget_(gtk_invisible_new()),
43 drag_context_(NULL),
44 drag_icon_(gtk_window_new(GTK_WINDOW_POPUP)) {
45 signals_.Connect(drag_widget_, "drag-failed",
46 G_CALLBACK(OnDragFailedThunk), this);
47 signals_.Connect(drag_widget_, "drag-begin",
48 G_CALLBACK(OnDragBeginThunk),
49 this);
50 signals_.Connect(drag_widget_, "drag-end",
51 G_CALLBACK(OnDragEndThunk), this);
52 signals_.Connect(drag_widget_, "drag-data-get",
53 G_CALLBACK(OnDragDataGetThunk), this);
55 signals_.Connect(drag_icon_, "expose-event",
56 G_CALLBACK(OnDragIconExposeThunk), this);
59 WebDragSourceGtk::~WebDragSourceGtk() {
60 // Break the current drag, if any.
61 if (drop_data_.get()) {
62 gtk_grab_add(drag_widget_);
63 gtk_grab_remove(drag_widget_);
64 MessageLoopForUI::current()->RemoveObserver(this);
65 drop_data_.reset();
68 gtk_widget_destroy(drag_widget_);
69 gtk_widget_destroy(drag_icon_);
72 void WebDragSourceGtk::StartDragging(const WebDropData& drop_data,
73 WebDragOperationsMask allowed_ops,
74 GdkEventButton* last_mouse_down,
75 const SkBitmap& image,
76 const gfx::Vector2d& image_offset) {
77 // Guard against re-starting before previous drag completed.
78 if (drag_context_) {
79 NOTREACHED();
80 web_contents_->SystemDragEnded();
81 return;
84 int targets_mask = 0;
86 if (!drop_data.text.string().empty())
87 targets_mask |= ui::TEXT_PLAIN;
88 if (drop_data.url.is_valid()) {
89 targets_mask |= ui::TEXT_URI_LIST;
90 targets_mask |= ui::CHROME_NAMED_URL;
91 targets_mask |= ui::NETSCAPE_URL;
93 if (!drop_data.html.string().empty())
94 targets_mask |= ui::TEXT_HTML;
95 if (!drop_data.file_contents.empty())
96 targets_mask |= ui::CHROME_WEBDROP_FILE_CONTENTS;
97 if (!drop_data.download_metadata.empty() &&
98 ParseDownloadMetadata(drop_data.download_metadata,
99 &wide_download_mime_type_,
100 &download_file_name_,
101 &download_url_)) {
102 targets_mask |= ui::DIRECT_SAVE_FILE;
104 if (!drop_data.custom_data.empty())
105 targets_mask |= ui::CUSTOM_DATA;
107 // NOTE: Begin a drag even if no targets present. Otherwise, things like
108 // draggable list elements will not work.
110 drop_data_.reset(new WebDropData(drop_data));
112 // The image we get from WebKit makes heavy use of alpha-shading. This looks
113 // bad on non-compositing WMs. Fall back to the default drag icon.
114 if (!image.isNull() && ui::IsScreenComposited())
115 drag_pixbuf_ = gfx::GdkPixbufFromSkBitmap(image);
116 image_offset_ = image_offset;
118 GtkTargetList* list = ui::GetTargetListFromCodeMask(targets_mask);
119 if (targets_mask & ui::CHROME_WEBDROP_FILE_CONTENTS) {
120 // Looking up the mime type can hit the disk. http://crbug.com/84896
121 base::ThreadRestrictions::ScopedAllowIO allow_io;
122 drag_file_mime_type_ = gdk_atom_intern(
123 base::nix::GetDataMimeType(drop_data.file_contents).c_str(), FALSE);
124 gtk_target_list_add(list, drag_file_mime_type_,
125 0, ui::CHROME_WEBDROP_FILE_CONTENTS);
128 drag_failed_ = false;
129 // If we don't pass an event, GDK won't know what event time to start grabbing
130 // mouse events. Technically it's the mouse motion event and not the mouse
131 // down event that causes the drag, but there's no reliable way to know
132 // *which* motion event initiated the drag, so this will have to do.
133 // TODO(estade): This can sometimes be very far off, e.g. if the user clicks
134 // and holds and doesn't start dragging for a long time. I doubt it matters
135 // much, but we should probably look into the possibility of getting the
136 // initiating event from webkit.
137 drag_context_ = gtk_drag_begin(drag_widget_, list,
138 WebDragOpToGdkDragAction(allowed_ops),
139 1, // Drags are always initiated by the left button.
140 reinterpret_cast<GdkEvent*>(last_mouse_down));
141 // The drag adds a ref; let it own the list.
142 gtk_target_list_unref(list);
144 // Sometimes the drag fails to start; |context| will be NULL and we won't
145 // get a drag-end signal.
146 if (!drag_context_) {
147 drag_failed_ = true;
148 drop_data_.reset();
149 web_contents_->SystemDragEnded();
150 return;
153 MessageLoopForUI::current()->AddObserver(this);
156 void WebDragSourceGtk::WillProcessEvent(GdkEvent* event) {
157 // No-op.
160 void WebDragSourceGtk::DidProcessEvent(GdkEvent* event) {
161 if (event->type != GDK_MOTION_NOTIFY)
162 return;
164 GdkEventMotion* event_motion = reinterpret_cast<GdkEventMotion*>(event);
165 gfx::Point client = ui::ClientPoint(GetContentNativeView());
167 if (GetRenderViewHost()) {
168 GetRenderViewHost()->DragSourceMovedTo(
169 client.x(), client.y(),
170 static_cast<int>(event_motion->x_root),
171 static_cast<int>(event_motion->y_root));
175 void WebDragSourceGtk::OnDragDataGet(GtkWidget* sender,
176 GdkDragContext* context,
177 GtkSelectionData* selection_data,
178 guint target_type,
179 guint time) {
180 const int kBitsPerByte = 8;
182 switch (target_type) {
183 case ui::TEXT_PLAIN: {
184 std::string utf8_text = UTF16ToUTF8(drop_data_->text.string());
185 gtk_selection_data_set_text(selection_data, utf8_text.c_str(),
186 utf8_text.length());
187 break;
190 case ui::TEXT_HTML: {
191 // TODO(estade): change relative links to be absolute using
192 // |html_base_url|.
193 std::string utf8_text = UTF16ToUTF8(drop_data_->html.string());
194 gtk_selection_data_set(selection_data,
195 ui::GetAtomForTarget(ui::TEXT_HTML),
196 kBitsPerByte,
197 reinterpret_cast<const guchar*>(utf8_text.c_str()),
198 utf8_text.length());
199 break;
202 case ui::TEXT_URI_LIST:
203 case ui::CHROME_NAMED_URL:
204 case ui::NETSCAPE_URL: {
205 ui::WriteURLWithName(selection_data, drop_data_->url,
206 drop_data_->url_title, target_type);
207 break;
210 case ui::CHROME_WEBDROP_FILE_CONTENTS: {
211 gtk_selection_data_set(
212 selection_data,
213 drag_file_mime_type_, kBitsPerByte,
214 reinterpret_cast<const guchar*>(drop_data_->file_contents.data()),
215 drop_data_->file_contents.length());
216 break;
219 case ui::DIRECT_SAVE_FILE: {
220 char status_code = 'E';
222 // Retrieves the full file path (in file URL format) provided by the
223 // drop target by reading from the source window's XdndDirectSave0
224 // property.
225 gint file_url_len = 0;
226 guchar* file_url_value = NULL;
227 if (gdk_property_get(context->source_window,
228 ui::GetAtomForTarget(ui::DIRECT_SAVE_FILE),
229 ui::GetAtomForTarget(ui::TEXT_PLAIN_NO_CHARSET),
231 1024,
232 FALSE,
233 NULL,
234 NULL,
235 &file_url_len,
236 &file_url_value) &&
237 file_url_value) {
238 // Convert from the file url to the file path.
239 GURL file_url(std::string(reinterpret_cast<char*>(file_url_value),
240 file_url_len));
241 g_free(file_url_value);
242 FilePath file_path;
243 if (net::FileURLToFilePath(file_url, &file_path)) {
244 // Open the file as a stream.
245 scoped_ptr<net::FileStream> file_stream(
246 CreateFileStreamForDrop(
247 &file_path,
248 GetContentClient()->browser()->GetNetLog()));
249 if (file_stream.get()) {
250 // Start downloading the file to the stream.
251 scoped_refptr<DragDownloadFile> drag_file_downloader =
252 new DragDownloadFile(
253 file_path,
254 file_stream.Pass(),
255 download_url_,
256 Referrer(web_contents_->GetURL(),
257 drop_data_->referrer_policy),
258 web_contents_->GetEncoding(),
259 web_contents_);
260 drag_file_downloader->Start(
261 new PromiseFileFinalizer(drag_file_downloader));
263 // Set the status code to success.
264 status_code = 'S';
268 // Return the status code to the file manager.
269 gtk_selection_data_set(selection_data,
270 gtk_selection_data_get_target(selection_data),
271 kBitsPerByte,
272 reinterpret_cast<guchar*>(&status_code),
275 break;
278 case ui::CUSTOM_DATA: {
279 Pickle custom_data;
280 ui::WriteCustomDataToPickle(drop_data_->custom_data, &custom_data);
281 gtk_selection_data_set(
282 selection_data,
283 ui::GetAtomForTarget(ui::CUSTOM_DATA),
284 kBitsPerByte,
285 reinterpret_cast<const guchar*>(custom_data.data()),
286 custom_data.size());
287 break;
290 default:
291 NOTREACHED();
295 gboolean WebDragSourceGtk::OnDragFailed(GtkWidget* sender,
296 GdkDragContext* context,
297 GtkDragResult result) {
298 drag_failed_ = true;
300 gfx::Point root = ui::ScreenPoint(GetContentNativeView());
301 gfx::Point client = ui::ClientPoint(GetContentNativeView());
303 if (GetRenderViewHost()) {
304 GetRenderViewHost()->DragSourceEndedAt(
305 client.x(), client.y(), root.x(), root.y(),
306 WebDragOperationNone);
309 // Let the native failure animation run.
310 return FALSE;
313 void WebDragSourceGtk::OnDragBegin(GtkWidget* sender,
314 GdkDragContext* drag_context) {
315 if (!download_url_.is_empty()) {
316 // Generate the file name based on both mime type and proposed file name.
317 std::string default_name =
318 GetContentClient()->browser()->GetDefaultDownloadName();
319 FilePath generated_download_file_name =
320 net::GenerateFileName(download_url_,
321 std::string(),
322 std::string(),
323 download_file_name_.value(),
324 UTF16ToUTF8(wide_download_mime_type_),
325 default_name);
327 // Pass the file name to the drop target by setting the source window's
328 // XdndDirectSave0 property.
329 gdk_property_change(drag_context->source_window,
330 ui::GetAtomForTarget(ui::DIRECT_SAVE_FILE),
331 ui::GetAtomForTarget(ui::TEXT_PLAIN_NO_CHARSET),
333 GDK_PROP_MODE_REPLACE,
334 reinterpret_cast<const guchar*>(
335 generated_download_file_name.value().c_str()),
336 generated_download_file_name.value().length());
339 if (drag_pixbuf_) {
340 gtk_widget_set_size_request(drag_icon_,
341 gdk_pixbuf_get_width(drag_pixbuf_),
342 gdk_pixbuf_get_height(drag_pixbuf_));
344 // We only need to do this once.
345 if (!gtk_widget_get_realized(drag_icon_)) {
346 GdkScreen* screen = gtk_widget_get_screen(drag_icon_);
347 GdkColormap* rgba = gdk_screen_get_rgba_colormap(screen);
348 if (rgba)
349 gtk_widget_set_colormap(drag_icon_, rgba);
352 gtk_drag_set_icon_widget(drag_context, drag_icon_,
353 image_offset_.x(), image_offset_.y());
357 void WebDragSourceGtk::OnDragEnd(GtkWidget* sender,
358 GdkDragContext* drag_context) {
359 if (drag_pixbuf_) {
360 g_object_unref(drag_pixbuf_);
361 drag_pixbuf_ = NULL;
364 MessageLoopForUI::current()->RemoveObserver(this);
366 if (!download_url_.is_empty()) {
367 gdk_property_delete(drag_context->source_window,
368 ui::GetAtomForTarget(ui::DIRECT_SAVE_FILE));
371 if (!drag_failed_) {
372 gfx::Point root = ui::ScreenPoint(GetContentNativeView());
373 gfx::Point client = ui::ClientPoint(GetContentNativeView());
375 if (GetRenderViewHost()) {
376 GetRenderViewHost()->DragSourceEndedAt(
377 client.x(), client.y(), root.x(), root.y(),
378 GdkDragActionToWebDragOp(drag_context->action));
382 web_contents_->SystemDragEnded();
384 drop_data_.reset();
385 drag_context_ = NULL;
388 RenderViewHostImpl* WebDragSourceGtk::GetRenderViewHost() const {
389 return static_cast<RenderViewHostImpl*>(web_contents_->GetRenderViewHost());
392 gfx::NativeView WebDragSourceGtk::GetContentNativeView() const {
393 return web_contents_->GetView()->GetContentNativeView();
396 gboolean WebDragSourceGtk::OnDragIconExpose(GtkWidget* sender,
397 GdkEventExpose* event) {
398 cairo_t* cr = gdk_cairo_create(event->window);
399 gdk_cairo_rectangle(cr, &event->area);
400 cairo_clip(cr);
401 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
402 gdk_cairo_set_source_pixbuf(cr, drag_pixbuf_, 0, 0);
403 cairo_paint(cr);
404 cairo_destroy(cr);
406 return TRUE;
409 } // namespace content