1 // Copyright 2013 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/shell_integration_linux.h"
14 #include "base/base_paths.h"
15 #include "base/command_line.h"
16 #include "base/containers/contains.h"
17 #include "base/environment.h"
18 #include "base/files/file_path.h"
19 #include "base/files/file_util.h"
20 #include "base/files/scoped_temp_dir.h"
21 #include "base/strings/string_piece.h"
22 #include "base/strings/string_util.h"
23 #include "base/strings/utf_string_conversions.h"
24 #include "base/test/scoped_path_override.h"
25 #include "build/branding_buildflags.h"
26 #include "chrome/browser/web_applications/os_integration/web_app_shortcut.h"
27 #include "chrome/browser/web_applications/web_app_helpers.h"
28 #include "chrome/common/chrome_constants.h"
29 #include "components/services/app_service/public/cpp/file_handler.h"
30 #include "components/webapps/common/web_app_id.h"
31 #include "content/public/test/browser_task_environment.h"
32 #include "testing/gmock/include/gmock/gmock.h"
33 #include "testing/gtest/include/gtest/gtest.h"
34 #include "ui/ozone/public/ozone_platform.h"
37 using ::testing::ElementsAre;
39 namespace shell_integration_linux {
43 // Provides mock environment variables values based on a stored map.
44 class MockEnvironment : public base::Environment {
48 MockEnvironment(const MockEnvironment&) = delete;
49 MockEnvironment& operator=(const MockEnvironment&) = delete;
51 void Set(base::StringPiece name, const std::string& value) {
52 variables_[std::string(name)] = value;
55 bool GetVar(base::StringPiece variable_name, std::string* result) override {
56 if (base::Contains(variables_, std::string(variable_name))) {
57 *result = variables_[std::string(variable_name)];
64 bool SetVar(base::StringPiece variable_name,
65 const std::string& new_value) override {
70 bool UnSetVar(base::StringPiece variable_name) override {
76 std::map<std::string, std::string> variables_;
79 // This helps EXPECT_THAT(..., ElementsAre(...)) print out more meaningful
81 std::vector<std::string> FilePathsToStrings(
82 const std::vector<base::FilePath>& paths) {
83 std::vector<std::string> values;
84 for (const auto& path : paths)
85 values.push_back(path.value());
91 TEST(ShellIntegrationTest, GetDataWriteLocation) {
92 content::BrowserTaskEnvironment task_environment;
94 // Test that it returns $XDG_DATA_HOME.
97 base::ScopedPathOverride home_override(base::DIR_HOME,
98 base::FilePath("/home/user"),
100 false /* create? */);
101 env.Set("XDG_DATA_HOME", "/user/path");
102 base::FilePath path = GetDataWriteLocation(&env);
103 EXPECT_EQ("/user/path", path.value());
106 // Test that $XDG_DATA_HOME falls back to $HOME/.local/share.
109 base::ScopedPathOverride home_override(base::DIR_HOME,
110 base::FilePath("/home/user"),
111 true /* absolute? */,
112 false /* create? */);
113 base::FilePath path = GetDataWriteLocation(&env);
114 EXPECT_EQ("/home/user/.local/share", path.value());
118 TEST(ShellIntegrationTest, GetDataSearchLocations) {
119 content::BrowserTaskEnvironment task_environment;
121 // Test that it returns $XDG_DATA_HOME + $XDG_DATA_DIRS.
124 base::ScopedPathOverride home_override(base::DIR_HOME,
125 base::FilePath("/home/user"),
126 true /* absolute? */,
127 false /* create? */);
128 env.Set("XDG_DATA_HOME", "/user/path");
129 env.Set("XDG_DATA_DIRS", "/system/path/1:/system/path/2");
131 FilePathsToStrings(GetDataSearchLocations(&env)),
132 ElementsAre("/user/path",
137 // Test that $XDG_DATA_HOME falls back to $HOME/.local/share.
140 base::ScopedPathOverride home_override(base::DIR_HOME,
141 base::FilePath("/home/user"),
142 true /* absolute? */,
143 false /* create? */);
144 env.Set("XDG_DATA_DIRS", "/system/path/1:/system/path/2");
146 FilePathsToStrings(GetDataSearchLocations(&env)),
147 ElementsAre("/home/user/.local/share",
152 // Test that if neither $XDG_DATA_HOME nor $HOME are specified, it still
156 env.Set("XDG_DATA_DIRS", "/system/path/1:/system/path/2");
157 std::vector<std::string> results =
158 FilePathsToStrings(GetDataSearchLocations(&env));
159 ASSERT_EQ(3U, results.size());
160 EXPECT_FALSE(results[0].empty());
161 EXPECT_EQ("/system/path/1", results[1]);
162 EXPECT_EQ("/system/path/2", results[2]);
165 // Test that $XDG_DATA_DIRS falls back to the two default paths.
168 base::ScopedPathOverride home_override(base::DIR_HOME,
169 base::FilePath("/home/user"),
170 true /* absolute? */,
171 false /* create? */);
172 env.Set("XDG_DATA_HOME", "/user/path");
174 FilePathsToStrings(GetDataSearchLocations(&env)),
175 ElementsAre("/user/path",
181 TEST(ShellIntegrationTest, GetExistingShortcutContents) {
182 const char kTemplateFilename[] = "shortcut-test.desktop";
183 base::FilePath kTemplateFilepath(kTemplateFilename);
184 const char kTestData1[] = "a magical testing string";
185 const char kTestData2[] = "a different testing string";
187 content::BrowserTaskEnvironment task_environment;
189 // Test that it searches $XDG_DATA_HOME/applications.
191 base::ScopedTempDir temp_dir;
192 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
195 env.Set("XDG_DATA_HOME", temp_dir.GetPath().value());
196 // Create a file in a non-applications directory. This should be ignored.
197 ASSERT_TRUE(base::WriteFile(temp_dir.GetPath().Append(kTemplateFilename),
200 base::CreateDirectory(temp_dir.GetPath().Append("applications")));
201 ASSERT_TRUE(base::WriteFile(
202 temp_dir.GetPath().Append("applications").Append(kTemplateFilename),
204 std::string contents;
206 GetExistingShortcutContents(&env, kTemplateFilepath, &contents));
207 EXPECT_EQ(kTestData1, contents);
210 // Test that it falls back to $HOME/.local/share/applications.
212 base::ScopedTempDir temp_dir;
213 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
216 base::ScopedPathOverride home_override(base::DIR_HOME, temp_dir.GetPath(),
217 true /* absolute? */,
218 false /* create? */);
219 ASSERT_TRUE(base::CreateDirectory(
220 temp_dir.GetPath().Append(".local/share/applications")));
221 ASSERT_TRUE(base::WriteFile(temp_dir.GetPath()
222 .Append(".local/share/applications")
223 .Append(kTemplateFilename),
225 std::string contents;
227 GetExistingShortcutContents(&env, kTemplateFilepath, &contents));
228 EXPECT_EQ(kTestData1, contents);
231 // Test that it searches $XDG_DATA_DIRS/applications.
233 base::ScopedTempDir temp_dir;
234 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
237 env.Set("XDG_DATA_DIRS", temp_dir.GetPath().value());
239 base::CreateDirectory(temp_dir.GetPath().Append("applications")));
240 ASSERT_TRUE(base::WriteFile(
241 temp_dir.GetPath().Append("applications").Append(kTemplateFilename),
243 std::string contents;
245 GetExistingShortcutContents(&env, kTemplateFilepath, &contents));
246 EXPECT_EQ(kTestData2, contents);
249 // Test that it searches $X/applications for each X in $XDG_DATA_DIRS.
251 base::ScopedTempDir temp_dir1;
252 ASSERT_TRUE(temp_dir1.CreateUniqueTempDir());
253 base::ScopedTempDir temp_dir2;
254 ASSERT_TRUE(temp_dir2.CreateUniqueTempDir());
257 env.Set("XDG_DATA_DIRS",
258 temp_dir1.GetPath().value() + ":" + temp_dir2.GetPath().value());
259 // Create a file in a non-applications directory. This should be ignored.
260 ASSERT_TRUE(base::WriteFile(temp_dir1.GetPath().Append(kTemplateFilename),
262 // Only create a findable desktop file in the second path.
264 base::CreateDirectory(temp_dir2.GetPath().Append("applications")));
265 ASSERT_TRUE(base::WriteFile(
266 temp_dir2.GetPath().Append("applications").Append(kTemplateFilename),
268 std::string contents;
270 GetExistingShortcutContents(&env, kTemplateFilepath, &contents));
271 EXPECT_EQ(kTestData2, contents);
275 TEST(ShellIntegrationTest, GetExistingProfileShortcutFilenames) {
276 base::FilePath kProfilePath("a/b/c/Profile Name?");
277 const char kApp1Filename[] = "chrome-extension1-Profile_Name_.desktop";
278 const char kApp2Filename[] = "chrome-extension2-Profile_Name_.desktop";
279 const char kUnrelatedAppFilename[] = "chrome-extension-Other_Profile.desktop";
281 content::BrowserTaskEnvironment task_environment;
283 base::ScopedTempDir temp_dir;
284 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
285 ASSERT_TRUE(base::WriteFile(temp_dir.GetPath().Append(kApp1Filename), ""));
286 ASSERT_TRUE(base::WriteFile(temp_dir.GetPath().Append(kApp2Filename), ""));
287 // This file should not be returned in the results.
289 base::WriteFile(temp_dir.GetPath().Append(kUnrelatedAppFilename), ""));
290 std::vector<base::FilePath> paths =
291 GetExistingProfileShortcutFilenames(kProfilePath, temp_dir.GetPath());
292 // Path order is arbitrary. Sort the output for consistency.
293 std::sort(paths.begin(), paths.end());
295 ElementsAre(base::FilePath(kApp1Filename),
296 base::FilePath(kApp2Filename)));
299 TEST(ShellIntegrationTest, GetWebShortcutFilename) {
301 const char* const path;
302 const char* const url;
304 { "http___foo_.desktop", "http://foo" },
305 { "http___foo_bar_.desktop", "http://foo/bar/" },
306 { "http___foo_bar_a=b&c=d.desktop", "http://foo/bar?a=b&c=d" },
308 // Now we're starting to be more evil...
309 { "http___foo_.desktop", "http://foo/bar/baz/../../../../../" },
310 { "http___foo_.desktop", "http://foo/bar/././../baz/././../" },
311 { "http___.._.desktop", "http://../../../../" },
313 for (size_t i = 0; i < std::size(test_cases); i++) {
314 EXPECT_EQ(std::string(chrome::kBrowserProcessExecutableName) + "-" +
316 GetWebShortcutFilename(GURL(test_cases[i].url)).value()) <<
317 " while testing " << test_cases[i].url;
321 TEST(ShellIntegrationTest, GetDesktopFileContents) {
322 const base::FilePath kChromeExePath("/opt/google/chrome/google-chrome");
324 const char* const url;
325 const char* const title;
326 const char* const icon_name;
327 const char* const categories;
328 const char* const mime_type;
330 const char* const expected_output;
333 {"http://gmail.com", "GMail", "chrome-http__gmail.com", "", "", false,
335 "#!/usr/bin/env xdg-open\n"
341 "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n"
342 "Icon=chrome-http__gmail.com\n"
343 "StartupWMClass=gmail.com\n"},
345 // Make sure that empty icons are replaced by the chrome icon.
346 {"http://gmail.com", "GMail", "", "", "", false,
348 "#!/usr/bin/env xdg-open\n"
354 "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n"
355 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
356 "Icon=google-chrome\n"
358 "Icon=chromium-browser\n"
360 "StartupWMClass=gmail.com\n"},
362 // Test adding categories and NoDisplay=true.
363 {"http://gmail.com", "GMail", "chrome-http__gmail.com",
364 "Graphics;Education;", "", true,
366 "#!/usr/bin/env xdg-open\n"
372 "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n"
373 "Icon=chrome-http__gmail.com\n"
374 "Categories=Graphics;Education;\n"
376 "StartupWMClass=gmail.com\n"},
378 // Now we're starting to be more evil...
379 {"http://evil.com/evil --join-the-b0tnet", "Ownz0red\nExec=rm -rf /",
380 "chrome-http__evil.com_evil", "", "", false,
382 "#!/usr/bin/env xdg-open\n"
387 "Name=http://evil.com/evil%20--join-the-b0tnet\n"
388 "Exec=/opt/google/chrome/google-chrome "
389 "--app=http://evil.com/evil%20--join-the-b0tnet\n"
390 "Icon=chrome-http__evil.com_evil\n"
391 "StartupWMClass=evil.com__evil%20--join-the-b0tnet\n"},
392 {"http://evil.com/evil; rm -rf /; \"; rm -rf $HOME >ownz0red",
393 "Innocent Title", "chrome-http__evil.com_evil", "", "", false,
395 "#!/usr/bin/env xdg-open\n"
400 "Name=Innocent Title\n"
401 "Exec=/opt/google/chrome/google-chrome "
402 "\"--app=http://evil.com/evil;%20rm%20-rf%20/;%20%22;%20rm%20"
403 // Note: $ is escaped as \$ within an arg to Exec, and then
404 // the \ is escaped as \\ as all strings in a Desktop file should
405 // be; finally, \\ becomes \\\\ when represented in a C++ string!
406 "-rf%20\\\\$HOME%20%3Eownz0red\"\n"
407 "Icon=chrome-http__evil.com_evil\n"
408 "StartupWMClass=evil.com__evil;%20rm%20-rf%20_;%20%22;%20"
409 "rm%20-rf%20$HOME%20%3Eownz0red\n"},
410 {"http://evil.com/evil | cat `echo ownz0red` >/dev/null",
411 "Innocent Title", "chrome-http__evil.com_evil", "", "", false,
413 "#!/usr/bin/env xdg-open\n"
418 "Name=Innocent Title\n"
419 "Exec=/opt/google/chrome/google-chrome "
420 "--app=http://evil.com/evil%20%7C%20cat%20%60echo%20ownz0red"
421 "%60%20%3E/dev/null\n"
422 "Icon=chrome-http__evil.com_evil\n"
423 "StartupWMClass=evil.com__evil%20%7C%20cat%20%60echo%20ownz0red"
424 "%60%20%3E_dev_null\n"},
425 // Test setting mime type
426 {"https://paint.app", "Paint", "chrome-https__paint.app", "Image",
427 "image/png;image/jpg", false,
429 "#!/usr/bin/env xdg-open\n"
435 "MimeType=image/png;image/jpg\n"
436 "Exec=/opt/google/chrome/google-chrome --app=https://paint.app/ %U\n"
437 "Icon=chrome-https__paint.app\n"
439 "StartupWMClass=paint.app\n"},
441 // Test evil mime type.
442 {"https://paint.app", "Evil Paint", "chrome-https__paint.app", "Image",
443 "image/png\nExec=rm -rf /", false,
445 "#!/usr/bin/env xdg-open\n"
451 "Exec=/opt/google/chrome/google-chrome --app=https://paint.app/\n"
452 "Icon=chrome-https__paint.app\n"
454 "StartupWMClass=paint.app\n"}};
456 for (size_t i = 0; i < std::size(test_cases); i++) {
459 test_cases[i].expected_output,
460 GetDesktopFileContents(
462 web_app::GenerateApplicationNameFromURL(GURL(test_cases[i].url)),
463 GURL(test_cases[i].url), std::string(),
464 base::ASCIIToUTF16(test_cases[i].title), test_cases[i].icon_name,
465 base::FilePath(), test_cases[i].categories, test_cases[i].mime_type,
466 test_cases[i].nodisplay, "", {}));
470 TEST(ShellIntegrationTest, GetDesktopFileContentsForApps) {
471 const base::FilePath kChromeExePath("/opt/google/chrome/google-chrome");
473 const char* const url;
474 const char* const title;
475 const char* const icon_name;
477 std::set<web_app::DesktopActionInfo> action_info;
478 const char* const expected_output;
480 // Test Shortcut Menu actions.
481 {"https://example.app",
486 web_app::DesktopActionInfo("action1", "Action 1",
487 GURL("https://example.com/action1")),
488 web_app::DesktopActionInfo("action2", "Action 2",
489 GURL("https://example.com/action2")),
490 web_app::DesktopActionInfo("action3", "Action 3",
491 GURL("https://example.com/action3")),
492 web_app::DesktopActionInfo("action4", "Action 4",
493 GURL("https://example.com/action4")),
494 web_app::DesktopActionInfo("action5", "Action 5",
495 GURL("https://example.com/action%205")),
498 "#!/usr/bin/env xdg-open\n"
503 "Name=Lawful example\n"
504 "Exec=/opt/google/chrome/google-chrome --app-id=TestAppId\n"
506 "StartupWMClass=example.app\n"
507 "Actions=action1;action2;action3;action4;action5\n\n"
508 "[Desktop Action action1]\n"
510 "Exec=/opt/google/chrome/google-chrome --app-id=TestAppId "
511 "--app-launch-url-for-shortcuts-menu-item=https://example.com/"
513 "[Desktop Action action2]\n"
515 "Exec=/opt/google/chrome/google-chrome --app-id=TestAppId "
516 "--app-launch-url-for-shortcuts-menu-item=https://example.com/"
518 "[Desktop Action action3]\n"
520 "Exec=/opt/google/chrome/google-chrome --app-id=TestAppId "
521 "--app-launch-url-for-shortcuts-menu-item=https://example.com/"
523 "[Desktop Action action4]\n"
525 "Exec=/opt/google/chrome/google-chrome --app-id=TestAppId "
526 "--app-launch-url-for-shortcuts-menu-item=https://example.com/"
528 "[Desktop Action action5]\n"
530 "Exec=/opt/google/chrome/google-chrome --app-id=TestAppId "
531 "--app-launch-url-for-shortcuts-menu-item=https://example.com/"
535 for (size_t i = 0; i < std::size(test_cases); i++) {
538 test_cases[i].expected_output,
539 GetDesktopFileContents(
541 web_app::GenerateApplicationNameFromURL(GURL(test_cases[i].url)),
542 GURL(test_cases[i].url), "TestAppId",
543 base::ASCIIToUTF16(test_cases[i].title), test_cases[i].icon_name,
544 base::FilePath(), "", "", test_cases[i].nodisplay, "",
545 test_cases[i].action_info));
549 TEST(ShellIntegrationTest, GetDirectoryFileContents) {
551 const char* const title;
552 const char* const icon_name;
553 const char* const expected_output;
556 {"Chrome Apps", "chrome-apps",
562 "Icon=chrome-apps\n"},
564 // Make sure that empty icons are replaced by the chrome icon.
571 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
572 "Icon=google-chrome\n"
574 "Icon=chromium-browser\n"
579 for (size_t i = 0; i < std::size(test_cases); i++) {
581 EXPECT_EQ(test_cases[i].expected_output,
582 GetDirectoryFileContents(base::ASCIIToUTF16(test_cases[i].title),
583 test_cases[i].icon_name));
587 TEST(ShellIntegrationTest, GetMimeTypesRegistrationFilename) {
589 const char* const profile_path;
590 const char* const app_id;
591 const char* const expected_filename;
593 {"Default", "app-id", "-app-id-Default.xml"},
594 {"Default Profile", "app-id", "-app-id-Default_Profile.xml"},
595 {"foo/Default", "app-id", "-app-id-Default.xml"},
596 {"Default*Profile", "app-id", "-app-id-Default_Profile.xml"}};
597 std::string browser_name(chrome::kBrowserProcessExecutableName);
599 for (const auto& test_case : test_cases) {
600 const base::FilePath filename =
601 GetMimeTypesRegistrationFilename(base::FilePath(test_case.profile_path),
602 webapps::AppId(test_case.app_id));
603 EXPECT_EQ(browser_name + test_case.expected_filename, filename.value());
607 TEST(ShellIntegrationTest, GetMimeTypesRegistrationFileContents) {
608 apps::FileHandlers file_handlers;
610 apps::FileHandler file_handler;
612 apps::FileHandler::AcceptEntry accept_entry;
613 accept_entry.mime_type = "application/foo";
614 accept_entry.file_extensions.insert(".foo");
615 file_handler.accept.push_back(accept_entry);
617 file_handler.display_name = u"FoO";
618 file_handlers.push_back(file_handler);
621 apps::FileHandler file_handler;
623 apps::FileHandler::AcceptEntry accept_entry;
624 accept_entry.mime_type = "application/foobar";
625 accept_entry.file_extensions.insert(".foobar");
626 file_handler.accept.push_back(accept_entry);
628 file_handlers.push_back(file_handler);
631 apps::FileHandler file_handler;
633 apps::FileHandler::AcceptEntry accept_entry;
634 accept_entry.mime_type = "application/bar";
635 // A name that has a reserved XML character.
636 file_handler.display_name = u"ba<r";
637 accept_entry.file_extensions.insert(".bar");
638 accept_entry.file_extensions.insert(".baz");
639 file_handler.accept.push_back(accept_entry);
641 file_handlers.push_back(file_handler);
644 const std::string file_contents =
645 GetMimeTypesRegistrationFileContents(file_handlers);
646 const std::string expected_file_contents =
647 "<?xml version=\"1.0\"?>\n"
649 "xmlns=\"http://www.freedesktop.org/standards/shared-mime-info\">\n"
650 " <mime-type type=\"application/foo\">\n"
651 " <comment>FoO</comment>\n"
652 " <glob pattern=\"*.foo\"/>\n"
654 " <mime-type type=\"application/foobar\">\n"
655 " <glob pattern=\"*.foobar\"/>\n"
657 " <mime-type type=\"application/bar\">\n"
658 " <comment>ba<r</comment>\n"
659 " <glob pattern=\"*.bar\"/>\n"
660 " <glob pattern=\"*.baz\"/>\n"
664 EXPECT_EQ(file_contents, expected_file_contents);
667 // The WM class name may be either capitalised or not, depending on the
669 void CheckProgramClassClass(const std::string& class_name) {
670 if (ui::OzonePlatform::GetPlatformNameForTest() == "x11") {
671 EXPECT_EQ("Foo", class_name);
673 EXPECT_EQ("foo", class_name);
677 TEST(ShellIntegrationTest, WmClass) {
678 base::CommandLine command_line((base::FilePath()));
679 EXPECT_EQ("foo", internal::GetProgramClassName(command_line, "foo.desktop"));
680 CheckProgramClassClass(
681 internal::GetProgramClassClass(command_line, "foo.desktop"));
683 command_line.AppendSwitchASCII("class", "baR");
684 EXPECT_EQ("foo", internal::GetProgramClassName(command_line, "foo.desktop"));
685 EXPECT_EQ("baR", internal::GetProgramClassClass(command_line, "foo.desktop"));
687 command_line = base::CommandLine(base::FilePath());
688 command_line.AppendSwitchASCII("user-data-dir", "/tmp/baz");
689 EXPECT_EQ("foo (/tmp/baz)",
690 internal::GetProgramClassName(command_line, "foo.desktop"));
691 CheckProgramClassClass(
692 internal::GetProgramClassClass(command_line, "foo.desktop"));
695 TEST(ShellIntegrationTest, GetDesktopEntryStringValueFromFromDesktopFile) {
696 const char* const kDesktopFileContents =
697 "#!/usr/bin/env xdg-open\n"
702 "Name=Lawful example\n"
703 "Exec=/opt/google/chrome/google-chrome --app-id=TestAppId\n"
705 "StartupWMClass=example.app\n"
706 "Actions=action1\n\n"
707 "[Desktop Action action1]\n"
709 "Exec=/opt/google/chrome/google-chrome --app-id=TestAppId --Test"
712 // Verify basic strings return the right value.
713 EXPECT_EQ("Lawful example",
714 shell_integration_linux::internal::
715 GetDesktopEntryStringValueFromFromDesktopFileForTest(
716 "Name", kDesktopFileContents));
717 EXPECT_EQ("example.app",
718 shell_integration_linux::internal::
719 GetDesktopEntryStringValueFromFromDesktopFileForTest(
720 "StartupWMClass", kDesktopFileContents));
721 // Verify that booleans are returned correctly.
722 EXPECT_EQ("false", shell_integration_linux::internal::
723 GetDesktopEntryStringValueFromFromDesktopFileForTest(
724 "Terminal", kDesktopFileContents));
725 // Verify that numbers are returned correctly.
726 EXPECT_EQ("1.0", shell_integration_linux::internal::
727 GetDesktopEntryStringValueFromFromDesktopFileForTest(
728 "Version", kDesktopFileContents));
729 // Verify that a non-existent key returns an empty string.
730 EXPECT_EQ("", shell_integration_linux::internal::
731 GetDesktopEntryStringValueFromFromDesktopFileForTest(
732 "DoesNotExistKey", kDesktopFileContents));
733 // Verify that a non-existent key in [Desktop Entry] section returns an empty
735 EXPECT_EQ("", shell_integration_linux::internal::
736 GetDesktopEntryStringValueFromFromDesktopFileForTest(
737 "Action1", kDesktopFileContents));
740 } // namespace shell_integration_linux