// Copyright (c) 2022 The Chromium Authors and Alex313031. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/shell_integration_linux.h" #include #include #include #include #include #include #include #include #include #include #include #include "base/base_paths.h" #include "base/command_line.h" #include "base/environment.h" #include "base/files/file_enumerator.h" #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/i18n/file_util_icu.h" #include "base/logging.h" #include "base/memory/ref_counted_memory.h" #include "base/nix/xdg_util.h" #include "base/path_service.h" #include "base/posix/eintr_wrapper.h" #include "base/process/kill.h" #include "base/process/launch.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_tokenizer.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "base/threading/scoped_blocking_call.h" #include "base/threading/thread.h" #include "base/threading/thread_restrictions.h" #include "build/branding_buildflags.h" #include "build/build_config.h" #include "build/chromeos_buildflags.h" #include "chrome/browser/shell_integration.h" #include "chrome/browser/web_applications/os_integration/web_app_shortcut.h" #include "chrome/browser/web_applications/web_app_helpers.h" #include "chrome/common/buildflags.h" #include "chrome/common/channel_info.h" #include "chrome/common/chrome_constants.h" #include "chrome/common/chrome_switches.h" #include "chrome/grit/chrome_unscaled_resources.h" #include "components/version_info/version_info.h" #include "third_party/libxml/chromium/xml_writer.h" #include "third_party/re2/src/re2/re2.h" #include "ui/base/resource/resource_bundle.h" #include "ui/gfx/image/image_family.h" #include "ui/ozone/public/ozone_platform.h" #include "ui/ozone/public/platform_utils.h" #include "url/gurl.h" #if defined(USE_GLIB) #include #endif namespace shell_integration_linux { const char kXdgSettings[] = "xdg-settings"; const char kXdgSettingsDefaultBrowser[] = "default-web-browser"; const char kXdgSettingsDefaultSchemeHandler[] = "default-url-scheme-handler"; // Utility function to get the path to the version of a script shipped with // Chrome. |script| gives the name of the script. |chrome_version| returns the // path to the Chrome version of the script, and the return value of the // function is true if the function is successful and the Chrome version is // not the script found on the PATH. bool GetChromeVersionOfScript(const std::string& script, std::string* chrome_version) { // Get the path to the Chrome version. base::FilePath chrome_dir; if (!base::PathService::Get(base::DIR_EXE, &chrome_dir)) return false; base::FilePath chrome_version_path = chrome_dir.Append(script); *chrome_version = chrome_version_path.value(); // Check if this is different to the one on path. std::vector argv; argv.push_back("which"); argv.push_back(script); std::string path_version; if (base::GetAppOutput(base::CommandLine(argv), &path_version)) { // Remove trailing newline path_version.erase(path_version.length() - 1, 1); base::FilePath path_version_path(path_version); return (chrome_version_path != path_version_path); } return false; } // Value returned by xdg-settings if it can't understand our request. const int EXIT_XDG_SETTINGS_SYNTAX_ERROR = 1; // We delegate the difficulty of setting the default browser and default url // scheme handler in Linux desktop environments to an xdg utility, xdg-settings. // When calling this script we first try to use the script on PATH. If that // fails we then try to use the script that we have included. This gives // scripts on the system priority over ours, as distribution vendors may have // tweaked the script, but still allows our copy to be used if the script on the // system fails, as the system copy may be missing capabilities of the Chrome // copy. // If |protocol| is empty this function sets Chrome as the default browser, // otherwise it sets Chrome as the default handler application for |protocol|. bool SetDefaultWebClient(const std::string& protocol) { #if BUILDFLAG(IS_CHROMEOS_ASH) return true; #else std::unique_ptr env(base::Environment::Create()); std::vector argv; argv.push_back(kXdgSettings); argv.push_back("set"); if (protocol.empty()) { argv.push_back(kXdgSettingsDefaultBrowser); } else { argv.push_back(kXdgSettingsDefaultSchemeHandler); argv.push_back(protocol); } argv.push_back(chrome::GetDesktopName(env.get())); int exit_code; bool ran_ok = LaunchXdgUtility(argv, &exit_code); if (ran_ok && exit_code == EXIT_XDG_SETTINGS_SYNTAX_ERROR) { if (GetChromeVersionOfScript(kXdgSettings, &argv[0])) { ran_ok = LaunchXdgUtility(argv, &exit_code); } } return ran_ok && exit_code == EXIT_SUCCESS; #endif } // If |protocol| is empty this function checks if Chrome is the default browser, // otherwise it checks if Chrome is the default handler application for // |protocol|. shell_integration::DefaultWebClientState GetIsDefaultWebClient( const std::string& protocol) { #if BUILDFLAG(IS_CHROMEOS_ASH) return shell_integration::UNKNOWN_DEFAULT; #else base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, base::BlockingType::MAY_BLOCK); std::unique_ptr env(base::Environment::Create()); std::vector argv; argv.push_back(kXdgSettings); argv.push_back("check"); if (protocol.empty()) { argv.push_back(kXdgSettingsDefaultBrowser); } else { argv.push_back(kXdgSettingsDefaultSchemeHandler); argv.push_back(protocol); } argv.push_back(chrome::GetDesktopName(env.get())); std::string reply; int success_code; bool ran_ok = base::GetAppOutputWithExitCode(base::CommandLine(argv), &reply, &success_code); if (ran_ok && success_code == EXIT_XDG_SETTINGS_SYNTAX_ERROR) { if (GetChromeVersionOfScript(kXdgSettings, &argv[0])) { ran_ok = base::GetAppOutputWithExitCode(base::CommandLine(argv), &reply, &success_code); } } if (!ran_ok || success_code != EXIT_SUCCESS) { // xdg-settings failed: we can't determine or set the default browser. return shell_integration::UNKNOWN_DEFAULT; } // Allow any reply that starts with "yes". return base::StartsWith(reply, "yes", base::CompareCase::SENSITIVE) ? shell_integration::IS_DEFAULT : shell_integration::NOT_DEFAULT; #endif } // https://wiki.gnome.org/Projects/GnomeShell/ApplicationBased // The WM_CLASS property should be set to the same as the *.desktop file without // the .desktop extension. We cannot simply use argv[0] in this case, because // on the stable channel, the executable name is google-chrome-stable, but the // desktop file is google-chrome.desktop. std::string GetDesktopBaseName(const std::string& desktop_file_name) { static const char kDesktopExtension[] = ".desktop"; if (base::EndsWith(desktop_file_name, kDesktopExtension, base::CompareCase::SENSITIVE)) { return desktop_file_name.substr( 0, desktop_file_name.length() - strlen(kDesktopExtension)); } return desktop_file_name; } namespace { #if defined(USE_GLIB) // Quote a string such that it appears as one verbatim argument for the Exec // key in a desktop file. std::string QuoteArgForDesktopFileExec(const std::string& arg) { // http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html // Quoting is only necessary if the argument has a reserved character. if (arg.find_first_of(" \t\n\"'\\><~|&;$*?#()`") == std::string::npos) return arg; // No quoting necessary. std::string quoted = "\""; for (size_t i = 0; i < arg.size(); ++i) { // Note that the set of backslashed characters is smaller than the // set of reserved characters. switch (arg[i]) { case '"': case '`': case '$': case '\\': quoted += '\\'; break; } quoted += arg[i]; } quoted += '"'; return quoted; } // Quote a command line so it is suitable for use as the Exec key in a desktop // file. Note: This should be used instead of GetCommandLineString, which does // not properly quote the string; this function is designed for the Exec key. std::string QuoteCommandLineForDesktopFileExec( const base::CommandLine& command_line) { // http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html std::string quoted_path; const base::CommandLine::StringVector& argv = command_line.argv(); for (auto i = argv.begin(); i != argv.end(); ++i) { if (i != argv.begin()) quoted_path += " "; quoted_path += QuoteArgForDesktopFileExec(*i); } return quoted_path; } #endif #if defined(USE_GLIB) const char kDesktopEntry[] = "Desktop Entry"; const char kXdgOpenShebang[] = "#!/usr/bin/env xdg-open"; void SetActionsForDesktopApplication( const base::CommandLine& command_line, GKeyFile* key_file, std::set action_info) { if (action_info.empty()) return; std::vector action_ids; for (const auto& info : action_info) { action_ids.push_back(info.id); } std::string joined_action_ids = base::JoinString(action_ids, ";"); g_key_file_set_string(key_file, kDesktopEntry, "Actions", joined_action_ids.c_str()); for (const auto& info : action_info) { std::string section_title = "Desktop Action " + info.id; g_key_file_set_string(key_file, section_title.c_str(), "Name", info.name.c_str()); std::string launch_url_str = info.exec_launch_url.spec(); // Escape % as %%. RE2::GlobalReplace(&launch_url_str, "%", "%%"); base::CommandLine current_cmd(command_line); current_cmd.AppendSwitchASCII(switches::kAppLaunchUrlForShortcutsMenuItem, launch_url_str); g_key_file_set_string( key_file, section_title.c_str(), "Exec", QuoteCommandLineForDesktopFileExec(current_cmd).c_str()); } } #endif } // namespace // Allows LaunchXdgUtility to join a process. // thread_restrictions.h assumes it to be in shell_integration_linux namespace. class LaunchXdgUtilityScopedAllowBaseSyncPrimitives : public base::ScopedAllowBaseSyncPrimitives {}; bool LaunchXdgUtility(const std::vector& argv, int* exit_code) { // xdg-settings internally runs xdg-mime, which uses mv to move newly-created // files on top of originals after making changes to them. In the event that // the original files are owned by another user (e.g. root, which can happen // if they are updated within sudo), mv will prompt the user to confirm if // standard input is a terminal (otherwise it just does it). So make sure it's // not, to avoid locking everything up waiting for mv. *exit_code = EXIT_FAILURE; int devnull = open("/dev/null", O_RDONLY); if (devnull < 0) return false; base::LaunchOptions options; options.fds_to_remap.push_back(std::make_pair(devnull, STDIN_FILENO)); base::Process process = base::LaunchProcess(argv, options); close(devnull); if (!process.IsValid()) return false; LaunchXdgUtilityScopedAllowBaseSyncPrimitives allow_base_sync_primitives; return process.WaitForExit(exit_code); } std::string GetWMClassFromAppName(std::string app_name) { base::i18n::ReplaceIllegalCharactersInPath(&app_name, '_'); base::TrimString(app_name, "_", &app_name); return app_name; } std::string GetXdgAppIdForWebApp(std::string app_name, const base::FilePath& profile_path) { if (base::StartsWith(app_name, web_app::kCrxAppPrefix)) app_name = app_name.substr(strlen(web_app::kCrxAppPrefix)); return GetDesktopBaseName( web_app::GetAppShortcutFilename(profile_path, app_name).AsUTF8Unsafe()); } base::FilePath GetDataWriteLocation(base::Environment* env) { return base::nix::GetXDGDirectory(env, "XDG_DATA_HOME", ".local/share"); } std::vector GetDataSearchLocations(base::Environment* env) { base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, base::BlockingType::MAY_BLOCK); std::vector search_paths; base::FilePath write_location = GetDataWriteLocation(env); search_paths.push_back(write_location); std::string xdg_data_dirs; if (env->GetVar("XDG_DATA_DIRS", &xdg_data_dirs) && !xdg_data_dirs.empty()) { base::StringTokenizer tokenizer(xdg_data_dirs, ":"); while (tokenizer.GetNext()) { search_paths.emplace_back(tokenizer.token_piece()); } } else { search_paths.push_back(base::FilePath("/usr/local/share")); search_paths.push_back(base::FilePath("/usr/share")); } return search_paths; } namespace internal { // Get the value of NoDisplay from the [Desktop Entry] section of a .desktop // file, given in |shortcut_contents|. If the key is not found, returns false. bool GetNoDisplayFromDesktopFile(const std::string& shortcut_contents) { #if defined(USE_GLIB) // An empty file causes a crash with glib <= 2.32, so special case here. if (shortcut_contents.empty()) return false; GKeyFile* key_file = g_key_file_new(); GError* err = NULL; if (!g_key_file_load_from_data(key_file, shortcut_contents.c_str(), shortcut_contents.size(), G_KEY_FILE_NONE, &err)) { LOG(WARNING) << "Unable to read desktop file template: " << err->message; g_error_free(err); g_key_file_free(key_file); return false; } bool nodisplay = false; char* nodisplay_c_string = g_key_file_get_string(key_file, kDesktopEntry, "NoDisplay", &err); if (nodisplay_c_string) { if (!g_strcmp0(nodisplay_c_string, "true")) nodisplay = true; g_free(nodisplay_c_string); } else { g_error_free(err); } g_key_file_free(key_file); return nodisplay; #else NOTIMPLEMENTED(); return false; #endif } // Gets the path to the Chrome executable or wrapper script. // Returns an empty path if the executable path could not be found, which should // never happen. base::FilePath GetChromeExePath() { // Try to get the name of the wrapper script that launched Chrome. std::unique_ptr environment(base::Environment::Create()); std::string wrapper_script; if (environment->GetVar("CHROME_WRAPPER", &wrapper_script)) return base::FilePath(wrapper_script); // Just return the name of the executable path for Chrome. base::FilePath chrome_exe_path; base::PathService::Get(base::FILE_EXE, &chrome_exe_path); return chrome_exe_path; } std::string GetProgramClassName(const base::CommandLine& command_line, const std::string& desktop_file_name) { std::string class_name = GetDesktopBaseName(desktop_file_name); std::string user_data_dir = command_line.GetSwitchValueNative(switches::kUserDataDir); // If the user launches with e.g. --user-data-dir=/tmp/my-user-data, set the // class name to "Chrome (/tmp/my-user-data)". The class name will show up in // the alt-tab list in gnome-shell if you're running a binary that doesn't // have a matching .desktop file. return user_data_dir.empty() ? class_name : class_name + " (" + user_data_dir + ")"; } std::string GetProgramClassClass(const base::CommandLine& command_line, const std::string& desktop_file_name) { if (command_line.HasSwitch(switches::kWmClass)) return command_line.GetSwitchValueASCII(switches::kWmClass); std::string desktop_base_name = GetDesktopBaseName(desktop_file_name); if (auto* platform_utils = ui::OzonePlatform::GetInstance()->GetPlatformUtils()) { return platform_utils->GetWmWindowClass(desktop_base_name); } if (!desktop_base_name.empty()) { // Capitalize the first character like gtk does. desktop_base_name[0] = base::ToUpperASCII(desktop_base_name[0]); } return desktop_base_name; } } // namespace internal std::string GetProgramClassName() { std::unique_ptr env(base::Environment::Create()); return internal::GetProgramClassName(*base::CommandLine::ForCurrentProcess(), chrome::GetDesktopName(env.get())); } std::string GetProgramClassClass() { std::unique_ptr env(base::Environment::Create()); return internal::GetProgramClassClass(*base::CommandLine::ForCurrentProcess(), chrome::GetDesktopName(env.get())); } std::string GetIconName() { #if BUILDFLAG(GOOGLE_CHROME_BRANDING) return "google-chrome"; #else // BUILDFLAG(CHROMIUM_BRANDING) return "thorium"; #endif } bool GetExistingShortcutContents(base::Environment* env, const base::FilePath& desktop_filename, std::string* output) { base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, base::BlockingType::MAY_BLOCK); std::vector search_paths = GetDataSearchLocations(env); for (std::vector::const_iterator i = search_paths.begin(); i != search_paths.end(); ++i) { base::FilePath path = i->Append("applications").Append(desktop_filename); VLOG(1) << "Looking for desktop file in " << path.value(); if (base::PathExists(path)) { VLOG(1) << "Found desktop file at " << path.value(); return base::ReadFileToString(path, output); } } return false; } base::FilePath GetWebShortcutFilename(const GURL& url) { // Use a prefix, because xdg-desktop-menu requires it. std::string filename = std::string(chrome::kBrowserProcessExecutableName) + "-" + url.spec(); base::i18n::ReplaceIllegalCharactersInPath(&filename, '_'); base::FilePath desktop_path; if (!base::PathService::Get(base::DIR_USER_DESKTOP, &desktop_path)) return base::FilePath(); base::FilePath filepath = desktop_path.Append(filename); base::FilePath alternative_filepath(filepath.value() + ".desktop"); for (size_t i = 1; i < 100; ++i) { if (base::PathExists(base::FilePath(alternative_filepath))) { alternative_filepath = base::FilePath( filepath.value() + "_" + base::NumberToString(i) + ".desktop"); } else { return base::FilePath(alternative_filepath).BaseName(); } } return base::FilePath(); } std::vector GetExistingProfileShortcutFilenames( const base::FilePath& profile_path, const base::FilePath& directory) { base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, base::BlockingType::MAY_BLOCK); // Use a prefix, because xdg-desktop-menu requires it. std::string prefix(chrome::kBrowserProcessExecutableName); prefix.append("-"); std::string suffix("-"); suffix.append(profile_path.BaseName().value()); base::i18n::ReplaceIllegalCharactersInPath(&suffix, '_'); // Spaces in filenames break xdg-desktop-menu // (see https://bugs.freedesktop.org/show_bug.cgi?id=66605). base::ReplaceChars(suffix, " ", "_", &suffix); std::string glob = prefix + "*" + suffix + ".desktop"; base::FileEnumerator files(directory, false, base::FileEnumerator::FILES, glob); base::FilePath shortcut_file = files.Next(); std::vector shortcut_paths; while (!shortcut_file.empty()) { shortcut_paths.push_back(shortcut_file.BaseName()); shortcut_file = files.Next(); } return shortcut_paths; } std::string GetDesktopFileContents( const base::FilePath& chrome_exe_path, const std::string& app_name, const GURL& url, const std::string& extension_id, const std::u16string& title, const std::string& icon_name, const base::FilePath& profile_path, const std::string& categories, const std::string& mime_type, bool no_display, const std::string& run_on_os_login_mode, std::set action_info) { base::CommandLine cmd_line = shell_integration::CommandLineArgsForLauncher( url, extension_id, profile_path, run_on_os_login_mode); cmd_line.SetProgram(chrome_exe_path); return GetDesktopFileContentsForCommand(cmd_line, app_name, url, title, icon_name, categories, mime_type, no_display, std::move(action_info)); } std::string GetDesktopFileContentsForCommand( const base::CommandLine& command_line, const std::string& app_name, const GURL& url, const std::u16string& title, const std::string& icon_name, const std::string& categories, const std::string& mime_type, bool no_display, std::set action_info) { #if defined(USE_GLIB) // Although not required by the spec, Nautilus on Ubuntu Karmic creates its // launchers with an xdg-open shebang. Follow that convention. std::string output_buffer = std::string(kXdgOpenShebang) + "\n"; // See http://standards.freedesktop.org/desktop-entry-spec/latest/ GKeyFile* key_file = g_key_file_new(); // Set keys with fixed values. g_key_file_set_string(key_file, kDesktopEntry, "Version", "1.0"); g_key_file_set_string(key_file, kDesktopEntry, "Terminal", "false"); g_key_file_set_string(key_file, kDesktopEntry, "Type", "Application"); // Set the "Name" key. std::string final_title = base::UTF16ToUTF8(title); // Make sure no endline characters can slip in and possibly introduce // additional lines (like Exec, which makes it a security risk). Also // use the URL as a default when the title is empty. if (final_title.empty() || final_title.find("\n") != std::string::npos || final_title.find("\r") != std::string::npos) { final_title = url.spec(); } g_key_file_set_string(key_file, kDesktopEntry, "Name", final_title.c_str()); base::CommandLine modified_command_line(command_line); // Set the "MimeType" key. if (!mime_type.empty() && mime_type.find("\n") == std::string::npos && mime_type.find("\r") == std::string::npos) { g_key_file_set_string(key_file, kDesktopEntry, "MimeType", mime_type.c_str()); // Some Linux Desktop Environments don't show file handlers unless they // specify where to place file arguments. // Note: We only include this parameter if the application is actually able // to handle files, to prevent it showing up in the list of all applications // which can handle files. modified_command_line.AppendArg("%U"); } // Set the "Exec" key. std::string final_path = QuoteCommandLineForDesktopFileExec(modified_command_line); g_key_file_set_string(key_file, kDesktopEntry, "Exec", final_path.c_str()); // Set the "Icon" key. if (!icon_name.empty()) { g_key_file_set_string(key_file, kDesktopEntry, "Icon", icon_name.c_str()); } else { g_key_file_set_string(key_file, kDesktopEntry, "Icon", GetIconName().c_str()); } // Set the "Categories" key. if (!categories.empty()) { g_key_file_set_string( key_file, kDesktopEntry, "Categories", categories.c_str()); } // Set the "NoDisplay" key. if (no_display) g_key_file_set_string(key_file, kDesktopEntry, "NoDisplay", "true"); std::string wmclass = GetWMClassFromAppName(app_name); g_key_file_set_string(key_file, kDesktopEntry, "StartupWMClass", wmclass.c_str()); SetActionsForDesktopApplication(command_line, key_file, std::move(action_info)); gsize length = 0; gchar* data_dump = g_key_file_to_data(key_file, &length, NULL); if (data_dump) { // If strlen(data_dump[0]) == 0, this check will fail. if (data_dump[0] == '\n') { // Older versions of glib produce a leading newline. If this is the case, // remove it to avoid double-newline after the shebang. output_buffer += (data_dump + 1); } else { output_buffer += data_dump; } g_free(data_dump); } g_key_file_free(key_file); return output_buffer; #else NOTIMPLEMENTED(); return std::string(); #endif } std::string GetDirectoryFileContents(const std::u16string& title, const std::string& icon_name) { #if defined(USE_GLIB) // See http://standards.freedesktop.org/desktop-entry-spec/latest/ GKeyFile* key_file = g_key_file_new(); g_key_file_set_string(key_file, kDesktopEntry, "Version", "1.0"); g_key_file_set_string(key_file, kDesktopEntry, "Type", "Directory"); std::string final_title = base::UTF16ToUTF8(title); g_key_file_set_string(key_file, kDesktopEntry, "Name", final_title.c_str()); if (!icon_name.empty()) { g_key_file_set_string(key_file, kDesktopEntry, "Icon", icon_name.c_str()); } else { g_key_file_set_string(key_file, kDesktopEntry, "Icon", GetIconName().c_str()); } gsize length = 0; gchar* data_dump = g_key_file_to_data(key_file, &length, NULL); std::string output_buffer; if (data_dump) { // If strlen(data_dump[0]) == 0, this check will fail. if (data_dump[0] == '\n') { // Older versions of glib produce a leading newline. If this is the case, // remove it to avoid double-newline after the shebang. output_buffer += (data_dump + 1); } else { output_buffer += data_dump; } g_free(data_dump); } g_key_file_free(key_file); return output_buffer; #else NOTIMPLEMENTED(); return std::string(); #endif } base::FilePath GetMimeTypesRegistrationFilename( const base::FilePath& profile_path, const web_app::AppId& app_id) { DCHECK(!profile_path.empty() && !app_id.empty()); // Use a prefix to clearly group files created by Chrome. std::string filename = base::StringPrintf( "%s-%s-%s%s", chrome::kBrowserProcessExecutableName, app_id.c_str(), profile_path.BaseName().value().c_str(), ".xml"); // Replace illegal characters and spaces in |filename|. base::i18n::ReplaceIllegalCharactersInPath(&filename, '_'); base::ReplaceChars(filename, " ", "_", &filename); return base::FilePath(filename); } std::string GetMimeTypesRegistrationFileContents( const apps::FileHandlers& file_handlers) { XmlWriter writer; writer.StartWriting(); writer.StartElement("mime-info"); writer.AddAttribute("xmlns", "http://www.freedesktop.org/standards/shared-mime-info"); for (const auto& file_handler : file_handlers) { for (const auto& accept_entry : file_handler.accept) { writer.StartElement("mime-type"); writer.AddAttribute("type", accept_entry.mime_type); if (!file_handler.display_name.empty()) { writer.WriteElement("comment", base::UTF16ToUTF8(file_handler.display_name)); } for (const auto& file_extension : accept_entry.file_extensions) { writer.StartElement("glob"); writer.AddAttribute("pattern", "*" + file_extension); writer.EndElement(); // "glob" } writer.EndElement(); // "mime-type" } } writer.EndElement(); // "mime-info" writer.StopWriting(); return writer.GetWrittenString(); } } // namespace shell_integration_linux namespace shell_integration { bool SetAsDefaultBrowser() { return shell_integration_linux::SetDefaultWebClient(std::string()); } bool SetAsDefaultProtocolClient(const std::string& protocol) { return shell_integration_linux::SetDefaultWebClient(protocol); } DefaultWebClientSetPermission GetDefaultWebClientSetPermission() { return SET_DEFAULT_UNATTENDED; } std::u16string GetApplicationNameForProtocol(const GURL& url) { return u"xdg-open"; } DefaultWebClientState GetDefaultBrowser() { return shell_integration_linux::GetIsDefaultWebClient(std::string()); } bool IsFirefoxDefaultBrowser() { std::vector argv; argv.push_back(shell_integration_linux::kXdgSettings); argv.push_back("get"); argv.push_back(shell_integration_linux::kXdgSettingsDefaultBrowser); std::string browser; // We don't care about the return value here. base::GetAppOutput(base::CommandLine(argv), &browser); return browser.find("firefox") != std::string::npos; } DefaultWebClientState IsDefaultProtocolClient(const std::string& protocol) { return shell_integration_linux::GetIsDefaultWebClient(protocol); } } // namespace shell_integration