Upstream version 7.35.144.0
[platform/framework/web/crosswalk.git] / src / content / browser / web_contents / web_drag_dest_gtk.cc
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/web_contents/web_drag_dest_gtk.h"
6
7 #include <string>
8
9 #include "base/bind.h"
10 #include "base/files/file_path.h"
11 #include "base/message_loop/message_loop.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "content/browser/renderer_host/render_view_host_impl.h"
14 #include "content/browser/web_contents/drag_utils_gtk.h"
15 #include "content/browser/web_contents/web_contents_impl.h"
16 #include "content/public/browser/web_contents_delegate.h"
17 #include "content/public/browser/web_drag_dest_delegate.h"
18 #include "content/public/common/url_constants.h"
19 #include "net/base/net_util.h"
20 #include "third_party/WebKit/public/web/WebInputEvent.h"
21 #include "ui/base/clipboard/custom_data_helper.h"
22 #include "ui/base/dragdrop/gtk_dnd_util.h"
23 #include "ui/base/gtk/gtk_screen_util.h"
24
25 using blink::WebDragOperation;
26 using blink::WebDragOperationNone;
27
28 namespace content {
29
30 namespace {
31 const int kNumGtkHandlers = 5;
32
33 int GetModifierFlags(GtkWidget* widget) {
34   int modifier_state = 0;
35   GdkModifierType state;
36   gdk_window_get_pointer(gtk_widget_get_window(widget), NULL, NULL, &state);
37
38   if (state & GDK_SHIFT_MASK)
39     modifier_state |= blink::WebInputEvent::ShiftKey;
40   if (state & GDK_CONTROL_MASK)
41     modifier_state |= blink::WebInputEvent::ControlKey;
42   if (state & GDK_MOD1_MASK)
43     modifier_state |= blink::WebInputEvent::AltKey;
44   if (state & GDK_META_MASK)
45     modifier_state |= blink::WebInputEvent::MetaKey;
46   return modifier_state;
47 }
48
49 }  // namespace
50
51 WebDragDestGtk::WebDragDestGtk(WebContents* web_contents, GtkWidget* widget)
52     : web_contents_(web_contents),
53       widget_(widget),
54       context_(NULL),
55       data_requests_(0),
56       delegate_(NULL),
57       canceled_(false),
58       method_factory_(this) {
59   gtk_drag_dest_set(widget, static_cast<GtkDestDefaults>(0),
60                     NULL, 0,
61                     static_cast<GdkDragAction>(GDK_ACTION_COPY |
62                                                GDK_ACTION_LINK |
63                                                GDK_ACTION_MOVE));
64
65   // If adding a handler, make sure to update kNumGtkHandlers and add it to the
66   // |handlers_| array so that it can be disconnected later on.
67   handlers_.reset(new int[kNumGtkHandlers]);
68   handlers_.get()[0] = g_signal_connect(
69       widget, "drag-motion", G_CALLBACK(OnDragMotionThunk), this);
70   handlers_.get()[1] = g_signal_connect(
71       widget, "drag-leave", G_CALLBACK(OnDragLeaveThunk), this);
72   handlers_.get()[2] = g_signal_connect(
73       widget, "drag-drop", G_CALLBACK(OnDragDropThunk), this);
74   handlers_.get()[3] = g_signal_connect(
75       widget, "drag-data-received", G_CALLBACK(OnDragDataReceivedThunk), this);
76   // TODO(tony): Need a drag-data-delete handler for moving content out of
77   // the WebContents.  http://crbug.com/38989
78
79   handlers_.get()[4] = g_signal_connect(
80       widget, "destroy", G_CALLBACK(gtk_widget_destroyed), &widget_);
81 }
82
83 WebDragDestGtk::~WebDragDestGtk() {
84   if (widget_) {
85     gtk_drag_dest_unset(widget_);
86     for (int i = 0; i < kNumGtkHandlers; ++i)
87       g_signal_handler_disconnect(widget_, handlers_.get()[i]);
88   }
89 }
90
91 void WebDragDestGtk::UpdateDragStatus(WebDragOperation operation) {
92   if (context_) {
93     is_drop_target_ = operation != WebDragOperationNone;
94     gdk_drag_status(context_, WebDragOpToGdkDragAction(operation),
95                     drag_over_time_);
96   }
97 }
98
99 void WebDragDestGtk::DragLeave() {
100   GetRenderViewHost()->DragTargetDragLeave();
101   if (delegate())
102     delegate()->OnDragLeave();
103
104   drop_data_.reset();
105 }
106
107 gboolean WebDragDestGtk::OnDragMotion(GtkWidget* sender,
108                                       GdkDragContext* context,
109                                       gint x, gint y,
110                                       guint time) {
111   if (context_ != context) {
112     context_ = context;
113     drop_data_.reset(new DropData);
114     is_drop_target_ = false;
115
116     if (delegate())
117       delegate()->DragInitialize(web_contents_);
118
119     // text/plain must come before text/uri-list. This is a hack that works in
120     // conjunction with OnDragDataReceived. Since some file managers populate
121     // text/plain with file URLs when dragging files, we want to handle
122     // text/uri-list after text/plain so that the plain text can be cleared if
123     // it's a file drag.
124     // Similarly, renderer taint must occur before anything else so we can
125     // ignore potentially forged filenames when handling text/uri-list.
126     static int supported_targets[] = {
127       ui::RENDERER_TAINT,
128       ui::TEXT_PLAIN,
129       ui::TEXT_URI_LIST,
130       ui::TEXT_HTML,
131       ui::NETSCAPE_URL,
132       ui::CHROME_NAMED_URL,
133       // TODO(estade): support image drags?
134       ui::CUSTOM_DATA,
135     };
136
137     // Add the delegate's requested target if applicable. Need to do this here
138     // since gtk_drag_get_data will dispatch to our drag-data-received.
139     data_requests_ = arraysize(supported_targets) + (delegate() ? 1 : 0);
140     for (size_t i = 0; i < arraysize(supported_targets); ++i) {
141       gtk_drag_get_data(widget_, context,
142                         ui::GetAtomForTarget(supported_targets[i]),
143                         time);
144     }
145
146     if (delegate()) {
147       gtk_drag_get_data(widget_, context, delegate()->GetBookmarkTargetAtom(),
148                         time);
149     }
150   } else if (data_requests_ == 0) {
151     if (canceled_)
152       return FALSE;
153
154     GetRenderViewHost()->DragTargetDragOver(
155         ui::ClientPoint(widget_),
156         ui::ScreenPoint(widget_),
157         GdkDragActionToWebDragOp(context->actions),
158         GetModifierFlags(widget_));
159
160     if (delegate())
161       delegate()->OnDragOver();
162
163     drag_over_time_ = time;
164   }
165
166   // Pretend we are a drag destination because we don't want to wait for
167   // the renderer to tell us if we really are or not.
168   return TRUE;
169 }
170
171 void WebDragDestGtk::OnDragDataReceived(
172     GtkWidget* sender, GdkDragContext* context, gint x, gint y,
173     GtkSelectionData* data, guint info, guint time) {
174   // We might get the data from an old get_data() request that we no longer
175   // care about.
176   if (context != context_)
177     return;
178
179   data_requests_--;
180
181   // Decode the data.
182   gint data_length = gtk_selection_data_get_length(data);
183   const guchar* raw_data = gtk_selection_data_get_data(data);
184   GdkAtom target = gtk_selection_data_get_target(data);
185   if (raw_data && data_length > 0) {
186     // If the source can't provide us with valid data for a requested target,
187     // raw_data will be NULL.
188     if (target == ui::GetAtomForTarget(ui::RENDERER_TAINT)) {
189       drop_data_->did_originate_from_renderer = true;
190     } else if (target == ui::GetAtomForTarget(ui::TEXT_PLAIN)) {
191       guchar* text = gtk_selection_data_get_text(data);
192       if (text) {
193         drop_data_->text = base::NullableString16(
194             base::UTF8ToUTF16(std::string(reinterpret_cast<const char*>(text))),
195             false);
196         g_free(text);
197       }
198     } else if (target == ui::GetAtomForTarget(ui::TEXT_URI_LIST)) {
199       gchar** uris = gtk_selection_data_get_uris(data);
200       if (uris) {
201         drop_data_->url = GURL();
202         for (gchar** uri_iter = uris; *uri_iter; uri_iter++) {
203           // Most file managers populate text/uri-list with file URLs when
204           // dragging files. To avoid exposing file system paths to web content,
205           // file URLs are never set as the URL content for the drop.
206           // TODO(estade): Can the filenames have a non-UTF8 encoding?
207           GURL url(*uri_iter);
208           base::FilePath file_path;
209           if (url.SchemeIs(kFileScheme) &&
210               net::FileURLToFilePath(url, &file_path)) {
211             drop_data_->filenames.push_back(
212                 ui::FileInfo(file_path, base::FilePath()));
213             // This is a hack. Some file managers also populate text/plain with
214             // a file URL when dragging files, so we clear it to avoid exposing
215             // it to the web content.
216             drop_data_->text = base::NullableString16();
217           } else if (!drop_data_->url.is_valid()) {
218             // Also set the first non-file URL as the URL content for the drop.
219             drop_data_->url = url;
220           }
221         }
222         g_strfreev(uris);
223       }
224     } else if (target == ui::GetAtomForTarget(ui::TEXT_HTML)) {
225       // TODO(estade): Can the html have a non-UTF8 encoding?
226       drop_data_->html = base::NullableString16(
227           base::UTF8ToUTF16(std::string(reinterpret_cast<const char*>(raw_data),
228                                   data_length)),
229           false);
230       // We leave the base URL empty.
231     } else if (target == ui::GetAtomForTarget(ui::NETSCAPE_URL)) {
232       std::string netscape_url(reinterpret_cast<const char*>(raw_data),
233                                data_length);
234       size_t split = netscape_url.find_first_of('\n');
235       if (split != std::string::npos) {
236         drop_data_->url = GURL(netscape_url.substr(0, split));
237         if (split < netscape_url.size() - 1) {
238           drop_data_->url_title =
239               base::UTF8ToUTF16(netscape_url.substr(split + 1));
240         }
241       }
242     } else if (target == ui::GetAtomForTarget(ui::CHROME_NAMED_URL)) {
243       ui::ExtractNamedURL(data, &drop_data_->url, &drop_data_->url_title);
244     } else if (target == ui::GetAtomForTarget(ui::CUSTOM_DATA)) {
245       ui::ReadCustomDataIntoMap(
246           raw_data, data_length, &drop_data_->custom_data);
247     }
248   }
249
250   if (data_requests_ == 0) {
251     // Give the delegate an opportunity to cancel the drag.
252     canceled_ = !web_contents_->GetDelegate()->CanDragEnter(
253         web_contents_,
254         *drop_data_,
255         GdkDragActionToWebDragOp(context->actions));
256     if (canceled_) {
257       drag_over_time_ = time;
258       UpdateDragStatus(WebDragOperationNone);
259       drop_data_.reset();
260       return;
261     }
262   }
263
264   // For CHROME_BOOKMARK_ITEM, we have to handle the case where the drag source
265   // doesn't have any data available for us. In this case we try to synthesize a
266   // URL bookmark.
267   // Note that bookmark drag data is encoded in the same format for both
268   // GTK and Views, hence we can share the same logic here.
269   if (delegate() && target == delegate()->GetBookmarkTargetAtom()) {
270     if (raw_data && data_length > 0) {
271       delegate()->OnReceiveDataFromGtk(data);
272     } else {
273       delegate()->OnReceiveProcessedData(drop_data_->url,
274                                          drop_data_->url_title);
275     }
276   }
277
278   if (data_requests_ == 0) {
279     // Tell the renderer about the drag.
280     // |x| and |y| are seemingly arbitrary at this point.
281     GetRenderViewHost()->DragTargetDragEnter(
282         *drop_data_.get(),
283         ui::ClientPoint(widget_),
284         ui::ScreenPoint(widget_),
285         GdkDragActionToWebDragOp(context->actions),
286         GetModifierFlags(widget_));
287
288     if (delegate())
289       delegate()->OnDragEnter();
290
291     drag_over_time_ = time;
292   }
293 }
294
295 // The drag has left our widget; forward this information to the renderer.
296 void WebDragDestGtk::OnDragLeave(GtkWidget* sender, GdkDragContext* context,
297                                  guint time) {
298   // Set |context_| to NULL to make sure we will recognize the next DragMotion
299   // as an enter.
300   context_ = NULL;
301
302   if (canceled_)
303     return;
304
305   // Sometimes we get a drag-leave event before getting a drag-data-received
306   // event. In that case, we don't want to bother the renderer with a
307   // DragLeave event.
308   if (data_requests_ != 0)
309     return;
310
311   // When GTK sends us a drag-drop signal, it is shortly (and synchronously)
312   // preceded by a drag-leave. The renderer doesn't like getting the signals
313   // in this order so delay telling it about the drag-leave till we are sure
314   // we are not getting a drop as well.
315   base::MessageLoop::current()->PostTask(
316       FROM_HERE,
317       base::Bind(&WebDragDestGtk::DragLeave, method_factory_.GetWeakPtr()));
318 }
319
320 // Called by GTK when the user releases the mouse, executing a drop.
321 gboolean WebDragDestGtk::OnDragDrop(GtkWidget* sender, GdkDragContext* context,
322                                     gint x, gint y, guint time) {
323   // Cancel that drag leave!
324   method_factory_.InvalidateWeakPtrs();
325
326   GetRenderViewHost()->
327       DragTargetDrop(ui::ClientPoint(widget_), ui::ScreenPoint(widget_),
328           GetModifierFlags(widget_));
329
330   if (delegate())
331     delegate()->OnDrop();
332
333   // The second parameter is just an educated guess as to whether or not the
334   // drag succeeded, but at least we will get the drag-end animation right
335   // sometimes.
336   gtk_drag_finish(context, is_drop_target_, FALSE, time);
337
338   return TRUE;
339 }
340
341 RenderViewHostImpl* WebDragDestGtk::GetRenderViewHost() const {
342   return static_cast<RenderViewHostImpl*>(web_contents_->GetRenderViewHost());
343 }
344
345 }  // namespace content