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_dest_gtk.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"
25 using WebKit::WebDragOperation
;
26 using WebKit::WebDragOperationNone
;
31 const int kNumGtkHandlers
= 5;
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
);
38 if (state
& GDK_SHIFT_MASK
)
39 modifier_state
|= WebKit::WebInputEvent::ShiftKey
;
40 if (state
& GDK_CONTROL_MASK
)
41 modifier_state
|= WebKit::WebInputEvent::ControlKey
;
42 if (state
& GDK_MOD1_MASK
)
43 modifier_state
|= WebKit::WebInputEvent::AltKey
;
44 if (state
& GDK_META_MASK
)
45 modifier_state
|= WebKit::WebInputEvent::MetaKey
;
46 return modifier_state
;
51 WebDragDestGtk::WebDragDestGtk(WebContents
* web_contents
, GtkWidget
* widget
)
52 : web_contents_(web_contents
),
58 method_factory_(this) {
59 gtk_drag_dest_set(widget
, static_cast<GtkDestDefaults
>(0),
61 static_cast<GdkDragAction
>(GDK_ACTION_COPY
|
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
79 handlers_
.get()[4] = g_signal_connect(
80 widget
, "destroy", G_CALLBACK(gtk_widget_destroyed
), &widget_
);
83 WebDragDestGtk::~WebDragDestGtk() {
85 gtk_drag_dest_unset(widget_
);
86 for (int i
= 0; i
< kNumGtkHandlers
; ++i
)
87 g_signal_handler_disconnect(widget_
, handlers_
.get()[i
]);
91 void WebDragDestGtk::UpdateDragStatus(WebDragOperation operation
) {
93 is_drop_target_
= operation
!= WebDragOperationNone
;
94 gdk_drag_status(context_
, WebDragOpToGdkDragAction(operation
),
99 void WebDragDestGtk::DragLeave() {
100 GetRenderViewHost()->DragTargetDragLeave();
102 delegate()->OnDragLeave();
107 gboolean
WebDragDestGtk::OnDragMotion(GtkWidget
* sender
,
108 GdkDragContext
* context
,
111 if (context_
!= context
) {
113 drop_data_
.reset(new DropData
);
114 is_drop_target_
= false;
117 delegate()->DragInitialize(web_contents_
);
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
124 static int supported_targets
[] = {
129 ui::CHROME_NAMED_URL
,
130 // TODO(estade): support image drags?
134 // Add the delegate's requested target if applicable. Need to do this here
135 // since gtk_drag_get_data will dispatch to our drag-data-received.
136 data_requests_
= arraysize(supported_targets
) + (delegate() ? 1 : 0);
137 for (size_t i
= 0; i
< arraysize(supported_targets
); ++i
) {
138 gtk_drag_get_data(widget_
, context
,
139 ui::GetAtomForTarget(supported_targets
[i
]),
144 gtk_drag_get_data(widget_
, context
, delegate()->GetBookmarkTargetAtom(),
147 } else if (data_requests_
== 0) {
151 GetRenderViewHost()->DragTargetDragOver(
152 ui::ClientPoint(widget_
),
153 ui::ScreenPoint(widget_
),
154 GdkDragActionToWebDragOp(context
->actions
),
155 GetModifierFlags(widget_
));
158 delegate()->OnDragOver();
160 drag_over_time_
= time
;
163 // Pretend we are a drag destination because we don't want to wait for
164 // the renderer to tell us if we really are or not.
168 void WebDragDestGtk::OnDragDataReceived(
169 GtkWidget
* sender
, GdkDragContext
* context
, gint x
, gint y
,
170 GtkSelectionData
* data
, guint info
, guint time
) {
171 // We might get the data from an old get_data() request that we no longer
173 if (context
!= context_
)
179 gint data_length
= gtk_selection_data_get_length(data
);
180 const guchar
* raw_data
= gtk_selection_data_get_data(data
);
181 GdkAtom target
= gtk_selection_data_get_target(data
);
182 if (raw_data
&& data_length
> 0) {
183 // If the source can't provide us with valid data for a requested target,
184 // raw_data will be NULL.
185 if (target
== ui::GetAtomForTarget(ui::TEXT_PLAIN
)) {
186 guchar
* text
= gtk_selection_data_get_text(data
);
188 drop_data_
->text
= base::NullableString16(
189 UTF8ToUTF16(std::string(reinterpret_cast<const char*>(text
))),
193 } else if (target
== ui::GetAtomForTarget(ui::TEXT_URI_LIST
)) {
194 gchar
** uris
= gtk_selection_data_get_uris(data
);
196 drop_data_
->url
= GURL();
197 for (gchar
** uri_iter
= uris
; *uri_iter
; uri_iter
++) {
198 // Most file managers populate text/uri-list with file URLs when
199 // dragging files. To avoid exposing file system paths to web content,
200 // file URLs are never set as the URL content for the drop.
201 // TODO(estade): Can the filenames have a non-UTF8 encoding?
203 base::FilePath file_path
;
204 if (url
.SchemeIs(chrome::kFileScheme
) &&
205 net::FileURLToFilePath(url
, &file_path
)) {
206 drop_data_
->filenames
.push_back(
207 DropData::FileInfo(UTF8ToUTF16(file_path
.value()), string16()));
208 // This is a hack. Some file managers also populate text/plain with
209 // a file URL when dragging files, so we clear it to avoid exposing
210 // it to the web content.
211 drop_data_
->text
= base::NullableString16();
212 } else if (!drop_data_
->url
.is_valid()) {
213 // Also set the first non-file URL as the URL content for the drop.
214 drop_data_
->url
= url
;
219 } else if (target
== ui::GetAtomForTarget(ui::TEXT_HTML
)) {
220 // TODO(estade): Can the html have a non-UTF8 encoding?
221 drop_data_
->html
= base::NullableString16(
222 UTF8ToUTF16(std::string(reinterpret_cast<const char*>(raw_data
),
225 // We leave the base URL empty.
226 } else if (target
== ui::GetAtomForTarget(ui::NETSCAPE_URL
)) {
227 std::string
netscape_url(reinterpret_cast<const char*>(raw_data
),
229 size_t split
= netscape_url
.find_first_of('\n');
230 if (split
!= std::string::npos
) {
231 drop_data_
->url
= GURL(netscape_url
.substr(0, split
));
232 if (split
< netscape_url
.size() - 1)
233 drop_data_
->url_title
= UTF8ToUTF16(netscape_url
.substr(split
+ 1));
235 } else if (target
== ui::GetAtomForTarget(ui::CHROME_NAMED_URL
)) {
236 ui::ExtractNamedURL(data
, &drop_data_
->url
, &drop_data_
->url_title
);
237 } else if (target
== ui::GetAtomForTarget(ui::CUSTOM_DATA
)) {
238 ui::ReadCustomDataIntoMap(
239 raw_data
, data_length
, &drop_data_
->custom_data
);
243 if (data_requests_
== 0) {
244 // Give the delegate an opportunity to cancel the drag.
245 canceled_
= !web_contents_
->GetDelegate()->CanDragEnter(
248 GdkDragActionToWebDragOp(context
->actions
));
250 drag_over_time_
= time
;
251 UpdateDragStatus(WebDragOperationNone
);
257 // For CHROME_BOOKMARK_ITEM, we have to handle the case where the drag source
258 // doesn't have any data available for us. In this case we try to synthesize a
260 // Note that bookmark drag data is encoded in the same format for both
261 // GTK and Views, hence we can share the same logic here.
262 if (delegate() && target
== delegate()->GetBookmarkTargetAtom()) {
263 if (raw_data
&& data_length
> 0) {
264 delegate()->OnReceiveDataFromGtk(data
);
266 delegate()->OnReceiveProcessedData(drop_data_
->url
,
267 drop_data_
->url_title
);
271 if (data_requests_
== 0) {
272 // Tell the renderer about the drag.
273 // |x| and |y| are seemingly arbitrary at this point.
274 GetRenderViewHost()->DragTargetDragEnter(
276 ui::ClientPoint(widget_
),
277 ui::ScreenPoint(widget_
),
278 GdkDragActionToWebDragOp(context
->actions
),
279 GetModifierFlags(widget_
));
282 delegate()->OnDragEnter();
284 drag_over_time_
= time
;
288 // The drag has left our widget; forward this information to the renderer.
289 void WebDragDestGtk::OnDragLeave(GtkWidget
* sender
, GdkDragContext
* context
,
291 // Set |context_| to NULL to make sure we will recognize the next DragMotion
298 // Sometimes we get a drag-leave event before getting a drag-data-received
299 // event. In that case, we don't want to bother the renderer with a
301 if (data_requests_
!= 0)
304 // When GTK sends us a drag-drop signal, it is shortly (and synchronously)
305 // preceded by a drag-leave. The renderer doesn't like getting the signals
306 // in this order so delay telling it about the drag-leave till we are sure
307 // we are not getting a drop as well.
308 base::MessageLoop::current()->PostTask(
310 base::Bind(&WebDragDestGtk::DragLeave
, method_factory_
.GetWeakPtr()));
313 // Called by GTK when the user releases the mouse, executing a drop.
314 gboolean
WebDragDestGtk::OnDragDrop(GtkWidget
* sender
, GdkDragContext
* context
,
315 gint x
, gint y
, guint time
) {
316 // Cancel that drag leave!
317 method_factory_
.InvalidateWeakPtrs();
319 GetRenderViewHost()->
320 DragTargetDrop(ui::ClientPoint(widget_
), ui::ScreenPoint(widget_
),
321 GetModifierFlags(widget_
));
324 delegate()->OnDrop();
326 // The second parameter is just an educated guess as to whether or not the
327 // drag succeeded, but at least we will get the drag-end animation right
329 gtk_drag_finish(context
, is_drop_target_
, FALSE
, time
);
334 RenderViewHostImpl
* WebDragDestGtk::GetRenderViewHost() const {
335 return static_cast<RenderViewHostImpl
*>(web_contents_
->GetRenderViewHost());
338 } // namespace content