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   if (HasString())
    131     return;
    132 
    133   std::string utf8 = base::UTF16ToUTF8(text_data);
    134   scoped_refptr<base::RefCountedMemory> mem(
    135       base::RefCountedString::TakeString(&utf8));
    136 
    137   format_map_.Insert(atom_cache_.GetAtom(Clipboard::kMimeTypeText), mem);
    138   format_map_.Insert(atom_cache_.GetAtom(kText), mem);
    139   format_map_.Insert(atom_cache_.GetAtom(kString), mem);
    140   format_map_.Insert(atom_cache_.GetAtom(kUtf8String), mem);
    141 }
    142 
    143 void OSExchangeDataProviderAuraX11::SetURL(const GURL& url,
    144                                            const base::string16& title) {
    145   // TODO(dcheng): The original GTK code tries very hard to avoid writing out an
    146   // empty title. Is this necessary?
    147   if (url.is_valid()) {
    148     // Mozilla's URL format: (UTF16: URL, newline, title)
    149     base::string16 spec = base::UTF8ToUTF16(url.spec());
    150 
    151     std::vector<unsigned char> data;
    152     ui::AddString16ToVector(spec, &data);
    153     ui::AddString16ToVector(base::ASCIIToUTF16("\n"), &data);
    154     ui::AddString16ToVector(title, &data);
    155     scoped_refptr<base::RefCountedMemory> mem(
    156         base::RefCountedBytes::TakeVector(&data));
    157 
    158     format_map_.Insert(atom_cache_.GetAtom(kMimeTypeMozillaURL), mem);
    159 
    160     // Set a string fallback as well.
    161     SetString(spec);
    162 
    163     // Return early if this drag already contains file contents (this implies
    164     // that file contents must be populated before URLs). Nautilus (and possibly
    165     // other file managers) prefer _NETSCAPE_URL over the X Direct Save
    166     // protocol, but we want to prioritize XDS in this case.
    167     if (!file_contents_name_.empty())
    168       return;
    169 
    170     // Set _NETSCAPE_URL for file managers like Nautilus that use it as a hint
    171     // to create a link to the URL. Setting text/uri-list doesn't work because
    172     // Nautilus will fetch and copy the contents of the URL to the drop target
    173     // instead of linking...
    174     // Format is UTF8: URL + "\n" + title.
    175     std::string netscape_url = url.spec();
    176     netscape_url += "\n";
    177     netscape_url += base::UTF16ToUTF8(title);
    178     format_map_.Insert(atom_cache_.GetAtom(kNetscapeURL),
    179                        scoped_refptr<base::RefCountedMemory>(
    180                            base::RefCountedString::TakeString(&netscape_url)));
    181   }
    182 }
    183 
    184 void OSExchangeDataProviderAuraX11::SetFilename(const base::FilePath& path) {
    185   std::vector<FileInfo> data;
    186   data.push_back(FileInfo(path, base::FilePath()));
    187   SetFilenames(data);
    188 }
    189 
    190 void OSExchangeDataProviderAuraX11::SetFilenames(
    191     const std::vector<FileInfo>& filenames) {
    192   std::vector<std::string> paths;
    193   for (std::vector<FileInfo>::const_iterator it = filenames.begin();
    194        it != filenames.end();
    195        ++it) {
    196     std::string url_spec = net::FilePathToFileURL(it->path).spec();
    197     if (!url_spec.empty())
    198       paths.push_back(url_spec);
    199   }
    200 
    201   std::string joined_data = JoinString(paths, '\n');
    202   scoped_refptr<base::RefCountedMemory> mem(
    203       base::RefCountedString::TakeString(&joined_data));
    204   format_map_.Insert(atom_cache_.GetAtom(Clipboard::kMimeTypeURIList), mem);
    205 }
    206 
    207 void OSExchangeDataProviderAuraX11::SetPickledData(
    208     const OSExchangeData::CustomFormat& format,
    209     const Pickle& pickle) {
    210   const unsigned char* data =
    211       reinterpret_cast<const unsigned char*>(pickle.data());
    212 
    213   std::vector<unsigned char> bytes;
    214   bytes.insert(bytes.end(), data, data + pickle.size());
    215   scoped_refptr<base::RefCountedMemory> mem(
    216       base::RefCountedBytes::TakeVector(&bytes));
    217 
    218   format_map_.Insert(atom_cache_.GetAtom(format.ToString().c_str()), mem);
    219 }
    220 
    221 bool OSExchangeDataProviderAuraX11::GetString(base::string16* result) const {
    222   if (HasFile()) {
    223     // Various Linux file managers both pass a list of file:// URIs and set the
    224     // string representation to the URI. We explicitly don't want to return use
    225     // this representation.
    226     return false;
    227   }
    228 
    229   std::vector< ::Atom> text_atoms = ui::GetTextAtomsFrom(&atom_cache_);
    230   std::vector< ::Atom> requested_types;
    231   ui::GetAtomIntersection(text_atoms, GetTargets(), &requested_types);
    232 
    233   ui::SelectionData data(format_map_.GetFirstOf(requested_types));
    234   if (data.IsValid()) {
    235     std::string text = data.GetText();
    236     *result = base::UTF8ToUTF16(text);
    237     return true;
    238   }
    239 
    240   return false;
    241 }
    242 
    243 bool OSExchangeDataProviderAuraX11::GetURLAndTitle(
    244     OSExchangeData::FilenameToURLPolicy policy,
    245     GURL* url,
    246     base::string16* title) const {
    247   std::vector< ::Atom> url_atoms = ui::GetURLAtomsFrom(&atom_cache_);
    248   std::vector< ::Atom> requested_types;
    249   ui::GetAtomIntersection(url_atoms, GetTargets(), &requested_types);
    250 
    251   ui::SelectionData data(format_map_.GetFirstOf(requested_types));
    252   if (data.IsValid()) {
    253     // TODO(erg): Technically, both of these forms can accept multiple URLs,
    254     // but that doesn't match the assumptions of the rest of the system which
    255     // expect single types.
    256 
    257     if (data.GetType() == atom_cache_.GetAtom(kMimeTypeMozillaURL)) {
    258       // Mozilla URLs are (UTF16: URL, newline, title).
    259       base::string16 unparsed;
    260       data.AssignTo(&unparsed);
    261 
    262       std::vector<base::string16> tokens;
    263       size_t num_tokens = Tokenize(unparsed, base::ASCIIToUTF16("\n"), &tokens);
    264       if (num_tokens > 0) {
    265         if (num_tokens > 1)
    266           *title = tokens[1];
    267         else
    268           *title = base::string16();
    269 
    270         *url = GURL(tokens[0]);
    271         return true;
    272       }
    273     } else if (data.GetType() == atom_cache_.GetAtom(
    274                    Clipboard::kMimeTypeURIList)) {
    275       std::vector<std::string> tokens = ui::ParseURIList(data);
    276       for (std::vector<std::string>::const_iterator it = tokens.begin();
    277            it != tokens.end(); ++it) {
    278         GURL test_url(*it);
    279         if (!test_url.SchemeIsFile() ||
    280             policy == OSExchangeData::CONVERT_FILENAMES) {
    281           *url = test_url;
    282           *title = base::string16();
    283           return true;
    284         }
    285       }
    286     }
    287   }
    288 
    289   return false;
    290 }
    291 
    292 bool OSExchangeDataProviderAuraX11::GetFilename(base::FilePath* path) const {
    293   std::vector<FileInfo> filenames;
    294   if (GetFilenames(&filenames)) {
    295     *path = filenames.front().path;
    296     return true;
    297   }
    298 
    299   return false;
    300 }
    301 
    302 bool OSExchangeDataProviderAuraX11::GetFilenames(
    303     std::vector<FileInfo>* filenames) const {
    304   std::vector< ::Atom> url_atoms = ui::GetURIListAtomsFrom(&atom_cache_);
    305   std::vector< ::Atom> requested_types;
    306   ui::GetAtomIntersection(url_atoms, GetTargets(), &requested_types);
    307 
    308   filenames->clear();
    309   ui::SelectionData data(format_map_.GetFirstOf(requested_types));
    310   if (data.IsValid()) {
    311     std::vector<std::string> tokens = ui::ParseURIList(data);
    312     for (std::vector<std::string>::const_iterator it = tokens.begin();
    313          it != tokens.end(); ++it) {
    314       GURL url(*it);
    315       base::FilePath file_path;
    316       if (url.SchemeIsFile() && net::FileURLToFilePath(url, &file_path)) {
    317         filenames->push_back(FileInfo(file_path, base::FilePath()));
    318       }
    319     }
    320   }
    321 
    322   return !filenames->empty();
    323 }
    324 
    325 bool OSExchangeDataProviderAuraX11::GetPickledData(
    326     const OSExchangeData::CustomFormat& format,
    327     Pickle* pickle) const {
    328   std::vector< ::Atom> requested_types;
    329   requested_types.push_back(atom_cache_.GetAtom(format.ToString().c_str()));
    330 
    331   ui::SelectionData data(format_map_.GetFirstOf(requested_types));
    332   if (data.IsValid()) {
    333     // Note that the pickle object on the right hand side of the assignment
    334     // only refers to the bytes in |data|. The assignment copies the data.
    335     *pickle = Pickle(reinterpret_cast<const char*>(data.GetData()),
    336                      static_cast<int>(data.GetSize()));
    337     return true;
    338   }
    339 
    340   return false;
    341 }
    342 
    343 bool OSExchangeDataProviderAuraX11::HasString() const {
    344   std::vector< ::Atom> text_atoms = ui::GetTextAtomsFrom(&atom_cache_);
    345   std::vector< ::Atom> requested_types;
    346   ui::GetAtomIntersection(text_atoms, GetTargets(), &requested_types);
    347   return !requested_types.empty() && !HasFile();
    348 }
    349 
    350 bool OSExchangeDataProviderAuraX11::HasURL(
    351     OSExchangeData::FilenameToURLPolicy policy) const {
    352   std::vector< ::Atom> url_atoms = ui::GetURLAtomsFrom(&atom_cache_);
    353   std::vector< ::Atom> requested_types;
    354   ui::GetAtomIntersection(url_atoms, GetTargets(), &requested_types);
    355 
    356   if (requested_types.empty())
    357     return false;
    358 
    359   // The Linux desktop doesn't differentiate between files and URLs like
    360   // Windows does and stuffs all the data into one mime type.
    361   ui::SelectionData data(format_map_.GetFirstOf(requested_types));
    362   if (data.IsValid()) {
    363     if (data.GetType() == atom_cache_.GetAtom(kMimeTypeMozillaURL)) {
    364       // File managers shouldn't be using this type, so this is a URL.
    365       return true;
    366     } else if (data.GetType() == atom_cache_.GetAtom(
    367         ui::Clipboard::kMimeTypeURIList)) {
    368       std::vector<std::string> tokens = ui::ParseURIList(data);
    369       for (std::vector<std::string>::const_iterator it = tokens.begin();
    370            it != tokens.end(); ++it) {
    371         if (!GURL(*it).SchemeIsFile() ||
    372             policy == OSExchangeData::CONVERT_FILENAMES)
    373           return true;
    374       }
    375 
    376       return false;
    377     }
    378   }
    379 
    380   return false;
    381 }
    382 
    383 bool OSExchangeDataProviderAuraX11::HasFile() const {
    384   std::vector< ::Atom> url_atoms = ui::GetURIListAtomsFrom(&atom_cache_);
    385   std::vector< ::Atom> requested_types;
    386   ui::GetAtomIntersection(url_atoms, GetTargets(), &requested_types);
    387 
    388   if (requested_types.empty())
    389     return false;
    390 
    391   // To actually answer whether we have a file, we need to look through the
    392   // contents of the kMimeTypeURIList type, and see if any of them are file://
    393   // URIs.
    394   ui::SelectionData data(format_map_.GetFirstOf(requested_types));
    395   if (data.IsValid()) {
    396     std::vector<std::string> tokens = ui::ParseURIList(data);
    397     for (std::vector<std::string>::const_iterator it = tokens.begin();
    398          it != tokens.end(); ++it) {
    399       GURL url(*it);
    400       base::FilePath file_path;
    401       if (url.SchemeIsFile() && net::FileURLToFilePath(url, &file_path))
    402         return true;
    403     }
    404   }
    405 
    406   return false;
    407 }
    408 
    409 bool OSExchangeDataProviderAuraX11::HasCustomFormat(
    410     const OSExchangeData::CustomFormat& format) const {
    411   std::vector< ::Atom> url_atoms;
    412   url_atoms.push_back(atom_cache_.GetAtom(format.ToString().c_str()));
    413   std::vector< ::Atom> requested_types;
    414   ui::GetAtomIntersection(url_atoms, GetTargets(), &requested_types);
    415 
    416   return !requested_types.empty();
    417 }
    418 
    419 void OSExchangeDataProviderAuraX11::SetFileContents(
    420     const base::FilePath& filename,
    421     const std::string& file_contents) {
    422   DCHECK(!filename.empty());
    423   DCHECK(format_map_.end() ==
    424          format_map_.find(atom_cache_.GetAtom(kMimeTypeMozillaURL)));
    425 
    426   file_contents_name_ = filename;
    427 
    428   // Direct save handling is a complicated juggling affair between this class,
    429   // SelectionFormat, and DesktopDragDropClientAuraX11. The general idea behind
    430   // the protocol is this:
    431   // - The source window sets its XdndDirectSave0 window property to the
    432   //   proposed filename.
    433   // - When a target window receives the drop, it updates the XdndDirectSave0
    434   //   property on the source window to the filename it would like the contents
    435   //   to be saved to and then requests the XdndDirectSave0 type from the
    436   //   source.
    437   // - The source is supposed to copy the file here and return success (S),
    438   //   failure (F), or error (E).
    439   // - In this case, failure means the destination should try to populate the
    440   //   file itself by copying the data from application/octet-stream. To make
    441   //   things simpler for Chrome, we always 'fail' and let the destination do
    442   //   the work.
    443   std::string failure("F");
    444   format_map_.Insert(
    445       atom_cache_.GetAtom("XdndDirectSave0"),
    446                           scoped_refptr<base::RefCountedMemory>(
    447                               base::RefCountedString::TakeString(&failure)));
    448   std::string file_contents_copy = file_contents;
    449   format_map_.Insert(
    450       atom_cache_.GetAtom("application/octet-stream"),
    451       scoped_refptr<base::RefCountedMemory>(
    452           base::RefCountedString::TakeString(&file_contents_copy)));
    453 }
    454 
    455 void OSExchangeDataProviderAuraX11::SetHtml(const base::string16& html,
    456                                             const GURL& base_url) {
    457   std::vector<unsigned char> bytes;
    458   // Manually jam a UTF16 BOM into bytes because otherwise, other programs will
    459   // assume UTF-8.
    460   bytes.push_back(0xFF);
    461   bytes.push_back(0xFE);
    462   ui::AddString16ToVector(html, &bytes);
    463   scoped_refptr<base::RefCountedMemory> mem(
    464       base::RefCountedBytes::TakeVector(&bytes));
    465 
    466   format_map_.Insert(atom_cache_.GetAtom(Clipboard::kMimeTypeHTML), mem);
    467 }
    468 
    469 bool OSExchangeDataProviderAuraX11::GetHtml(base::string16* html,
    470                                             GURL* base_url) const {
    471   std::vector< ::Atom> url_atoms;
    472   url_atoms.push_back(atom_cache_.GetAtom(Clipboard::kMimeTypeHTML));
    473   std::vector< ::Atom> requested_types;
    474   ui::GetAtomIntersection(url_atoms, GetTargets(), &requested_types);
    475 
    476   ui::SelectionData data(format_map_.GetFirstOf(requested_types));
    477   if (data.IsValid()) {
    478     *html = data.GetHtml();
    479     *base_url = GURL();
    480     return true;
    481   }
    482 
    483   return false;
    484 }
    485 
    486 bool OSExchangeDataProviderAuraX11::HasHtml() const {
    487   std::vector< ::Atom> url_atoms;
    488   url_atoms.push_back(atom_cache_.GetAtom(Clipboard::kMimeTypeHTML));
    489   std::vector< ::Atom> requested_types;
    490   ui::GetAtomIntersection(url_atoms, GetTargets(), &requested_types);
    491 
    492   return !requested_types.empty();
    493 }
    494 
    495 void OSExchangeDataProviderAuraX11::SetDragImage(
    496     const gfx::ImageSkia& image,
    497     const gfx::Vector2d& cursor_offset) {
    498   drag_image_ = image;
    499   drag_image_offset_ = cursor_offset;
    500 }
    501 
    502 const gfx::ImageSkia& OSExchangeDataProviderAuraX11::GetDragImage() const {
    503   return drag_image_;
    504 }
    505 
    506 const gfx::Vector2d& OSExchangeDataProviderAuraX11::GetDragImageOffset() const {
    507   return drag_image_offset_;
    508 }
    509 
    510 bool OSExchangeDataProviderAuraX11::CanDispatchEvent(
    511     const PlatformEvent& event) {
    512   return event->xany.window == x_window_;
    513 }
    514 
    515 uint32_t OSExchangeDataProviderAuraX11::DispatchEvent(
    516     const PlatformEvent& event) {
    517   XEvent* xev = event;
    518   switch (xev->type) {
    519     case SelectionRequest:
    520       selection_owner_.OnSelectionRequest(*xev);
    521       return ui::POST_DISPATCH_STOP_PROPAGATION;
    522     default:
    523       NOTIMPLEMENTED();
    524   }
    525   return ui::POST_DISPATCH_NONE;
    526 }
    527 
    528 bool OSExchangeDataProviderAuraX11::GetPlainTextURL(GURL* url) const {
    529   base::string16 text;
    530   if (GetString(&text)) {
    531     GURL test_url(text);
    532     if (test_url.is_valid()) {
    533       *url = test_url;
    534       return true;
    535     }
    536   }
    537 
    538   return false;
    539 }
    540 
    541 std::vector< ::Atom> OSExchangeDataProviderAuraX11::GetTargets() const {
    542   return format_map_.GetTypes();
    543 }
    544 
    545 ///////////////////////////////////////////////////////////////////////////////
    546 // OSExchangeData, public:
    547 
    548 // static
    549 OSExchangeData::Provider* OSExchangeData::CreateProvider() {
    550   return new OSExchangeDataProviderAuraX11();
    551 }
    552 
    553 }  // namespace ui
    554