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