OLD | NEW |
| (Empty) |
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/filename_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 | |
OLD | NEW |