Respond with QuotaExceededError when IndexedDB has no disk space on open.
[chromium-blink-merge.git] / content / browser / web_contents / web_drag_dest_gtk.cc
blob55f0d9c04a290cc1fb28164b17af8bf3a52a6d64
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/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;
28 namespace content {
30 namespace {
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;
49 } // namespace
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));
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() {
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]);
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_);
99 void WebDragDestGtk::DragLeave() {
100 GetRenderViewHost()->DragTargetDragLeave();
101 if (delegate())
102 delegate()->OnDragLeave();
104 drop_data_.reset();
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;
116 if (delegate())
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
123 // it's a file drag.
124 static int supported_targets[] = {
125 ui::TEXT_PLAIN,
126 ui::TEXT_URI_LIST,
127 ui::TEXT_HTML,
128 ui::NETSCAPE_URL,
129 ui::CHROME_NAMED_URL,
130 // TODO(estade): support image drags?
131 ui::CUSTOM_DATA,
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]),
140 time);
143 if (delegate()) {
144 gtk_drag_get_data(widget_, context, delegate()->GetBookmarkTargetAtom(),
145 time);
147 } else if (data_requests_ == 0) {
148 if (canceled_)
149 return FALSE;
151 GetRenderViewHost()->DragTargetDragOver(
152 ui::ClientPoint(widget_),
153 ui::ScreenPoint(widget_),
154 GdkDragActionToWebDragOp(context->actions),
155 GetModifierFlags(widget_));
157 if (delegate())
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.
165 return TRUE;
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
172 // care about.
173 if (context != context_)
174 return;
176 data_requests_--;
178 // Decode the data.
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);
187 if (text) {
188 drop_data_->text = base::NullableString16(
189 UTF8ToUTF16(std::string(reinterpret_cast<const char*>(text))),
190 false);
191 g_free(text);
193 } else if (target == ui::GetAtomForTarget(ui::TEXT_URI_LIST)) {
194 gchar** uris = gtk_selection_data_get_uris(data);
195 if (uris) {
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?
202 GURL url(*uri_iter);
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;
217 g_strfreev(uris);
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),
223 data_length)),
224 false);
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),
228 data_length);
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(
246 web_contents_,
247 *drop_data_,
248 GdkDragActionToWebDragOp(context->actions));
249 if (canceled_) {
250 drag_over_time_ = time;
251 UpdateDragStatus(WebDragOperationNone);
252 drop_data_.reset();
253 return;
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
259 // URL bookmark.
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);
265 } else {
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(
275 *drop_data_.get(),
276 ui::ClientPoint(widget_),
277 ui::ScreenPoint(widget_),
278 GdkDragActionToWebDragOp(context->actions),
279 GetModifierFlags(widget_));
281 if (delegate())
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,
290 guint time) {
291 // Set |context_| to NULL to make sure we will recognize the next DragMotion
292 // as an enter.
293 context_ = NULL;
295 if (canceled_)
296 return;
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
300 // DragLeave event.
301 if (data_requests_ != 0)
302 return;
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(
309 FROM_HERE,
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_));
323 if (delegate())
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
328 // sometimes.
329 gtk_drag_finish(context, is_drop_target_, FALSE, time);
331 return TRUE;
334 RenderViewHostImpl* WebDragDestGtk::GetRenderViewHost() const {
335 return static_cast<RenderViewHostImpl*>(web_contents_->GetRenderViewHost());
338 } // namespace content