1// Copyright 2013 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 "chrome/browser/shell_integration_linux.h" 6 7#include <algorithm> 8#include <cstdlib> 9#include <map> 10 11#include "base/base_paths.h" 12#include "base/command_line.h" 13#include "base/environment.h" 14#include "base/files/file_path.h" 15#include "base/files/file_util.h" 16#include "base/files/scoped_temp_dir.h" 17#include "base/message_loop/message_loop.h" 18#include "base/stl_util.h" 19#include "base/strings/string_util.h" 20#include "base/strings/utf_string_conversions.h" 21#include "base/test/scoped_path_override.h" 22#include "chrome/common/chrome_constants.h" 23#include "content/public/test/test_browser_thread.h" 24#include "testing/gmock/include/gmock/gmock.h" 25#include "testing/gtest/include/gtest/gtest.h" 26#include "url/gurl.h" 27 28#define FPL FILE_PATH_LITERAL 29 30using content::BrowserThread; 31using ::testing::ElementsAre; 32 33namespace shell_integration_linux { 34 35namespace { 36 37// Provides mock environment variables values based on a stored map. 38class MockEnvironment : public base::Environment { 39 public: 40 MockEnvironment() {} 41 42 void Set(const std::string& name, const std::string& value) { 43 variables_[name] = value; 44 } 45 46 virtual bool GetVar(const char* variable_name, std::string* result) OVERRIDE { 47 if (ContainsKey(variables_, variable_name)) { 48 *result = variables_[variable_name]; 49 return true; 50 } 51 52 return false; 53 } 54 55 virtual bool SetVar(const char* variable_name, 56 const std::string& new_value) OVERRIDE { 57 ADD_FAILURE(); 58 return false; 59 } 60 61 virtual bool UnSetVar(const char* variable_name) OVERRIDE { 62 ADD_FAILURE(); 63 return false; 64 } 65 66 private: 67 std::map<std::string, std::string> variables_; 68 69 DISALLOW_COPY_AND_ASSIGN(MockEnvironment); 70}; 71 72} // namespace 73 74TEST(ShellIntegrationTest, GetDataWriteLocation) { 75 base::MessageLoop message_loop; 76 content::TestBrowserThread file_thread(BrowserThread::FILE, &message_loop); 77 78 // Test that it returns $XDG_DATA_HOME. 79 { 80 MockEnvironment env; 81 env.Set("HOME", "/home/user"); 82 env.Set("XDG_DATA_HOME", "/user/path"); 83 base::FilePath path; 84 ASSERT_TRUE(GetDataWriteLocation(&env, &path)); 85 EXPECT_EQ(base::FilePath("/user/path"), path); 86 } 87 88 // Test that $XDG_DATA_HOME falls back to $HOME/.local/share. 89 { 90 MockEnvironment env; 91 env.Set("HOME", "/home/user"); 92 base::FilePath path; 93 ASSERT_TRUE(GetDataWriteLocation(&env, &path)); 94 EXPECT_EQ(base::FilePath("/home/user/.local/share"), path); 95 } 96 97 // Test that if neither $XDG_DATA_HOME nor $HOME are specified, it fails. 98 { 99 MockEnvironment env; 100 base::FilePath path; 101 ASSERT_FALSE(GetDataWriteLocation(&env, &path)); 102 } 103} 104 105TEST(ShellIntegrationTest, GetDataSearchLocations) { 106 base::MessageLoop message_loop; 107 content::TestBrowserThread file_thread(BrowserThread::FILE, &message_loop); 108 109 // Test that it returns $XDG_DATA_HOME + $XDG_DATA_DIRS. 110 { 111 MockEnvironment env; 112 env.Set("HOME", "/home/user"); 113 env.Set("XDG_DATA_HOME", "/user/path"); 114 env.Set("XDG_DATA_DIRS", "/system/path/1:/system/path/2"); 115 EXPECT_THAT( 116 GetDataSearchLocations(&env), 117 ElementsAre(base::FilePath("/user/path"), 118 base::FilePath("/system/path/1"), 119 base::FilePath("/system/path/2"))); 120 } 121 122 // Test that $XDG_DATA_HOME falls back to $HOME/.local/share. 123 { 124 MockEnvironment env; 125 env.Set("HOME", "/home/user"); 126 env.Set("XDG_DATA_DIRS", "/system/path/1:/system/path/2"); 127 EXPECT_THAT( 128 GetDataSearchLocations(&env), 129 ElementsAre(base::FilePath("/home/user/.local/share"), 130 base::FilePath("/system/path/1"), 131 base::FilePath("/system/path/2"))); 132 } 133 134 // Test that if neither $XDG_DATA_HOME nor $HOME are specified, it still 135 // succeeds. 136 { 137 MockEnvironment env; 138 env.Set("XDG_DATA_DIRS", "/system/path/1:/system/path/2"); 139 EXPECT_THAT( 140 GetDataSearchLocations(&env), 141 ElementsAre(base::FilePath("/system/path/1"), 142 base::FilePath("/system/path/2"))); 143 } 144 145 // Test that $XDG_DATA_DIRS falls back to the two default paths. 146 { 147 MockEnvironment env; 148 env.Set("HOME", "/home/user"); 149 env.Set("XDG_DATA_HOME", "/user/path"); 150 EXPECT_THAT( 151 GetDataSearchLocations(&env), 152 ElementsAre(base::FilePath("/user/path"), 153 base::FilePath("/usr/local/share"), 154 base::FilePath("/usr/share"))); 155 } 156} 157 158TEST(ShellIntegrationTest, GetExistingShortcutLocations) { 159 base::FilePath kProfilePath("Profile 1"); 160 const char kExtensionId[] = "test_extension"; 161 const char kTemplateFilename[] = "chrome-test_extension-Profile_1.desktop"; 162 base::FilePath kTemplateFilepath(kTemplateFilename); 163 const char kNoDisplayDesktopFile[] = "[Desktop Entry]\nNoDisplay=true"; 164 165 base::MessageLoop message_loop; 166 content::TestBrowserThread file_thread(BrowserThread::FILE, &message_loop); 167 168 // No existing shortcuts. 169 { 170 MockEnvironment env; 171 web_app::ShortcutLocations result = 172 GetExistingShortcutLocations(&env, kProfilePath, kExtensionId); 173 EXPECT_FALSE(result.on_desktop); 174 EXPECT_EQ(web_app::APP_MENU_LOCATION_NONE, 175 result.applications_menu_location); 176 177 EXPECT_FALSE(result.in_quick_launch_bar); 178 } 179 180 // Shortcut on desktop. 181 { 182 base::ScopedTempDir temp_dir; 183 ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); 184 base::FilePath desktop_path = temp_dir.path(); 185 186 MockEnvironment env; 187 ASSERT_TRUE(base::CreateDirectory(desktop_path)); 188 ASSERT_FALSE(base::WriteFile( 189 desktop_path.AppendASCII(kTemplateFilename), 190 "", 0)); 191 web_app::ShortcutLocations result = GetExistingShortcutLocations( 192 &env, kProfilePath, kExtensionId, desktop_path); 193 EXPECT_TRUE(result.on_desktop); 194 EXPECT_EQ(web_app::APP_MENU_LOCATION_NONE, 195 result.applications_menu_location); 196 197 EXPECT_FALSE(result.in_quick_launch_bar); 198 } 199 200 // Shortcut in applications directory. 201 { 202 base::ScopedTempDir temp_dir; 203 ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); 204 base::FilePath apps_path = temp_dir.path().AppendASCII("applications"); 205 206 MockEnvironment env; 207 env.Set("XDG_DATA_HOME", temp_dir.path().value()); 208 ASSERT_TRUE(base::CreateDirectory(apps_path)); 209 ASSERT_FALSE(base::WriteFile( 210 apps_path.AppendASCII(kTemplateFilename), 211 "", 0)); 212 web_app::ShortcutLocations result = 213 GetExistingShortcutLocations(&env, kProfilePath, kExtensionId); 214 EXPECT_FALSE(result.on_desktop); 215 EXPECT_EQ(web_app::APP_MENU_LOCATION_SUBDIR_CHROMEAPPS, 216 result.applications_menu_location); 217 218 EXPECT_FALSE(result.in_quick_launch_bar); 219 } 220 221 // Shortcut in applications directory with NoDisplay=true. 222 { 223 base::ScopedTempDir temp_dir; 224 ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); 225 base::FilePath apps_path = temp_dir.path().AppendASCII("applications"); 226 227 MockEnvironment env; 228 env.Set("XDG_DATA_HOME", temp_dir.path().value()); 229 ASSERT_TRUE(base::CreateDirectory(apps_path)); 230 ASSERT_TRUE(base::WriteFile( 231 apps_path.AppendASCII(kTemplateFilename), 232 kNoDisplayDesktopFile, strlen(kNoDisplayDesktopFile))); 233 web_app::ShortcutLocations result = 234 GetExistingShortcutLocations(&env, kProfilePath, kExtensionId); 235 // Doesn't count as being in applications menu. 236 EXPECT_FALSE(result.on_desktop); 237 EXPECT_EQ(web_app::APP_MENU_LOCATION_HIDDEN, 238 result.applications_menu_location); 239 EXPECT_FALSE(result.in_quick_launch_bar); 240 } 241 242 // Shortcut on desktop and in applications directory. 243 { 244 base::ScopedTempDir temp_dir1; 245 ASSERT_TRUE(temp_dir1.CreateUniqueTempDir()); 246 base::FilePath desktop_path = temp_dir1.path(); 247 248 base::ScopedTempDir temp_dir2; 249 ASSERT_TRUE(temp_dir2.CreateUniqueTempDir()); 250 base::FilePath apps_path = temp_dir2.path().AppendASCII("applications"); 251 252 MockEnvironment env; 253 ASSERT_TRUE(base::CreateDirectory(desktop_path)); 254 ASSERT_FALSE(base::WriteFile( 255 desktop_path.AppendASCII(kTemplateFilename), 256 "", 0)); 257 env.Set("XDG_DATA_HOME", temp_dir2.path().value()); 258 ASSERT_TRUE(base::CreateDirectory(apps_path)); 259 ASSERT_FALSE(base::WriteFile( 260 apps_path.AppendASCII(kTemplateFilename), 261 "", 0)); 262 web_app::ShortcutLocations result = GetExistingShortcutLocations( 263 &env, kProfilePath, kExtensionId, desktop_path); 264 EXPECT_TRUE(result.on_desktop); 265 EXPECT_EQ(web_app::APP_MENU_LOCATION_SUBDIR_CHROMEAPPS, 266 result.applications_menu_location); 267 EXPECT_FALSE(result.in_quick_launch_bar); 268 } 269} 270 271TEST(ShellIntegrationTest, GetExistingShortcutContents) { 272 const char kTemplateFilename[] = "shortcut-test.desktop"; 273 base::FilePath kTemplateFilepath(kTemplateFilename); 274 const char kTestData1[] = "a magical testing string"; 275 const char kTestData2[] = "a different testing string"; 276 277 base::MessageLoop message_loop; 278 content::TestBrowserThread file_thread(BrowserThread::FILE, &message_loop); 279 280 // Test that it searches $XDG_DATA_HOME/applications. 281 { 282 base::ScopedTempDir temp_dir; 283 ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); 284 285 MockEnvironment env; 286 env.Set("XDG_DATA_HOME", temp_dir.path().value()); 287 // Create a file in a non-applications directory. This should be ignored. 288 ASSERT_TRUE(base::WriteFile( 289 temp_dir.path().AppendASCII(kTemplateFilename), 290 kTestData2, strlen(kTestData2))); 291 ASSERT_TRUE(base::CreateDirectory( 292 temp_dir.path().AppendASCII("applications"))); 293 ASSERT_TRUE(base::WriteFile( 294 temp_dir.path().AppendASCII("applications") 295 .AppendASCII(kTemplateFilename), 296 kTestData1, strlen(kTestData1))); 297 std::string contents; 298 ASSERT_TRUE( 299 GetExistingShortcutContents(&env, kTemplateFilepath, &contents)); 300 EXPECT_EQ(kTestData1, contents); 301 } 302 303 // Test that it falls back to $HOME/.local/share/applications. 304 { 305 base::ScopedTempDir temp_dir; 306 ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); 307 308 MockEnvironment env; 309 env.Set("HOME", temp_dir.path().value()); 310 ASSERT_TRUE(base::CreateDirectory( 311 temp_dir.path().AppendASCII(".local/share/applications"))); 312 ASSERT_TRUE(base::WriteFile( 313 temp_dir.path().AppendASCII(".local/share/applications") 314 .AppendASCII(kTemplateFilename), 315 kTestData1, strlen(kTestData1))); 316 std::string contents; 317 ASSERT_TRUE( 318 GetExistingShortcutContents(&env, kTemplateFilepath, &contents)); 319 EXPECT_EQ(kTestData1, contents); 320 } 321 322 // Test that it searches $XDG_DATA_DIRS/applications. 323 { 324 base::ScopedTempDir temp_dir; 325 ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); 326 327 MockEnvironment env; 328 env.Set("XDG_DATA_DIRS", temp_dir.path().value()); 329 ASSERT_TRUE(base::CreateDirectory( 330 temp_dir.path().AppendASCII("applications"))); 331 ASSERT_TRUE(base::WriteFile( 332 temp_dir.path().AppendASCII("applications") 333 .AppendASCII(kTemplateFilename), 334 kTestData2, strlen(kTestData2))); 335 std::string contents; 336 ASSERT_TRUE( 337 GetExistingShortcutContents(&env, kTemplateFilepath, &contents)); 338 EXPECT_EQ(kTestData2, contents); 339 } 340 341 // Test that it searches $X/applications for each X in $XDG_DATA_DIRS. 342 { 343 base::ScopedTempDir temp_dir1; 344 ASSERT_TRUE(temp_dir1.CreateUniqueTempDir()); 345 base::ScopedTempDir temp_dir2; 346 ASSERT_TRUE(temp_dir2.CreateUniqueTempDir()); 347 348 MockEnvironment env; 349 env.Set("XDG_DATA_DIRS", temp_dir1.path().value() + ":" + 350 temp_dir2.path().value()); 351 // Create a file in a non-applications directory. This should be ignored. 352 ASSERT_TRUE(base::WriteFile( 353 temp_dir1.path().AppendASCII(kTemplateFilename), 354 kTestData1, strlen(kTestData1))); 355 // Only create a findable desktop file in the second path. 356 ASSERT_TRUE(base::CreateDirectory( 357 temp_dir2.path().AppendASCII("applications"))); 358 ASSERT_TRUE(base::WriteFile( 359 temp_dir2.path().AppendASCII("applications") 360 .AppendASCII(kTemplateFilename), 361 kTestData2, strlen(kTestData2))); 362 std::string contents; 363 ASSERT_TRUE( 364 GetExistingShortcutContents(&env, kTemplateFilepath, &contents)); 365 EXPECT_EQ(kTestData2, contents); 366 } 367} 368 369TEST(ShellIntegrationTest, GetExtensionShortcutFilename) { 370 base::FilePath kProfilePath("a/b/c/Profile Name?"); 371 const char kExtensionId[] = "extensionid"; 372 EXPECT_EQ(base::FilePath("chrome-extensionid-Profile_Name_.desktop"), 373 GetExtensionShortcutFilename(kProfilePath, kExtensionId)); 374} 375 376TEST(ShellIntegrationTest, GetExistingProfileShortcutFilenames) { 377 base::FilePath kProfilePath("a/b/c/Profile Name?"); 378 const char kApp1Filename[] = "chrome-extension1-Profile_Name_.desktop"; 379 const char kApp2Filename[] = "chrome-extension2-Profile_Name_.desktop"; 380 const char kUnrelatedAppFilename[] = "chrome-extension-Other_Profile.desktop"; 381 382 base::MessageLoop message_loop; 383 content::TestBrowserThread file_thread(BrowserThread::FILE, &message_loop); 384 385 base::ScopedTempDir temp_dir; 386 ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); 387 ASSERT_EQ(0, 388 base::WriteFile( 389 temp_dir.path().AppendASCII(kApp1Filename), "", 0)); 390 ASSERT_EQ(0, 391 base::WriteFile( 392 temp_dir.path().AppendASCII(kApp2Filename), "", 0)); 393 // This file should not be returned in the results. 394 ASSERT_EQ(0, 395 base::WriteFile( 396 temp_dir.path().AppendASCII(kUnrelatedAppFilename), "", 0)); 397 std::vector<base::FilePath> paths = 398 GetExistingProfileShortcutFilenames(kProfilePath, temp_dir.path()); 399 // Path order is arbitrary. Sort the output for consistency. 400 std::sort(paths.begin(), paths.end()); 401 EXPECT_THAT(paths, 402 ElementsAre(base::FilePath(kApp1Filename), 403 base::FilePath(kApp2Filename))); 404} 405 406TEST(ShellIntegrationTest, GetWebShortcutFilename) { 407 const struct { 408 const base::FilePath::CharType* path; 409 const char* url; 410 } test_cases[] = { 411 { FPL("http___foo_.desktop"), "http://foo" }, 412 { FPL("http___foo_bar_.desktop"), "http://foo/bar/" }, 413 { FPL("http___foo_bar_a=b&c=d.desktop"), "http://foo/bar?a=b&c=d" }, 414 415 // Now we're starting to be more evil... 416 { FPL("http___foo_.desktop"), "http://foo/bar/baz/../../../../../" }, 417 { FPL("http___foo_.desktop"), "http://foo/bar/././../baz/././../" }, 418 { FPL("http___.._.desktop"), "http://../../../../" }, 419 }; 420 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); i++) { 421 EXPECT_EQ(std::string(chrome::kBrowserProcessExecutableName) + "-" + 422 test_cases[i].path, 423 GetWebShortcutFilename(GURL(test_cases[i].url)).value()) << 424 " while testing " << test_cases[i].url; 425 } 426} 427 428TEST(ShellIntegrationTest, GetDesktopFileContents) { 429 const base::FilePath kChromeExePath("/opt/google/chrome/google-chrome"); 430 const struct { 431 const char* url; 432 const char* title; 433 const char* icon_name; 434 const char* categories; 435 bool nodisplay; 436 const char* expected_output; 437 } test_cases[] = { 438 // Real-world case. 439 { "http://gmail.com", 440 "GMail", 441 "chrome-http__gmail.com", 442 "", 443 false, 444 445 "#!/usr/bin/env xdg-open\n" 446 "[Desktop Entry]\n" 447 "Version=1.0\n" 448 "Terminal=false\n" 449 "Type=Application\n" 450 "Name=GMail\n" 451 "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n" 452 "Icon=chrome-http__gmail.com\n" 453 "StartupWMClass=gmail.com\n" 454 }, 455 456 // Make sure that empty icons are replaced by the chrome icon. 457 { "http://gmail.com", 458 "GMail", 459 "", 460 "", 461 false, 462 463 "#!/usr/bin/env xdg-open\n" 464 "[Desktop Entry]\n" 465 "Version=1.0\n" 466 "Terminal=false\n" 467 "Type=Application\n" 468 "Name=GMail\n" 469 "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n" 470#if defined(GOOGLE_CHROME_BUILD) 471 "Icon=google-chrome\n" 472#else 473 "Icon=chromium-browser\n" 474#endif 475 "StartupWMClass=gmail.com\n" 476 }, 477 478 // Test adding categories and NoDisplay=true. 479 { "http://gmail.com", 480 "GMail", 481 "chrome-http__gmail.com", 482 "Graphics;Education;", 483 true, 484 485 "#!/usr/bin/env xdg-open\n" 486 "[Desktop Entry]\n" 487 "Version=1.0\n" 488 "Terminal=false\n" 489 "Type=Application\n" 490 "Name=GMail\n" 491 "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n" 492 "Icon=chrome-http__gmail.com\n" 493 "Categories=Graphics;Education;\n" 494 "NoDisplay=true\n" 495 "StartupWMClass=gmail.com\n" 496 }, 497 498 // Now we're starting to be more evil... 499 { "http://evil.com/evil --join-the-b0tnet", 500 "Ownz0red\nExec=rm -rf /", 501 "chrome-http__evil.com_evil", 502 "", 503 false, 504 505 "#!/usr/bin/env xdg-open\n" 506 "[Desktop Entry]\n" 507 "Version=1.0\n" 508 "Terminal=false\n" 509 "Type=Application\n" 510 "Name=http://evil.com/evil%20--join-the-b0tnet\n" 511 "Exec=/opt/google/chrome/google-chrome " 512 "--app=http://evil.com/evil%20--join-the-b0tnet\n" 513 "Icon=chrome-http__evil.com_evil\n" 514 "StartupWMClass=evil.com__evil%20--join-the-b0tnet\n" 515 }, 516 { "http://evil.com/evil; rm -rf /; \"; rm -rf $HOME >ownz0red", 517 "Innocent Title", 518 "chrome-http__evil.com_evil", 519 "", 520 false, 521 522 "#!/usr/bin/env xdg-open\n" 523 "[Desktop Entry]\n" 524 "Version=1.0\n" 525 "Terminal=false\n" 526 "Type=Application\n" 527 "Name=Innocent Title\n" 528 "Exec=/opt/google/chrome/google-chrome " 529 "\"--app=http://evil.com/evil;%20rm%20-rf%20/;%20%22;%20rm%20" 530 // Note: $ is escaped as \$ within an arg to Exec, and then 531 // the \ is escaped as \\ as all strings in a Desktop file should 532 // be; finally, \\ becomes \\\\ when represented in a C++ string! 533 "-rf%20\\\\$HOME%20%3Eownz0red\"\n" 534 "Icon=chrome-http__evil.com_evil\n" 535 "StartupWMClass=evil.com__evil;%20rm%20-rf%20_;%20%22;%20" 536 "rm%20-rf%20$HOME%20%3Eownz0red\n" 537 }, 538 { "http://evil.com/evil | cat `echo ownz0red` >/dev/null", 539 "Innocent Title", 540 "chrome-http__evil.com_evil", 541 "", 542 false, 543 544 "#!/usr/bin/env xdg-open\n" 545 "[Desktop Entry]\n" 546 "Version=1.0\n" 547 "Terminal=false\n" 548 "Type=Application\n" 549 "Name=Innocent Title\n" 550 "Exec=/opt/google/chrome/google-chrome " 551 "--app=http://evil.com/evil%20%7C%20cat%20%60echo%20ownz0red" 552 "%60%20%3E/dev/null\n" 553 "Icon=chrome-http__evil.com_evil\n" 554 "StartupWMClass=evil.com__evil%20%7C%20cat%20%60echo%20ownz0red" 555 "%60%20%3E_dev_null\n" 556 }, 557 }; 558 559 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); i++) { 560 SCOPED_TRACE(i); 561 EXPECT_EQ( 562 test_cases[i].expected_output, 563 GetDesktopFileContents( 564 kChromeExePath, 565 web_app::GenerateApplicationNameFromURL(GURL(test_cases[i].url)), 566 GURL(test_cases[i].url), 567 std::string(), 568 base::ASCIIToUTF16(test_cases[i].title), 569 test_cases[i].icon_name, 570 base::FilePath(), 571 test_cases[i].categories, 572 test_cases[i].nodisplay)); 573 } 574} 575 576TEST(ShellIntegrationTest, GetDesktopFileContentsAppList) { 577 const base::FilePath kChromeExePath("/opt/google/chrome/google-chrome"); 578 base::CommandLine command_line(kChromeExePath); 579 command_line.AppendSwitch("--show-app-list"); 580 EXPECT_EQ( 581 "#!/usr/bin/env xdg-open\n" 582 "[Desktop Entry]\n" 583 "Version=1.0\n" 584 "Terminal=false\n" 585 "Type=Application\n" 586 "Name=Chrome App Launcher\n" 587 "Exec=/opt/google/chrome/google-chrome --show-app-list\n" 588 "Icon=chrome_app_list\n" 589 "Categories=Network;WebBrowser;\n" 590 "StartupWMClass=chrome-app-list\n", 591 GetDesktopFileContentsForCommand( 592 command_line, 593 "chrome-app-list", 594 GURL(), 595 base::ASCIIToUTF16("Chrome App Launcher"), 596 "chrome_app_list", 597 "Network;WebBrowser;", 598 false)); 599} 600 601TEST(ShellIntegrationTest, GetDirectoryFileContents) { 602 const struct { 603 const char* title; 604 const char* icon_name; 605 const char* expected_output; 606 } test_cases[] = { 607 // Real-world case. 608 { "Chrome Apps", 609 "chrome-apps", 610 611 "[Desktop Entry]\n" 612 "Version=1.0\n" 613 "Type=Directory\n" 614 "Name=Chrome Apps\n" 615 "Icon=chrome-apps\n" 616 }, 617 618 // Make sure that empty icons are replaced by the chrome icon. 619 { "Chrome Apps", 620 "", 621 622 "[Desktop Entry]\n" 623 "Version=1.0\n" 624 "Type=Directory\n" 625 "Name=Chrome Apps\n" 626#if defined(GOOGLE_CHROME_BUILD) 627 "Icon=google-chrome\n" 628#else 629 "Icon=chromium-browser\n" 630#endif 631 }, 632 }; 633 634 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); i++) { 635 SCOPED_TRACE(i); 636 EXPECT_EQ(test_cases[i].expected_output, 637 GetDirectoryFileContents(base::ASCIIToUTF16(test_cases[i].title), 638 test_cases[i].icon_name)); 639 } 640} 641 642} // namespace shell_integration_linux 643