Remove PlatformFile from profile_browsertest
[chromium-blink-merge.git] / content / browser / web_contents / web_drag_source_gtk.cc
blob68a79f90e0e16a09390fb351a86fc84e0e71cf0b
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/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;
35 namespace content {
37 WebDragSourceGtk::WebDragSourceGtk(WebContents* web_contents)
38 : web_contents_(static_cast<WebContentsImpl*>(web_contents)),
39 drag_pixbuf_(NULL),
40 drag_failed_(false),
41 drag_widget_(gtk_invisible_new()),
42 drag_context_(NULL),
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),
48 this);
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.
60 if (drop_data_) {
61 gtk_grab_add(drag_widget_);
62 gtk_grab_remove(drag_widget_);
63 base::MessageLoopForUI::current()->RemoveObserver(this);
64 drop_data_.reset();
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.
77 if (drag_context_) {
78 NOTREACHED();
79 return false;
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_,
98 &download_file_name_,
99 &download_url_)) {
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_) {
145 drag_failed_ = true;
146 drop_data_.reset();
147 return false;
150 base::MessageLoopForUI::current()->AddObserver(this);
151 return true;
154 void WebDragSourceGtk::WillProcessEvent(GdkEvent* event) {
155 // No-op.
158 void WebDragSourceGtk::DidProcessEvent(GdkEvent* event) {
159 if (event->type != GDK_MOTION_NOTIFY)
160 return;
162 GdkEventMotion* event_motion = reinterpret_cast<GdkEventMotion*>(event);
163 gfx::Point client = ui::ClientPoint(GetContentNativeView());
165 if (web_contents_) {
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,
176 guint target_type,
177 guint time) {
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(),
184 utf8_text.length());
185 break;
188 case ui::TEXT_HTML: {
189 // TODO(estade): change relative links to be absolute using
190 // |html_base_url|.
191 std::string utf8_text = base::UTF16ToUTF8(drop_data_->html.string());
192 gtk_selection_data_set(selection_data,
193 ui::GetAtomForTarget(ui::TEXT_HTML),
194 kBitsPerByte,
195 reinterpret_cast<const guchar*>(utf8_text.c_str()),
196 utf8_text.length());
197 break;
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);
205 break;
208 case ui::CHROME_WEBDROP_FILE_CONTENTS: {
209 gtk_selection_data_set(
210 selection_data,
211 drag_file_mime_type_, kBitsPerByte,
212 reinterpret_cast<const guchar*>(drop_data_->file_contents.data()),
213 drop_data_->file_contents.length());
214 break;
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
222 // property.
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),
229 1024,
230 FALSE,
231 NULL,
232 NULL,
233 &file_url_len,
234 &file_url_value) &&
235 file_url_value) {
236 // Convert from the file url to the file path.
237 GURL file_url(std::string(reinterpret_cast<char*>(file_url_value),
238 file_url_len));
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(
245 &file_path,
246 GetContentClient()->browser()->GetNetLog()));
247 if (file_stream) {
248 // Start downloading the file to the stream.
249 scoped_refptr<DragDownloadFile> drag_file_downloader =
250 new DragDownloadFile(
251 file_path,
252 file_stream.Pass(),
253 download_url_,
254 Referrer(web_contents_->GetURL(),
255 drop_data_->referrer_policy),
256 web_contents_->GetEncoding(),
257 web_contents_);
258 drag_file_downloader->Start(
259 new PromiseFileFinalizer(drag_file_downloader.get()));
261 // Set the status code to success.
262 status_code = 'S';
266 // Return the status code to the file manager.
267 gtk_selection_data_set(selection_data,
268 gtk_selection_data_get_target(selection_data),
269 kBitsPerByte,
270 reinterpret_cast<guchar*>(&status_code),
273 break;
276 case ui::CUSTOM_DATA: {
277 Pickle custom_data;
278 ui::WriteCustomDataToPickle(drop_data_->custom_data, &custom_data);
279 gtk_selection_data_set(
280 selection_data,
281 ui::GetAtomForTarget(ui::CUSTOM_DATA),
282 kBitsPerByte,
283 reinterpret_cast<const guchar*>(custom_data.data()),
284 custom_data.size());
285 break;
288 case ui::RENDERER_TAINT: {
289 static const char kPlaceholder[] = "x";
290 gtk_selection_data_set(
291 selection_data,
292 ui::GetAtomForTarget(ui::RENDERER_TAINT),
293 kBitsPerByte,
294 reinterpret_cast<const guchar*>(kPlaceholder),
295 strlen(kPlaceholder));
296 break;
299 default:
300 NOTREACHED();
304 gboolean WebDragSourceGtk::OnDragFailed(GtkWidget* sender,
305 GdkDragContext* context,
306 GtkDragResult result) {
307 drag_failed_ = true;
309 gfx::Point root = ui::ScreenPoint(GetContentNativeView());
310 gfx::Point client = ui::ClientPoint(GetContentNativeView());
312 if (web_contents_) {
313 web_contents_->DragSourceEndedAt(
314 client.x(), client.y(), root.x(), root.y(),
315 WebDragOperationNone);
318 // Let the native failure animation run.
319 return FALSE;
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_,
330 std::string(),
331 std::string(),
332 download_file_name_.value(),
333 base::UTF16ToUTF8(wide_download_mime_type_),
334 default_name);
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());
348 if (drag_pixbuf_) {
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);
357 if (rgba)
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) {
368 if (drag_pixbuf_) {
369 g_object_unref(drag_pixbuf_);
370 drag_pixbuf_ = NULL;
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));
380 if (!drag_failed_) {
381 gfx::Point root = ui::ScreenPoint(GetContentNativeView());
382 gfx::Point client = ui::ClientPoint(GetContentNativeView());
384 if (web_contents_) {
385 web_contents_->DragSourceEndedAt(
386 client.x(), client.y(), root.x(), root.y(),
387 GdkDragActionToWebDragOp(drag_context->action));
391 web_contents_->SystemDragEnded();
393 drop_data_.reset();
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);
405 cairo_clip(cr);
406 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
407 gdk_cairo_set_source_pixbuf(cr, drag_pixbuf_, 0, 0);
408 cairo_paint(cr);
409 cairo_destroy(cr);
411 return TRUE;
414 } // namespace content