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