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
) {
133 std::string utf8
= base::UTF16ToUTF8(text_data
);
134 scoped_refptr
<base::RefCountedMemory
> mem(
135 base::RefCountedString::TakeString(&utf8
));
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
);
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());
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
));
158 format_map_
.Insert(atom_cache_
.GetAtom(kMimeTypeMozillaURL
), mem
);
160 // Set a string fallback as well.
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())
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
)));
184 void OSExchangeDataProviderAuraX11::SetFilename(const base::FilePath
& path
) {
185 std::vector
<FileInfo
> data
;
186 data
.push_back(FileInfo(path
, base::FilePath()));
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();
196 std::string url_spec
= net::FilePathToFileURL(it
->path
).spec();
197 if (!url_spec
.empty())
198 paths
.push_back(url_spec
);
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
);
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());
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
));
218 format_map_
.Insert(atom_cache_
.GetAtom(format
.ToString().c_str()), mem
);
221 bool OSExchangeDataProviderAuraX11::GetString(base::string16
* result
) const {
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.
229 std::vector
< ::Atom
> text_atoms
= ui::GetTextAtomsFrom(&atom_cache_
);
230 std::vector
< ::Atom
> requested_types
;
231 ui::GetAtomIntersection(text_atoms
, GetTargets(), &requested_types
);
233 ui::SelectionData
data(format_map_
.GetFirstOf(requested_types
));
234 if (data
.IsValid()) {
235 std::string text
= data
.GetText();
236 *result
= base::UTF8ToUTF16(text
);
243 bool OSExchangeDataProviderAuraX11::GetURLAndTitle(
244 OSExchangeData::FilenameToURLPolicy policy
,
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
);
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.
257 if (data
.GetType() == atom_cache_
.GetAtom(kMimeTypeMozillaURL
)) {
258 // Mozilla URLs are (UTF16: URL, newline, title).
259 base::string16 unparsed
;
260 data
.AssignTo(&unparsed
);
262 std::vector
<base::string16
> tokens
;
263 size_t num_tokens
= Tokenize(unparsed
, base::ASCIIToUTF16("\n"), &tokens
);
264 if (num_tokens
> 0) {
268 *title
= base::string16();
270 *url
= GURL(tokens
[0]);
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
) {
279 if (!test_url
.SchemeIsFile() ||
280 policy
== OSExchangeData::CONVERT_FILENAMES
) {
282 *title
= base::string16();
292 bool OSExchangeDataProviderAuraX11::GetFilename(base::FilePath
* path
) const {
293 std::vector
<FileInfo
> filenames
;
294 if (GetFilenames(&filenames
)) {
295 *path
= filenames
.front().path
;
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
);
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
) {
315 base::FilePath file_path
;
316 if (url
.SchemeIsFile() && net::FileURLToFilePath(url
, &file_path
)) {
317 filenames
->push_back(FileInfo(file_path
, base::FilePath()));
322 return !filenames
->empty();
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()));
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()));
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();
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
);
356 if (requested_types
.empty())
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.
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
)
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
);
388 if (requested_types
.empty())
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://
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
) {
400 base::FilePath file_path
;
401 if (url
.SchemeIsFile() && net::FileURLToFilePath(url
, &file_path
))
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
);
416 return !requested_types
.empty();
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
)));
426 file_contents_name_
= filename
;
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
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
443 std::string
failure("F");
445 atom_cache_
.GetAtom("XdndDirectSave0"),
446 scoped_refptr
<base::RefCountedMemory
>(
447 base::RefCountedString::TakeString(&failure
)));
448 std::string file_contents_copy
= file_contents
;
450 atom_cache_
.GetAtom("application/octet-stream"),
451 scoped_refptr
<base::RefCountedMemory
>(
452 base::RefCountedString::TakeString(&file_contents_copy
)));
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
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
));
466 format_map_
.Insert(atom_cache_
.GetAtom(Clipboard::kMimeTypeHTML
), mem
);
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
);
476 ui::SelectionData
data(format_map_
.GetFirstOf(requested_types
));
477 if (data
.IsValid()) {
478 *html
= data
.GetHtml();
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
);
492 return !requested_types
.empty();
495 void OSExchangeDataProviderAuraX11::SetDragImage(
496 const gfx::ImageSkia
& image
,
497 const gfx::Vector2d
& cursor_offset
) {
499 drag_image_offset_
= cursor_offset
;
502 const gfx::ImageSkia
& OSExchangeDataProviderAuraX11::GetDragImage() const {
506 const gfx::Vector2d
& OSExchangeDataProviderAuraX11::GetDragImageOffset() const {
507 return drag_image_offset_
;
510 bool OSExchangeDataProviderAuraX11::CanDispatchEvent(
511 const PlatformEvent
& event
) {
512 return event
->xany
.window
== x_window_
;
515 uint32_t OSExchangeDataProviderAuraX11::DispatchEvent(
516 const PlatformEvent
& event
) {
519 case SelectionRequest
:
520 selection_owner_
.OnSelectionRequest(*xev
);
521 return ui::POST_DISPATCH_STOP_PROPAGATION
;
525 return ui::POST_DISPATCH_NONE
;
528 bool OSExchangeDataProviderAuraX11::GetPlainTextURL(GURL
* url
) const {
530 if (GetString(&text
)) {
532 if (test_url
.is_valid()) {
541 std::vector
< ::Atom
> OSExchangeDataProviderAuraX11::GetTargets() const {
542 return format_map_
.GetTypes();
545 ///////////////////////////////////////////////////////////////////////////////
546 // OSExchangeData, public:
549 OSExchangeData::Provider
* OSExchangeData::CreateProvider() {
550 return new OSExchangeDataProviderAuraX11();