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