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 std::string utf8 = base::UTF16ToUTF8(text_data); 131 scoped_refptr<base::RefCountedMemory> mem( 132 base::RefCountedString::TakeString(&utf8)); 133 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); 138 } 139 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()); 147 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)); 154 155 format_map_.Insert(atom_cache_.GetAtom(kMimeTypeMozillaURL), mem); 156 157 // Set a string fallback as well. 158 SetString(spec); 159 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()) 165 return; 166 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))); 178 } 179 } 180 181 void OSExchangeDataProviderAuraX11::SetFilename(const base::FilePath& path) { 182 std::vector<FileInfo> data; 183 data.push_back(FileInfo(path, base::FilePath())); 184 SetFilenames(data); 185 } 186 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(); 192 ++it) { 193 std::string url_spec = net::FilePathToFileURL(it->path).spec(); 194 if (!url_spec.empty()) 195 paths.push_back(url_spec); 196 } 197 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); 202 } 203 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()); 209 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)); 214 215 format_map_.Insert(atom_cache_.GetAtom(format.ToString().c_str()), mem); 216 } 217 218 bool OSExchangeDataProviderAuraX11::GetString(base::string16* result) const { 219 if (HasFile()) { 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. 223 return false; 224 } 225 226 std::vector< ::Atom> text_atoms = ui::GetTextAtomsFrom(&atom_cache_); 227 std::vector< ::Atom> requested_types; 228 ui::GetAtomIntersection(text_atoms, GetTargets(), &requested_types); 229 230 ui::SelectionData data(format_map_.GetFirstOf(requested_types)); 231 if (data.IsValid()) { 232 std::string text = data.GetText(); 233 *result = base::UTF8ToUTF16(text); 234 return true; 235 } 236 237 return false; 238 } 239 240 bool OSExchangeDataProviderAuraX11::GetURLAndTitle( 241 OSExchangeData::FilenameToURLPolicy policy, 242 GURL* url, 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); 247 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. 253 254 if (data.GetType() == atom_cache_.GetAtom(kMimeTypeMozillaURL)) { 255 // Mozilla URLs are (UTF16: URL, newline, title). 256 base::string16 unparsed; 257 data.AssignTo(&unparsed); 258 259 std::vector<base::string16> tokens; 260 size_t num_tokens = Tokenize(unparsed, base::ASCIIToUTF16("\n"), &tokens); 261 if (num_tokens > 0) { 262 if (num_tokens > 1) 263 *title = tokens[1]; 264 else 265 *title = base::string16(); 266 267 *url = GURL(tokens[0]); 268 return true; 269 } 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) { 275 GURL test_url(*it); 276 if (!test_url.SchemeIsFile() || 277 policy == OSExchangeData::CONVERT_FILENAMES) { 278 *url = test_url; 279 *title = base::string16(); 280 return true; 281 } 282 } 283 } 284 } 285 286 return false; 287 } 288 289 bool OSExchangeDataProviderAuraX11::GetFilename(base::FilePath* path) const { 290 std::vector<FileInfo> filenames; 291 if (GetFilenames(&filenames)) { 292 *path = filenames.front().path; 293 return true; 294 } 295 296 return false; 297 } 298 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); 304 305 filenames->clear(); 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) { 311 GURL url(*it); 312 base::FilePath file_path; 313 if (url.SchemeIsFile() && net::FileURLToFilePath(url, &file_path)) { 314 filenames->push_back(FileInfo(file_path, base::FilePath())); 315 } 316 } 317 } 318 319 return !filenames->empty(); 320 } 321 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())); 327 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())); 334 return true; 335 } 336 337 return false; 338 } 339 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(); 345 } 346 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); 352 353 if (requested_types.empty()) 354 return false; 355 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. 362 return true; 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) 370 return true; 371 } 372 373 return false; 374 } 375 } 376 377 return false; 378 } 379 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); 384 385 if (requested_types.empty()) 386 return false; 387 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:// 390 // URIs. 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) { 396 GURL url(*it); 397 base::FilePath file_path; 398 if (url.SchemeIsFile() && net::FileURLToFilePath(url, &file_path)) 399 return true; 400 } 401 } 402 403 return false; 404 } 405 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); 412 413 return !requested_types.empty(); 414 } 415 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))); 422 423 file_contents_name_ = filename; 424 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 433 // source. 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 439 // the work. 440 std::string failure("F"); 441 format_map_.Insert( 442 atom_cache_.GetAtom("XdndDirectSave0"), 443 scoped_refptr<base::RefCountedMemory>( 444 base::RefCountedString::TakeString(&failure))); 445 std::string file_contents_copy = file_contents; 446 format_map_.Insert( 447 atom_cache_.GetAtom("application/octet-stream"), 448 scoped_refptr<base::RefCountedMemory>( 449 base::RefCountedString::TakeString(&file_contents_copy))); 450 } 451 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 456 // assume UTF-8. 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)); 462 463 format_map_.Insert(atom_cache_.GetAtom(Clipboard::kMimeTypeHTML), mem); 464 } 465 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); 472 473 ui::SelectionData data(format_map_.GetFirstOf(requested_types)); 474 if (data.IsValid()) { 475 *html = data.GetHtml(); 476 *base_url = GURL(); 477 return true; 478 } 479 480 return false; 481 } 482 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); 488 489 return !requested_types.empty(); 490 } 491 492 void OSExchangeDataProviderAuraX11::SetDragImage( 493 const gfx::ImageSkia& image, 494 const gfx::Vector2d& cursor_offset) { 495 drag_image_ = image; 496 drag_image_offset_ = cursor_offset; 497 } 498 499 const gfx::ImageSkia& OSExchangeDataProviderAuraX11::GetDragImage() const { 500 return drag_image_; 501 } 502 503 const gfx::Vector2d& OSExchangeDataProviderAuraX11::GetDragImageOffset() const { 504 return drag_image_offset_; 505 } 506 507 bool OSExchangeDataProviderAuraX11::CanDispatchEvent( 508 const PlatformEvent& event) { 509 return event->xany.window == x_window_; 510 } 511 512 uint32_t OSExchangeDataProviderAuraX11::DispatchEvent( 513 const PlatformEvent& event) { 514 XEvent* xev = event; 515 switch (xev->type) { 516 case SelectionRequest: 517 selection_owner_.OnSelectionRequest(xev->xselectionrequest); 518 return ui::POST_DISPATCH_STOP_PROPAGATION; 519 default: 520 NOTIMPLEMENTED(); 521 } 522 return ui::POST_DISPATCH_NONE; 523 } 524 525 bool OSExchangeDataProviderAuraX11::GetPlainTextURL(GURL* url) const { 526 base::string16 text; 527 if (GetString(&text)) { 528 GURL test_url(text); 529 if (test_url.is_valid()) { 530 *url = test_url; 531 return true; 532 } 533 } 534 535 return false; 536 } 537 538 std::vector< ::Atom> OSExchangeDataProviderAuraX11::GetTargets() const { 539 return format_map_.GetTypes(); 540 } 541 542 /////////////////////////////////////////////////////////////////////////////// 543 // OSExchangeData, public: 544 545 // static 546 OSExchangeData::Provider* OSExchangeData::CreateProvider() { 547 return new OSExchangeDataProviderAuraX11(); 548 } 549 550 } // namespace ui 551