// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "ui/base/dragdrop/os_exchange_data_provider_aurax11.h" #include "base/logging.h" #include "base/memory/ref_counted_memory.h" #include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "net/base/filename_util.h" #include "ui/base/clipboard/clipboard.h" #include "ui/base/clipboard/scoped_clipboard_writer.h" #include "ui/base/dragdrop/file_info.h" #include "ui/base/x/selection_utils.h" #include "ui/base/x/x11_util.h" #include "ui/events/platform/platform_event_source.h" // Note: the GetBlah() methods are used immediately by the // web_contents_view_aura.cc:PrepareDropData(), while the omnibox is a // little more discriminating and calls HasBlah() before trying to get the // information. namespace ui { namespace { const char kDndSelection[] = "XdndSelection"; const char kRendererTaint[] = "chromium/x-renderer-taint"; const char kNetscapeURL[] = "_NETSCAPE_URL"; const char* kAtomsToCache[] = { kString, kText, kUtf8String, kDndSelection, Clipboard::kMimeTypeURIList, kMimeTypeMozillaURL, kNetscapeURL, Clipboard::kMimeTypeText, kRendererTaint, NULL }; } // namespace OSExchangeDataProviderAuraX11::OSExchangeDataProviderAuraX11( ::Window x_window, const SelectionFormatMap& selection) : x_display_(gfx::GetXDisplay()), x_root_window_(DefaultRootWindow(x_display_)), own_window_(false), x_window_(x_window), atom_cache_(x_display_, kAtomsToCache), format_map_(selection), selection_owner_(x_display_, x_window_, atom_cache_.GetAtom(kDndSelection)) { // We don't know all possible MIME types at compile time. atom_cache_.allow_uncached_atoms(); } OSExchangeDataProviderAuraX11::OSExchangeDataProviderAuraX11() : x_display_(gfx::GetXDisplay()), x_root_window_(DefaultRootWindow(x_display_)), own_window_(true), x_window_(XCreateWindow( x_display_, x_root_window_, -100, -100, 10, 10, // x, y, width, height 0, // border width CopyFromParent, // depth InputOnly, CopyFromParent, // visual 0, NULL)), atom_cache_(x_display_, kAtomsToCache), format_map_(), selection_owner_(x_display_, x_window_, atom_cache_.GetAtom(kDndSelection)) { // We don't know all possible MIME types at compile time. atom_cache_.allow_uncached_atoms(); XStoreName(x_display_, x_window_, "Chromium Drag & Drop Window"); PlatformEventSource::GetInstance()->AddPlatformEventDispatcher(this); } OSExchangeDataProviderAuraX11::~OSExchangeDataProviderAuraX11() { if (own_window_) { PlatformEventSource::GetInstance()->RemovePlatformEventDispatcher(this); XDestroyWindow(x_display_, x_window_); } } void OSExchangeDataProviderAuraX11::TakeOwnershipOfSelection() const { selection_owner_.TakeOwnershipOfSelection(format_map_); } void OSExchangeDataProviderAuraX11::RetrieveTargets( std::vector* targets) const { selection_owner_.RetrieveTargets(targets); } SelectionFormatMap OSExchangeDataProviderAuraX11::GetFormatMap() const { // We return the |selection_owner_|'s format map instead of our own in case // ours has been modified since TakeOwnershipOfSelection() was called. return selection_owner_.selection_format_map(); } OSExchangeData::Provider* OSExchangeDataProviderAuraX11::Clone() const { OSExchangeDataProviderAuraX11* ret = new OSExchangeDataProviderAuraX11(); ret->format_map_ = format_map_; return ret; } void OSExchangeDataProviderAuraX11::MarkOriginatedFromRenderer() { std::string empty; format_map_.Insert(atom_cache_.GetAtom(kRendererTaint), scoped_refptr( base::RefCountedString::TakeString(&empty))); } bool OSExchangeDataProviderAuraX11::DidOriginateFromRenderer() const { return format_map_.find(atom_cache_.GetAtom(kRendererTaint)) != format_map_.end(); } void OSExchangeDataProviderAuraX11::SetString(const base::string16& text_data) { if (HasString()) return; std::string utf8 = base::UTF16ToUTF8(text_data); scoped_refptr mem( base::RefCountedString::TakeString(&utf8)); format_map_.Insert(atom_cache_.GetAtom(Clipboard::kMimeTypeText), mem); format_map_.Insert(atom_cache_.GetAtom(kText), mem); format_map_.Insert(atom_cache_.GetAtom(kString), mem); format_map_.Insert(atom_cache_.GetAtom(kUtf8String), mem); } void OSExchangeDataProviderAuraX11::SetURL(const GURL& url, const base::string16& title) { // TODO(dcheng): The original GTK code tries very hard to avoid writing out an // empty title. Is this necessary? if (url.is_valid()) { // Mozilla's URL format: (UTF16: URL, newline, title) base::string16 spec = base::UTF8ToUTF16(url.spec()); std::vector data; ui::AddString16ToVector(spec, &data); ui::AddString16ToVector(base::ASCIIToUTF16("\n"), &data); ui::AddString16ToVector(title, &data); scoped_refptr mem( base::RefCountedBytes::TakeVector(&data)); format_map_.Insert(atom_cache_.GetAtom(kMimeTypeMozillaURL), mem); // Set a string fallback as well. SetString(spec); // Return early if this drag already contains file contents (this implies // that file contents must be populated before URLs). Nautilus (and possibly // other file managers) prefer _NETSCAPE_URL over the X Direct Save // protocol, but we want to prioritize XDS in this case. if (!file_contents_name_.empty()) return; // Set _NETSCAPE_URL for file managers like Nautilus that use it as a hint // to create a link to the URL. Setting text/uri-list doesn't work because // Nautilus will fetch and copy the contents of the URL to the drop target // instead of linking... // Format is UTF8: URL + "\n" + title. std::string netscape_url = url.spec(); netscape_url += "\n"; netscape_url += base::UTF16ToUTF8(title); format_map_.Insert(atom_cache_.GetAtom(kNetscapeURL), scoped_refptr( base::RefCountedString::TakeString(&netscape_url))); } } void OSExchangeDataProviderAuraX11::SetFilename(const base::FilePath& path) { std::vector data; data.push_back(FileInfo(path, base::FilePath())); SetFilenames(data); } void OSExchangeDataProviderAuraX11::SetFilenames( const std::vector& filenames) { std::vector paths; for (std::vector::const_iterator it = filenames.begin(); it != filenames.end(); ++it) { std::string url_spec = net::FilePathToFileURL(it->path).spec(); if (!url_spec.empty()) paths.push_back(url_spec); } std::string joined_data = base::JoinString(paths, "\n"); scoped_refptr mem( base::RefCountedString::TakeString(&joined_data)); format_map_.Insert(atom_cache_.GetAtom(Clipboard::kMimeTypeURIList), mem); } void OSExchangeDataProviderAuraX11::SetPickledData( const Clipboard::FormatType& format, const base::Pickle& pickle) { const unsigned char* data = reinterpret_cast(pickle.data()); std::vector bytes; bytes.insert(bytes.end(), data, data + pickle.size()); scoped_refptr mem( base::RefCountedBytes::TakeVector(&bytes)); format_map_.Insert(atom_cache_.GetAtom(format.ToString().c_str()), mem); } bool OSExchangeDataProviderAuraX11::GetString(base::string16* result) const { if (HasFile()) { // Various Linux file managers both pass a list of file:// URIs and set the // string representation to the URI. We explicitly don't want to return use // this representation. return false; } std::vector< ::Atom> text_atoms = ui::GetTextAtomsFrom(&atom_cache_); std::vector< ::Atom> requested_types; ui::GetAtomIntersection(text_atoms, GetTargets(), &requested_types); ui::SelectionData data(format_map_.GetFirstOf(requested_types)); if (data.IsValid()) { std::string text = data.GetText(); *result = base::UTF8ToUTF16(text); return true; } return false; } bool OSExchangeDataProviderAuraX11::GetURLAndTitle( OSExchangeData::FilenameToURLPolicy policy, GURL* url, base::string16* title) const { std::vector< ::Atom> url_atoms = ui::GetURLAtomsFrom(&atom_cache_); std::vector< ::Atom> requested_types; ui::GetAtomIntersection(url_atoms, GetTargets(), &requested_types); ui::SelectionData data(format_map_.GetFirstOf(requested_types)); if (data.IsValid()) { // TODO(erg): Technically, both of these forms can accept multiple URLs, // but that doesn't match the assumptions of the rest of the system which // expect single types. if (data.GetType() == atom_cache_.GetAtom(kMimeTypeMozillaURL)) { // Mozilla URLs are (UTF16: URL, newline, title). base::string16 unparsed; data.AssignTo(&unparsed); std::vector tokens = base::SplitString( unparsed, base::ASCIIToUTF16("\n"), base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY); if (tokens.size() > 0) { if (tokens.size() > 1) *title = tokens[1]; else *title = base::string16(); *url = GURL(tokens[0]); return true; } } else if (data.GetType() == atom_cache_.GetAtom( Clipboard::kMimeTypeURIList)) { std::vector tokens = ui::ParseURIList(data); for (std::vector::const_iterator it = tokens.begin(); it != tokens.end(); ++it) { GURL test_url(*it); if (!test_url.SchemeIsFile() || policy == OSExchangeData::CONVERT_FILENAMES) { *url = test_url; *title = base::string16(); return true; } } } } return false; } bool OSExchangeDataProviderAuraX11::GetFilename(base::FilePath* path) const { std::vector filenames; if (GetFilenames(&filenames)) { *path = filenames.front().path; return true; } return false; } bool OSExchangeDataProviderAuraX11::GetFilenames( std::vector* filenames) const { std::vector< ::Atom> url_atoms = ui::GetURIListAtomsFrom(&atom_cache_); std::vector< ::Atom> requested_types; ui::GetAtomIntersection(url_atoms, GetTargets(), &requested_types); filenames->clear(); ui::SelectionData data(format_map_.GetFirstOf(requested_types)); if (data.IsValid()) { std::vector tokens = ui::ParseURIList(data); for (std::vector::const_iterator it = tokens.begin(); it != tokens.end(); ++it) { GURL url(*it); base::FilePath file_path; if (url.SchemeIsFile() && net::FileURLToFilePath(url, &file_path)) { filenames->push_back(FileInfo(file_path, base::FilePath())); } } } return !filenames->empty(); } bool OSExchangeDataProviderAuraX11::GetPickledData( const Clipboard::FormatType& format, base::Pickle* pickle) const { std::vector< ::Atom> requested_types; requested_types.push_back(atom_cache_.GetAtom(format.ToString().c_str())); ui::SelectionData data(format_map_.GetFirstOf(requested_types)); if (data.IsValid()) { // Note that the pickle object on the right hand side of the assignment // only refers to the bytes in |data|. The assignment copies the data. *pickle = base::Pickle(reinterpret_cast(data.GetData()), static_cast(data.GetSize())); return true; } return false; } bool OSExchangeDataProviderAuraX11::HasString() const { std::vector< ::Atom> text_atoms = ui::GetTextAtomsFrom(&atom_cache_); std::vector< ::Atom> requested_types; ui::GetAtomIntersection(text_atoms, GetTargets(), &requested_types); return !requested_types.empty() && !HasFile(); } bool OSExchangeDataProviderAuraX11::HasURL( OSExchangeData::FilenameToURLPolicy policy) const { std::vector< ::Atom> url_atoms = ui::GetURLAtomsFrom(&atom_cache_); std::vector< ::Atom> requested_types; ui::GetAtomIntersection(url_atoms, GetTargets(), &requested_types); if (requested_types.empty()) return false; // The Linux desktop doesn't differentiate between files and URLs like // Windows does and stuffs all the data into one mime type. ui::SelectionData data(format_map_.GetFirstOf(requested_types)); if (data.IsValid()) { if (data.GetType() == atom_cache_.GetAtom(kMimeTypeMozillaURL)) { // File managers shouldn't be using this type, so this is a URL. return true; } else if (data.GetType() == atom_cache_.GetAtom( ui::Clipboard::kMimeTypeURIList)) { std::vector tokens = ui::ParseURIList(data); for (std::vector::const_iterator it = tokens.begin(); it != tokens.end(); ++it) { if (!GURL(*it).SchemeIsFile() || policy == OSExchangeData::CONVERT_FILENAMES) return true; } return false; } } return false; } bool OSExchangeDataProviderAuraX11::HasFile() const { std::vector< ::Atom> url_atoms = ui::GetURIListAtomsFrom(&atom_cache_); std::vector< ::Atom> requested_types; ui::GetAtomIntersection(url_atoms, GetTargets(), &requested_types); if (requested_types.empty()) return false; // To actually answer whether we have a file, we need to look through the // contents of the kMimeTypeURIList type, and see if any of them are file:// // URIs. ui::SelectionData data(format_map_.GetFirstOf(requested_types)); if (data.IsValid()) { std::vector tokens = ui::ParseURIList(data); for (std::vector::const_iterator it = tokens.begin(); it != tokens.end(); ++it) { GURL url(*it); base::FilePath file_path; if (url.SchemeIsFile() && net::FileURLToFilePath(url, &file_path)) return true; } } return false; } bool OSExchangeDataProviderAuraX11::HasCustomFormat( const Clipboard::FormatType& format) const { std::vector< ::Atom> url_atoms; url_atoms.push_back(atom_cache_.GetAtom(format.ToString().c_str())); std::vector< ::Atom> requested_types; ui::GetAtomIntersection(url_atoms, GetTargets(), &requested_types); return !requested_types.empty(); } void OSExchangeDataProviderAuraX11::SetFileContents( const base::FilePath& filename, const std::string& file_contents) { DCHECK(!filename.empty()); DCHECK(format_map_.end() == format_map_.find(atom_cache_.GetAtom(kMimeTypeMozillaURL))); file_contents_name_ = filename; // Direct save handling is a complicated juggling affair between this class, // SelectionFormat, and DesktopDragDropClientAuraX11. The general idea behind // the protocol is this: // - The source window sets its XdndDirectSave0 window property to the // proposed filename. // - When a target window receives the drop, it updates the XdndDirectSave0 // property on the source window to the filename it would like the contents // to be saved to and then requests the XdndDirectSave0 type from the // source. // - The source is supposed to copy the file here and return success (S), // failure (F), or error (E). // - In this case, failure means the destination should try to populate the // file itself by copying the data from application/octet-stream. To make // things simpler for Chrome, we always 'fail' and let the destination do // the work. std::string failure("F"); format_map_.Insert( atom_cache_.GetAtom("XdndDirectSave0"), scoped_refptr( base::RefCountedString::TakeString(&failure))); std::string file_contents_copy = file_contents; format_map_.Insert( atom_cache_.GetAtom("application/octet-stream"), scoped_refptr( base::RefCountedString::TakeString(&file_contents_copy))); } void OSExchangeDataProviderAuraX11::SetHtml(const base::string16& html, const GURL& base_url) { std::vector bytes; // Manually jam a UTF16 BOM into bytes because otherwise, other programs will // assume UTF-8. bytes.push_back(0xFF); bytes.push_back(0xFE); ui::AddString16ToVector(html, &bytes); scoped_refptr mem( base::RefCountedBytes::TakeVector(&bytes)); format_map_.Insert(atom_cache_.GetAtom(Clipboard::kMimeTypeHTML), mem); } bool OSExchangeDataProviderAuraX11::GetHtml(base::string16* html, GURL* base_url) const { std::vector< ::Atom> url_atoms; url_atoms.push_back(atom_cache_.GetAtom(Clipboard::kMimeTypeHTML)); std::vector< ::Atom> requested_types; ui::GetAtomIntersection(url_atoms, GetTargets(), &requested_types); ui::SelectionData data(format_map_.GetFirstOf(requested_types)); if (data.IsValid()) { *html = data.GetHtml(); *base_url = GURL(); return true; } return false; } bool OSExchangeDataProviderAuraX11::HasHtml() const { std::vector< ::Atom> url_atoms; url_atoms.push_back(atom_cache_.GetAtom(Clipboard::kMimeTypeHTML)); std::vector< ::Atom> requested_types; ui::GetAtomIntersection(url_atoms, GetTargets(), &requested_types); return !requested_types.empty(); } void OSExchangeDataProviderAuraX11::SetDragImage( const gfx::ImageSkia& image, const gfx::Vector2d& cursor_offset) { drag_image_ = image; drag_image_offset_ = cursor_offset; } const gfx::ImageSkia& OSExchangeDataProviderAuraX11::GetDragImage() const { return drag_image_; } const gfx::Vector2d& OSExchangeDataProviderAuraX11::GetDragImageOffset() const { return drag_image_offset_; } bool OSExchangeDataProviderAuraX11::CanDispatchEvent( const PlatformEvent& event) { return event->xany.window == x_window_; } uint32_t OSExchangeDataProviderAuraX11::DispatchEvent( const PlatformEvent& event) { XEvent* xev = event; switch (xev->type) { case SelectionRequest: selection_owner_.OnSelectionRequest(*xev); return ui::POST_DISPATCH_STOP_PROPAGATION; default: NOTIMPLEMENTED(); } return ui::POST_DISPATCH_NONE; } bool OSExchangeDataProviderAuraX11::GetPlainTextURL(GURL* url) const { base::string16 text; if (GetString(&text)) { GURL test_url(text); if (test_url.is_valid()) { *url = test_url; return true; } } return false; } std::vector< ::Atom> OSExchangeDataProviderAuraX11::GetTargets() const { return format_map_.GetTypes(); } /////////////////////////////////////////////////////////////////////////////// // OSExchangeData, public: // static OSExchangeData::Provider* OSExchangeData::CreateProvider() { return new OSExchangeDataProviderAuraX11(); } } // namespace ui