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.
5 #include "ui/base/dragdrop/os_exchange_data_provider_aurax11.h"
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"
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
28 const char kDndSelection[] = "XdndSelection";
29 const char kRendererTaint[] = "chromium/x-renderer-taint";
31 const char kNetscapeURL[] = "_NETSCAPE_URL";
33 const char* kAtomsToCache[] = {
38 Clipboard::kMimeTypeURIList,
41 Clipboard::kMimeTypeText,
48 OSExchangeDataProviderAuraX11::OSExchangeDataProviderAuraX11(
50 const SelectionFormatMap& selection)
51 : x_display_(gfx::GetXDisplay()),
52 x_root_window_(DefaultRootWindow(x_display_)),
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();
63 OSExchangeDataProviderAuraX11::OSExchangeDataProviderAuraX11()
64 : x_display_(gfx::GetXDisplay()),
65 x_root_window_(DefaultRootWindow(x_display_)),
67 x_window_(XCreateWindow(
70 -100, -100, 10, 10, // x, y, width, height
72 CopyFromParent, // depth
74 CopyFromParent, // visual
77 atom_cache_(x_display_, kAtomsToCache),
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();
84 XStoreName(x_display_, x_window_, "Chromium Drag & Drop Window");
86 PlatformEventSource::GetInstance()->AddPlatformEventDispatcher(this);
89 OSExchangeDataProviderAuraX11::~OSExchangeDataProviderAuraX11() {
91 PlatformEventSource::GetInstance()->RemovePlatformEventDispatcher(this);
92 XDestroyWindow(x_display_, x_window_);
96 void OSExchangeDataProviderAuraX11::TakeOwnershipOfSelection() const {
97 selection_owner_.TakeOwnershipOfSelection(format_map_);
100 void OSExchangeDataProviderAuraX11::RetrieveTargets(
101 std::vector<Atom>* targets) const {
102 selection_owner_.RetrieveTargets(targets);
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();
111 OSExchangeData::Provider* OSExchangeDataProviderAuraX11::Clone() const {
112 OSExchangeDataProviderAuraX11* ret = new OSExchangeDataProviderAuraX11();
113 ret->format_map_ = format_map_;
117 void OSExchangeDataProviderAuraX11::MarkOriginatedFromRenderer() {
119 format_map_.Insert(atom_cache_.GetAtom(kRendererTaint),
120 scoped_refptr<base::RefCountedMemory>(
121 base::RefCountedString::TakeString(&empty)));
124 bool OSExchangeDataProviderAuraX11::DidOriginateFromRenderer() const {
125 return format_map_.find(atom_cache_.GetAtom(kRendererTaint)) !=
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));
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);
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());
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));
155 format_map_.Insert(atom_cache_.GetAtom(kMimeTypeMozillaURL), mem);
157 // Set a string fallback as well.
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())
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)));
181 void OSExchangeDataProviderAuraX11::SetFilename(const base::FilePath& path) {
182 std::vector<FileInfo> data;
183 data.push_back(FileInfo(path, base::FilePath()));
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();
193 std::string url_spec = net::FilePathToFileURL(it->path).spec();
194 if (!url_spec.empty())
195 paths.push_back(url_spec);
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);
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());
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));
215 format_map_.Insert(atom_cache_.GetAtom(format.ToString().c_str()), mem);
218 bool OSExchangeDataProviderAuraX11::GetString(base::string16* result) const {
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.
226 std::vector< ::Atom> text_atoms = ui::GetTextAtomsFrom(&atom_cache_);
227 std::vector< ::Atom> requested_types;
228 ui::GetAtomIntersection(text_atoms, GetTargets(), &requested_types);
230 ui::SelectionData data(format_map_.GetFirstOf(requested_types));
231 if (data.IsValid()) {
232 std::string text = data.GetText();
233 *result = base::UTF8ToUTF16(text);
240 bool OSExchangeDataProviderAuraX11::GetURLAndTitle(
241 OSExchangeData::FilenameToURLPolicy policy,
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);
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.
254 if (data.GetType() == atom_cache_.GetAtom(kMimeTypeMozillaURL)) {
255 // Mozilla URLs are (UTF16: URL, newline, title).
256 base::string16 unparsed;
257 data.AssignTo(&unparsed);
259 std::vector<base::string16> tokens;
260 size_t num_tokens = Tokenize(unparsed, base::ASCIIToUTF16("\n"), &tokens);
261 if (num_tokens > 0) {
265 *title = base::string16();
267 *url = GURL(tokens[0]);
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) {
276 if (!test_url.SchemeIsFile() ||
277 policy == OSExchangeData::CONVERT_FILENAMES) {
279 *title = base::string16();
289 bool OSExchangeDataProviderAuraX11::GetFilename(base::FilePath* path) const {
290 std::vector<FileInfo> filenames;
291 if (GetFilenames(&filenames)) {
292 *path = filenames.front().path;
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);
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) {
312 base::FilePath file_path;
313 if (url.SchemeIsFile() && net::FileURLToFilePath(url, &file_path)) {
314 filenames->push_back(FileInfo(file_path, base::FilePath()));
319 return !filenames->empty();
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()));
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()));
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();
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);
353 if (requested_types.empty())
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.
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)
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);
385 if (requested_types.empty())
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://
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) {
397 base::FilePath file_path;
398 if (url.SchemeIsFile() && net::FileURLToFilePath(url, &file_path))
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);
413 return !requested_types.empty();
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)));
423 file_contents_name_ = filename;
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
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
440 std::string failure("F");
442 atom_cache_.GetAtom("XdndDirectSave0"),
443 scoped_refptr<base::RefCountedMemory>(
444 base::RefCountedString::TakeString(&failure)));
445 std::string file_contents_copy = file_contents;
447 atom_cache_.GetAtom("application/octet-stream"),
448 scoped_refptr<base::RefCountedMemory>(
449 base::RefCountedString::TakeString(&file_contents_copy)));
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
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));
463 format_map_.Insert(atom_cache_.GetAtom(Clipboard::kMimeTypeHTML), mem);
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);
473 ui::SelectionData data(format_map_.GetFirstOf(requested_types));
474 if (data.IsValid()) {
475 *html = data.GetHtml();
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);
489 return !requested_types.empty();
492 void OSExchangeDataProviderAuraX11::SetDragImage(
493 const gfx::ImageSkia& image,
494 const gfx::Vector2d& cursor_offset) {
496 drag_image_offset_ = cursor_offset;
499 const gfx::ImageSkia& OSExchangeDataProviderAuraX11::GetDragImage() const {
503 const gfx::Vector2d& OSExchangeDataProviderAuraX11::GetDragImageOffset() const {
504 return drag_image_offset_;
507 bool OSExchangeDataProviderAuraX11::CanDispatchEvent(
508 const PlatformEvent& event) {
509 return event->xany.window == x_window_;
512 uint32_t OSExchangeDataProviderAuraX11::DispatchEvent(
513 const PlatformEvent& event) {
516 case SelectionRequest:
517 selection_owner_.OnSelectionRequest(xev->xselectionrequest);
518 return ui::POST_DISPATCH_STOP_PROPAGATION;
522 return ui::POST_DISPATCH_NONE;
525 bool OSExchangeDataProviderAuraX11::GetPlainTextURL(GURL* url) const {
527 if (GetString(&text)) {
529 if (test_url.is_valid()) {
538 std::vector< ::Atom> OSExchangeDataProviderAuraX11::GetTargets() const {
539 return format_map_.GetTypes();
542 ///////////////////////////////////////////////////////////////////////////////
543 // OSExchangeData, public:
546 OSExchangeData::Provider* OSExchangeData::CreateProvider() {
547 return new OSExchangeDataProviderAuraX11();