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