Home | History | Annotate | Download | only in dragdrop
      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 "ui/base/dragdrop/os_exchange_data_provider_aurax11.h"
      6 
      7 #include "base/logging.h"
      8 #include "base/memory/ref_counted_memory.h"
      9 #include "base/strings/string_util.h"
     10 #include "base/strings/utf_string_conversions.h"
     11 #include "net/base/filename_util.h"
     12 #include "ui/base/clipboard/clipboard.h"
     13 #include "ui/base/clipboard/scoped_clipboard_writer.h"
     14 #include "ui/base/dragdrop/file_info.h"
     15 #include "ui/base/x/selection_utils.h"
     16 #include "ui/base/x/x11_util.h"
     17 #include "ui/events/platform/platform_event_source.h"
     18 
     19 // Note: the GetBlah() methods are used immediately by the
     20 // web_contents_view_aura.cc:PrepareDropData(), while the omnibox is a
     21 // little more discriminating and calls HasBlah() before trying to get the
     22 // information.
     23 
     24 namespace ui {
     25 
     26 namespace {
     27 
     28 const char kDndSelection[] = "XdndSelection";
     29 const char kRendererTaint[] = "chromium/x-renderer-taint";
     30 
     31 const char kNetscapeURL[] = "_NETSCAPE_URL";
     32 
     33 const char* kAtomsToCache[] = {
     34   kString,
     35   kText,
     36   kUtf8String,
     37   kDndSelection,
     38   Clipboard::kMimeTypeURIList,
     39   kMimeTypeMozillaURL,
     40   kNetscapeURL,
     41   Clipboard::kMimeTypeText,
     42   kRendererTaint,
     43   NULL
     44 };
     45 
     46 }  // namespace
     47 
     48 OSExchangeDataProviderAuraX11::OSExchangeDataProviderAuraX11(
     49     ::Window x_window,
     50     const SelectionFormatMap& selection)
     51     : x_display_(gfx::GetXDisplay()),
     52       x_root_window_(DefaultRootWindow(x_display_)),
     53       own_window_(false),
     54       x_window_(x_window),
     55       atom_cache_(x_display_, kAtomsToCache),
     56       format_map_(selection),
     57       selection_owner_(x_display_, x_window_,
     58                        atom_cache_.GetAtom(kDndSelection)) {
     59   // We don't know all possible MIME types at compile time.
     60   atom_cache_.allow_uncached_atoms();
     61 }
     62 
     63 OSExchangeDataProviderAuraX11::OSExchangeDataProviderAuraX11()
     64     : x_display_(gfx::GetXDisplay()),
     65       x_root_window_(DefaultRootWindow(x_display_)),
     66       own_window_(true),
     67       x_window_(XCreateWindow(
     68           x_display_,
     69           x_root_window_,
     70           -100, -100, 10, 10,  // x, y, width, height
     71           0,                   // border width
     72           CopyFromParent,      // depth
     73           InputOnly,
     74           CopyFromParent,      // visual
     75           0,
     76           NULL)),
     77       atom_cache_(x_display_, kAtomsToCache),
     78       format_map_(),
     79       selection_owner_(x_display_, x_window_,
     80                        atom_cache_.GetAtom(kDndSelection)) {
     81   // We don't know all possible MIME types at compile time.
     82   atom_cache_.allow_uncached_atoms();
     83 
     84   XStoreName(x_display_, x_window_, "Chromium Drag & Drop Window");
     85 
     86   PlatformEventSource::GetInstance()->AddPlatformEventDispatcher(this);
     87 }
     88 
     89 OSExchangeDataProviderAuraX11::~OSExchangeDataProviderAuraX11() {
     90   if (own_window_) {
     91     PlatformEventSource::GetInstance()->RemovePlatformEventDispatcher(this);
     92     XDestroyWindow(x_display_, x_window_);
     93   }
     94 }
     95 
     96 void OSExchangeDataProviderAuraX11::TakeOwnershipOfSelection() const {
     97   selection_owner_.TakeOwnershipOfSelection(format_map_);
     98 }
     99 
    100 void OSExchangeDataProviderAuraX11::RetrieveTargets(
    101     std::vector<Atom>* targets) const {
    102   selection_owner_.RetrieveTargets(targets);
    103 }
    104 
    105 SelectionFormatMap OSExchangeDataProviderAuraX11::GetFormatMap() const {
    106   // We return the |selection_owner_|'s format map instead of our own in case
    107   // ours has been modified since TakeOwnershipOfSelection() was called.
    108   return selection_owner_.selection_format_map();
    109 }
    110 
    111 OSExchangeData::Provider* OSExchangeDataProviderAuraX11::Clone() const {
    112   OSExchangeDataProviderAuraX11* ret = new OSExchangeDataProviderAuraX11();
    113   ret->format_map_ = format_map_;
    114   return ret;
    115 }
    116 
    117 void OSExchangeDataProviderAuraX11::MarkOriginatedFromRenderer() {
    118   std::string empty;
    119   format_map_.Insert(atom_cache_.GetAtom(kRendererTaint),
    120                      scoped_refptr<base::RefCountedMemory>(
    121                          base::RefCountedString::TakeString(&empty)));
    122 }
    123 
    124 bool OSExchangeDataProviderAuraX11::DidOriginateFromRenderer() const {
    125   return format_map_.find(atom_cache_.GetAtom(kRendererTaint)) !=
    126          format_map_.end();
    127 }
    128 
    129 void OSExchangeDataProviderAuraX11::SetString(const base::string16& text_data) {
    130   std::string utf8 = base::UTF16ToUTF8(text_data);
    131   scoped_refptr<base::RefCountedMemory> mem(
    132       base::RefCountedString::TakeString(&utf8));
    133 
    134   format_map_.Insert(atom_cache_.GetAtom(Clipboard::kMimeTypeText), mem);
    135   format_map_.Insert(atom_cache_.GetAtom(kText), mem);
    136   format_map_.Insert(atom_cache_.GetAtom(kString), mem);
    137   format_map_.Insert(atom_cache_.GetAtom(kUtf8String), mem);
    138 }
    139 
    140 void OSExchangeDataProviderAuraX11::SetURL(const GURL& url,
    141                                            const base::string16& title) {
    142   // TODO(dcheng): The original GTK code tries very hard to avoid writing out an
    143   // empty title. Is this necessary?
    144   if (url.is_valid()) {
    145     // Mozilla's URL format: (UTF16: URL, newline, title)
    146     base::string16 spec = base::UTF8ToUTF16(url.spec());
    147 
    148     std::vector<unsigned char> data;
    149     ui::AddString16ToVector(spec, &data);
    150     ui::AddString16ToVector(base::ASCIIToUTF16("\n"), &data);
    151     ui::AddString16ToVector(title, &data);
    152     scoped_refptr<base::RefCountedMemory> mem(
    153         base::RefCountedBytes::TakeVector(&data));
    154 
    155     format_map_.Insert(atom_cache_.GetAtom(kMimeTypeMozillaURL), mem);
    156 
    157     // Set a string fallback as well.
    158     SetString(spec);
    159 
    160     // Return early if this drag already contains file contents (this implies
    161     // that file contents must be populated before URLs). Nautilus (and possibly
    162     // other file managers) prefer _NETSCAPE_URL over the X Direct Save
    163     // protocol, but we want to prioritize XDS in this case.
    164     if (!file_contents_name_.empty())
    165       return;
    166 
    167     // Set _NETSCAPE_URL for file managers like Nautilus that use it as a hint
    168     // to create a link to the URL. Setting text/uri-list doesn't work because
    169     // Nautilus will fetch and copy the contents of the URL to the drop target
    170     // instead of linking...
    171     // Format is UTF8: URL + "\n" + title.
    172     std::string netscape_url = url.spec();
    173     netscape_url += "\n";
    174     netscape_url += base::UTF16ToUTF8(title);
    175     format_map_.Insert(atom_cache_.GetAtom(kNetscapeURL),
    176                        scoped_refptr<base::RefCountedMemory>(
    177                            base::RefCountedString::TakeString(&netscape_url)));
    178   }
    179 }
    180 
    181 void OSExchangeDataProviderAuraX11::SetFilename(const base::FilePath& path) {
    182   std::vector<FileInfo> data;
    183   data.push_back(FileInfo(path, base::FilePath()));
    184   SetFilenames(data);
    185 }
    186 
    187 void OSExchangeDataProviderAuraX11::SetFilenames(
    188     const std::vector<FileInfo>& filenames) {
    189   std::vector<std::string> paths;
    190   for (std::vector<FileInfo>::const_iterator it = filenames.begin();
    191        it != filenames.end();
    192        ++it) {
    193     std::string url_spec = net::FilePathToFileURL(it->path).spec();
    194     if (!url_spec.empty())
    195       paths.push_back(url_spec);
    196   }
    197 
    198   std::string joined_data = JoinString(paths, '\n');
    199   scoped_refptr<base::RefCountedMemory> mem(
    200       base::RefCountedString::TakeString(&joined_data));
    201   format_map_.Insert(atom_cache_.GetAtom(Clipboard::kMimeTypeURIList), mem);
    202 }
    203 
    204 void OSExchangeDataProviderAuraX11::SetPickledData(
    205     const OSExchangeData::CustomFormat& format,
    206     const Pickle& pickle) {
    207   const unsigned char* data =
    208       reinterpret_cast<const unsigned char*>(pickle.data());
    209 
    210   std::vector<unsigned char> bytes;
    211   bytes.insert(bytes.end(), data, data + pickle.size());
    212   scoped_refptr<base::RefCountedMemory> mem(
    213       base::RefCountedBytes::TakeVector(&bytes));
    214 
    215   format_map_.Insert(atom_cache_.GetAtom(format.ToString().c_str()), mem);
    216 }
    217 
    218 bool OSExchangeDataProviderAuraX11::GetString(base::string16* result) const {
    219   if (HasFile()) {
    220     // Various Linux file managers both pass a list of file:// URIs and set the
    221     // string representation to the URI. We explicitly don't want to return use
    222     // this representation.
    223     return false;
    224   }
    225 
    226   std::vector< ::Atom> text_atoms = ui::GetTextAtomsFrom(&atom_cache_);
    227   std::vector< ::Atom> requested_types;
    228   ui::GetAtomIntersection(text_atoms, GetTargets(), &requested_types);
    229 
    230   ui::SelectionData data(format_map_.GetFirstOf(requested_types));
    231   if (data.IsValid()) {
    232     std::string text = data.GetText();
    233     *result = base::UTF8ToUTF16(text);
    234     return true;
    235   }
    236 
    237   return false;
    238 }
    239 
    240 bool OSExchangeDataProviderAuraX11::GetURLAndTitle(
    241     OSExchangeData::FilenameToURLPolicy policy,
    242     GURL* url,
    243     base::string16* title) const {
    244   std::vector< ::Atom> url_atoms = ui::GetURLAtomsFrom(&atom_cache_);
    245   std::vector< ::Atom> requested_types;
    246   ui::GetAtomIntersection(url_atoms, GetTargets(), &requested_types);
    247 
    248   ui::SelectionData data(format_map_.GetFirstOf(requested_types));
    249   if (data.IsValid()) {
    250     // TODO(erg): Technically, both of these forms can accept multiple URLs,
    251     // but that doesn't match the assumptions of the rest of the system which
    252     // expect single types.
    253 
    254     if (data.GetType() == atom_cache_.GetAtom(kMimeTypeMozillaURL)) {
    255       // Mozilla URLs are (UTF16: URL, newline, title).
    256       base::string16 unparsed;
    257       data.AssignTo(&unparsed);
    258 
    259       std::vector<base::string16> tokens;
    260       size_t num_tokens = Tokenize(unparsed, base::ASCIIToUTF16("\n"), &tokens);
    261       if (num_tokens > 0) {
    262         if (num_tokens > 1)
    263           *title = tokens[1];
    264         else
    265           *title = base::string16();
    266 
    267         *url = GURL(tokens[0]);
    268         return true;
    269       }
    270     } else if (data.GetType() == atom_cache_.GetAtom(
    271                    Clipboard::kMimeTypeURIList)) {
    272       std::vector<std::string> tokens = ui::ParseURIList(data);
    273       for (std::vector<std::string>::const_iterator it = tokens.begin();
    274            it != tokens.end(); ++it) {
    275         GURL test_url(*it);
    276         if (!test_url.SchemeIsFile() ||
    277             policy == OSExchangeData::CONVERT_FILENAMES) {
    278           *url = test_url;
    279           *title = base::string16();
    280           return true;
    281         }
    282       }
    283     }
    284   }
    285 
    286   return false;
    287 }
    288 
    289 bool OSExchangeDataProviderAuraX11::GetFilename(base::FilePath* path) const {
    290   std::vector<FileInfo> filenames;
    291   if (GetFilenames(&filenames)) {
    292     *path = filenames.front().path;
    293     return true;
    294   }
    295 
    296   return false;
    297 }
    298 
    299 bool OSExchangeDataProviderAuraX11::GetFilenames(
    300     std::vector<FileInfo>* filenames) const {
    301   std::vector< ::Atom> url_atoms = ui::GetURIListAtomsFrom(&atom_cache_);
    302   std::vector< ::Atom> requested_types;
    303   ui::GetAtomIntersection(url_atoms, GetTargets(), &requested_types);
    304 
    305   filenames->clear();
    306   ui::SelectionData data(format_map_.GetFirstOf(requested_types));
    307   if (data.IsValid()) {
    308     std::vector<std::string> tokens = ui::ParseURIList(data);
    309     for (std::vector<std::string>::const_iterator it = tokens.begin();
    310          it != tokens.end(); ++it) {
    311       GURL url(*it);
    312       base::FilePath file_path;
    313       if (url.SchemeIsFile() && net::FileURLToFilePath(url, &file_path)) {
    314         filenames->push_back(FileInfo(file_path, base::FilePath()));
    315       }
    316     }
    317   }
    318 
    319   return !filenames->empty();
    320 }
    321 
    322 bool OSExchangeDataProviderAuraX11::GetPickledData(
    323     const OSExchangeData::CustomFormat& format,
    324     Pickle* pickle) const {
    325   std::vector< ::Atom> requested_types;
    326   requested_types.push_back(atom_cache_.GetAtom(format.ToString().c_str()));
    327 
    328   ui::SelectionData data(format_map_.GetFirstOf(requested_types));
    329   if (data.IsValid()) {
    330     // Note that the pickle object on the right hand side of the assignment
    331     // only refers to the bytes in |data|. The assignment copies the data.
    332     *pickle = Pickle(reinterpret_cast<const char*>(data.GetData()),
    333                      static_cast<int>(data.GetSize()));
    334     return true;
    335   }
    336 
    337   return false;
    338 }
    339 
    340 bool OSExchangeDataProviderAuraX11::HasString() const {
    341   std::vector< ::Atom> text_atoms = ui::GetTextAtomsFrom(&atom_cache_);
    342   std::vector< ::Atom> requested_types;
    343   ui::GetAtomIntersection(text_atoms, GetTargets(), &requested_types);
    344   return !requested_types.empty() && !HasFile();
    345 }
    346 
    347 bool OSExchangeDataProviderAuraX11::HasURL(
    348     OSExchangeData::FilenameToURLPolicy policy) const {
    349   std::vector< ::Atom> url_atoms = ui::GetURLAtomsFrom(&atom_cache_);
    350   std::vector< ::Atom> requested_types;
    351   ui::GetAtomIntersection(url_atoms, GetTargets(), &requested_types);
    352 
    353   if (requested_types.empty())
    354     return false;
    355 
    356   // The Linux desktop doesn't differentiate between files and URLs like
    357   // Windows does and stuffs all the data into one mime type.
    358   ui::SelectionData data(format_map_.GetFirstOf(requested_types));
    359   if (data.IsValid()) {
    360     if (data.GetType() == atom_cache_.GetAtom(kMimeTypeMozillaURL)) {
    361       // File managers shouldn't be using this type, so this is a URL.
    362       return true;
    363     } else if (data.GetType() == atom_cache_.GetAtom(
    364         ui::Clipboard::kMimeTypeURIList)) {
    365       std::vector<std::string> tokens = ui::ParseURIList(data);
    366       for (std::vector<std::string>::const_iterator it = tokens.begin();
    367            it != tokens.end(); ++it) {
    368         if (!GURL(*it).SchemeIsFile() ||
    369             policy == OSExchangeData::CONVERT_FILENAMES)
    370           return true;
    371       }
    372 
    373       return false;
    374     }
    375   }
    376 
    377   return false;
    378 }
    379 
    380 bool OSExchangeDataProviderAuraX11::HasFile() const {
    381   std::vector< ::Atom> url_atoms = ui::GetURIListAtomsFrom(&atom_cache_);
    382   std::vector< ::Atom> requested_types;
    383   ui::GetAtomIntersection(url_atoms, GetTargets(), &requested_types);
    384 
    385   if (requested_types.empty())
    386     return false;
    387 
    388   // To actually answer whether we have a file, we need to look through the
    389   // contents of the kMimeTypeURIList type, and see if any of them are file://
    390   // URIs.
    391   ui::SelectionData data(format_map_.GetFirstOf(requested_types));
    392   if (data.IsValid()) {
    393     std::vector<std::string> tokens = ui::ParseURIList(data);
    394     for (std::vector<std::string>::const_iterator it = tokens.begin();
    395          it != tokens.end(); ++it) {
    396       GURL url(*it);
    397       base::FilePath file_path;
    398       if (url.SchemeIsFile() && net::FileURLToFilePath(url, &file_path))
    399         return true;
    400     }
    401   }
    402 
    403   return false;
    404 }
    405 
    406 bool OSExchangeDataProviderAuraX11::HasCustomFormat(
    407     const OSExchangeData::CustomFormat& format) const {
    408   std::vector< ::Atom> url_atoms;
    409   url_atoms.push_back(atom_cache_.GetAtom(format.ToString().c_str()));
    410   std::vector< ::Atom> requested_types;
    411   ui::GetAtomIntersection(url_atoms, GetTargets(), &requested_types);
    412 
    413   return !requested_types.empty();
    414 }
    415 
    416 void OSExchangeDataProviderAuraX11::SetFileContents(
    417     const base::FilePath& filename,
    418     const std::string& file_contents) {
    419   DCHECK(!filename.empty());
    420   DCHECK(format_map_.end() ==
    421          format_map_.find(atom_cache_.GetAtom(kMimeTypeMozillaURL)));
    422 
    423   file_contents_name_ = filename;
    424 
    425   // Direct save handling is a complicated juggling affair between this class,
    426   // SelectionFormat, and DesktopDragDropClientAuraX11. The general idea behind
    427   // the protocol is this:
    428   // - The source window sets its XdndDirectSave0 window property to the
    429   //   proposed filename.
    430   // - When a target window receives the drop, it updates the XdndDirectSave0
    431   //   property on the source window to the filename it would like the contents
    432   //   to be saved to and then requests the XdndDirectSave0 type from the
    433   //   source.
    434   // - The source is supposed to copy the file here and return success (S),
    435   //   failure (F), or error (E).
    436   // - In this case, failure means the destination should try to populate the
    437   //   file itself by copying the data from application/octet-stream. To make
    438   //   things simpler for Chrome, we always 'fail' and let the destination do
    439   //   the work.
    440   std::string failure("F");
    441   format_map_.Insert(
    442       atom_cache_.GetAtom("XdndDirectSave0"),
    443                           scoped_refptr<base::RefCountedMemory>(
    444                               base::RefCountedString::TakeString(&failure)));
    445   std::string file_contents_copy = file_contents;
    446   format_map_.Insert(
    447       atom_cache_.GetAtom("application/octet-stream"),
    448       scoped_refptr<base::RefCountedMemory>(
    449           base::RefCountedString::TakeString(&file_contents_copy)));
    450 }
    451 
    452 void OSExchangeDataProviderAuraX11::SetHtml(const base::string16& html,
    453                                             const GURL& base_url) {
    454   std::vector<unsigned char> bytes;
    455   // Manually jam a UTF16 BOM into bytes because otherwise, other programs will
    456   // assume UTF-8.
    457   bytes.push_back(0xFF);
    458   bytes.push_back(0xFE);
    459   ui::AddString16ToVector(html, &bytes);
    460   scoped_refptr<base::RefCountedMemory> mem(
    461       base::RefCountedBytes::TakeVector(&bytes));
    462 
    463   format_map_.Insert(atom_cache_.GetAtom(Clipboard::kMimeTypeHTML), mem);
    464 }
    465 
    466 bool OSExchangeDataProviderAuraX11::GetHtml(base::string16* html,
    467                                             GURL* base_url) const {
    468   std::vector< ::Atom> url_atoms;
    469   url_atoms.push_back(atom_cache_.GetAtom(Clipboard::kMimeTypeHTML));
    470   std::vector< ::Atom> requested_types;
    471   ui::GetAtomIntersection(url_atoms, GetTargets(), &requested_types);
    472 
    473   ui::SelectionData data(format_map_.GetFirstOf(requested_types));
    474   if (data.IsValid()) {
    475     *html = data.GetHtml();
    476     *base_url = GURL();
    477     return true;
    478   }
    479 
    480   return false;
    481 }
    482 
    483 bool OSExchangeDataProviderAuraX11::HasHtml() const {
    484   std::vector< ::Atom> url_atoms;
    485   url_atoms.push_back(atom_cache_.GetAtom(Clipboard::kMimeTypeHTML));
    486   std::vector< ::Atom> requested_types;
    487   ui::GetAtomIntersection(url_atoms, GetTargets(), &requested_types);
    488 
    489   return !requested_types.empty();
    490 }
    491 
    492 void OSExchangeDataProviderAuraX11::SetDragImage(
    493     const gfx::ImageSkia& image,
    494     const gfx::Vector2d& cursor_offset) {
    495   drag_image_ = image;
    496   drag_image_offset_ = cursor_offset;
    497 }
    498 
    499 const gfx::ImageSkia& OSExchangeDataProviderAuraX11::GetDragImage() const {
    500   return drag_image_;
    501 }
    502 
    503 const gfx::Vector2d& OSExchangeDataProviderAuraX11::GetDragImageOffset() const {
    504   return drag_image_offset_;
    505 }
    506 
    507 bool OSExchangeDataProviderAuraX11::CanDispatchEvent(
    508     const PlatformEvent& event) {
    509   return event->xany.window == x_window_;
    510 }
    511 
    512 uint32_t OSExchangeDataProviderAuraX11::DispatchEvent(
    513     const PlatformEvent& event) {
    514   XEvent* xev = event;
    515   switch (xev->type) {
    516     case SelectionRequest:
    517       selection_owner_.OnSelectionRequest(xev->xselectionrequest);
    518       return ui::POST_DISPATCH_STOP_PROPAGATION;
    519     default:
    520       NOTIMPLEMENTED();
    521   }
    522   return ui::POST_DISPATCH_NONE;
    523 }
    524 
    525 bool OSExchangeDataProviderAuraX11::GetPlainTextURL(GURL* url) const {
    526   base::string16 text;
    527   if (GetString(&text)) {
    528     GURL test_url(text);
    529     if (test_url.is_valid()) {
    530       *url = test_url;
    531       return true;
    532     }
    533   }
    534 
    535   return false;
    536 }
    537 
    538 std::vector< ::Atom> OSExchangeDataProviderAuraX11::GetTargets() const {
    539   return format_map_.GetTypes();
    540 }
    541 
    542 ///////////////////////////////////////////////////////////////////////////////
    543 // OSExchangeData, public:
    544 
    545 // static
    546 OSExchangeData::Provider* OSExchangeData::CreateProvider() {
    547   return new OSExchangeDataProviderAuraX11();
    548 }
    549 
    550 }  // namespace ui
    551