Commit 05b50bd9 authored by Jay Harris's avatar Jay Harris Committed by Commit Bot

WebApps: Adds support for handling mimetypes in WebApps on Linux.

This lets web apps that can handle files show up in the
open-with menu on Linux.

Note: This is still experimental and hidden behind the
NativeFileSystemAPI and FileHandlingAPI flags.

In addition, some work needs to be done to register custom mimetypes
with the operating system as in:
https://specifications.freedesktop.org/shared-mime-info-spec/latest/ar01s02.html

Bug: 829689
Change-Id: I92bcc07cb5dffaa715c409bc6cf07c8410db34ef
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1833027
Commit-Queue: Jay Harris <harrisjay@chromium.org>
Reviewed-by: default avatarThomas Anderson <thomasanderson@chromium.org>
Reviewed-by: default avatarAlexey Baskakov <loyso@chromium.org>
Reviewed-by: default avatarAlan Cutter <alancutter@chromium.org>
Cr-Commit-Position: refs/heads/master@{#710593}
parent 90f9556a
......@@ -497,21 +497,22 @@ std::vector<base::FilePath> GetExistingProfileShortcutFilenames(
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 base::string16& title,
const std::string& icon_name,
const base::FilePath& profile_path,
const std::string& categories,
bool no_display) {
std::string GetDesktopFileContents(const base::FilePath& chrome_exe_path,
const std::string& app_name,
const GURL& url,
const std::string& extension_id,
const base::string16& title,
const std::string& icon_name,
const base::FilePath& profile_path,
const std::string& categories,
const std::string& mime_type,
bool no_display) {
base::CommandLine cmd_line = shell_integration::CommandLineArgsForLauncher(
url, extension_id, profile_path);
cmd_line.SetProgram(chrome_exe_path);
return GetDesktopFileContentsForCommand(cmd_line, app_name, url, title,
icon_name, categories, no_display);
icon_name, categories, mime_type,
no_display);
}
std::string GetDesktopFileContentsForCommand(
......@@ -521,6 +522,7 @@ std::string GetDesktopFileContentsForCommand(
const base::string16& title,
const std::string& icon_name,
const std::string& categories,
const std::string& mime_type,
bool no_display) {
#if defined(USE_GLIB)
// Although not required by the spec, Nautilus on Ubuntu Karmic creates its
......@@ -565,6 +567,13 @@ std::string GetDesktopFileContentsForCommand(
key_file, kDesktopEntry, "Categories", categories.c_str());
}
// 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());
}
// Set the "NoDisplay" key.
if (no_display)
g_key_file_set_string(key_file, kDesktopEntry, "NoDisplay", "true");
......
......@@ -65,6 +65,7 @@ std::string GetDesktopFileContents(const base::FilePath& chrome_exe_path,
const std::string& icon_name,
const base::FilePath& profile_path,
const std::string& categories,
const std::string& mime_type,
bool no_display);
// Returns contents for .desktop file that executes command_line. This is a more
......@@ -77,6 +78,7 @@ std::string GetDesktopFileContentsForCommand(
const base::string16& title,
const std::string& icon_name,
const std::string& categories,
const std::string& mime_type,
bool no_display);
// Returns contents for .directory file named |title| with icon |icon_name|. If
......
......@@ -330,11 +330,12 @@ TEST(ShellIntegrationTest, GetDesktopFileContents) {
const char* const title;
const char* const icon_name;
const char* const categories;
const char* const mime_type;
bool nodisplay;
const char* const expected_output;
} test_cases[] = {
// Real-world case.
{"http://gmail.com", "GMail", "chrome-http__gmail.com", "", false,
{"http://gmail.com", "GMail", "chrome-http__gmail.com", "", "", false,
"#!/usr/bin/env xdg-open\n"
"[Desktop Entry]\n"
......@@ -347,7 +348,7 @@ TEST(ShellIntegrationTest, GetDesktopFileContents) {
"StartupWMClass=gmail.com\n"},
// Make sure that empty icons are replaced by the chrome icon.
{"http://gmail.com", "GMail", "", "", false,
{"http://gmail.com", "GMail", "", "", "", false,
"#!/usr/bin/env xdg-open\n"
"[Desktop Entry]\n"
......@@ -365,7 +366,7 @@ TEST(ShellIntegrationTest, GetDesktopFileContents) {
// Test adding categories and NoDisplay=true.
{"http://gmail.com", "GMail", "chrome-http__gmail.com",
"Graphics;Education;", true,
"Graphics;Education;", "", true,
"#!/usr/bin/env xdg-open\n"
"[Desktop Entry]\n"
......@@ -381,7 +382,7 @@ TEST(ShellIntegrationTest, GetDesktopFileContents) {
// Now we're starting to be more evil...
{"http://evil.com/evil --join-the-b0tnet", "Ownz0red\nExec=rm -rf /",
"chrome-http__evil.com_evil", "", false,
"chrome-http__evil.com_evil", "", "", false,
"#!/usr/bin/env xdg-open\n"
"[Desktop Entry]\n"
......@@ -394,7 +395,7 @@ TEST(ShellIntegrationTest, GetDesktopFileContents) {
"Icon=chrome-http__evil.com_evil\n"
"StartupWMClass=evil.com__evil%20--join-the-b0tnet\n"},
{"http://evil.com/evil; rm -rf /; \"; rm -rf $HOME >ownz0red",
"Innocent Title", "chrome-http__evil.com_evil", "", false,
"Innocent Title", "chrome-http__evil.com_evil", "", "", false,
"#!/usr/bin/env xdg-open\n"
"[Desktop Entry]\n"
......@@ -412,7 +413,7 @@ TEST(ShellIntegrationTest, GetDesktopFileContents) {
"StartupWMClass=evil.com__evil;%20rm%20-rf%20_;%20%22;%20"
"rm%20-rf%20$HOME%20%3Eownz0red\n"},
{"http://evil.com/evil | cat `echo ownz0red` >/dev/null",
"Innocent Title", "chrome-http__evil.com_evil", "", false,
"Innocent Title", "chrome-http__evil.com_evil", "", "", false,
"#!/usr/bin/env xdg-open\n"
"[Desktop Entry]\n"
......@@ -426,7 +427,36 @@ TEST(ShellIntegrationTest, GetDesktopFileContents) {
"Icon=chrome-http__evil.com_evil\n"
"StartupWMClass=evil.com__evil%20%7C%20cat%20%60echo%20ownz0red"
"%60%20%3E_dev_null\n"},
};
// Test setting mime type
{"https://paint.app", "Paint", "chrome-https__paint.app", "Image",
"image/png;image/jpg", false,
"#!/usr/bin/env xdg-open\n"
"[Desktop Entry]\n"
"Version=1.0\n"
"Terminal=false\n"
"Type=Application\n"
"Name=Paint\n"
"Exec=/opt/google/chrome/google-chrome --app=https://paint.app/\n"
"Icon=chrome-https__paint.app\n"
"Categories=Image\n"
"MimeType=image/png;image/jpg\n"
"StartupWMClass=paint.app\n"},
// Test evil mime type.
{"https://paint.app", "Evil Paint", "chrome-https__paint.app", "Image",
"image/png\nExec=rm -rf /", false,
"#!/usr/bin/env xdg-open\n"
"[Desktop Entry]\n"
"Version=1.0\n"
"Terminal=false\n"
"Type=Application\n"
"Name=Evil Paint\n"
"Exec=/opt/google/chrome/google-chrome --app=https://paint.app/\n"
"Icon=chrome-https__paint.app\n"
"Categories=Image\n"
"StartupWMClass=paint.app\n"}};
for (size_t i = 0; i < base::size(test_cases); i++) {
SCOPED_TRACE(i);
......@@ -435,12 +465,9 @@ TEST(ShellIntegrationTest, GetDesktopFileContents) {
GetDesktopFileContents(
kChromeExePath,
web_app::GenerateApplicationNameFromURL(GURL(test_cases[i].url)),
GURL(test_cases[i].url),
std::string(),
base::ASCIIToUTF16(test_cases[i].title),
test_cases[i].icon_name,
base::FilePath(),
test_cases[i].categories,
GURL(test_cases[i].url), std::string(),
base::ASCIIToUTF16(test_cases[i].title), test_cases[i].icon_name,
base::FilePath(), test_cases[i].categories, test_cases[i].mime_type,
test_cases[i].nodisplay));
}
}
......@@ -461,13 +488,9 @@ TEST(ShellIntegrationTest, GetDesktopFileContentsAppList) {
"Categories=Network;WebBrowser;\n"
"StartupWMClass=chrome-app-list\n",
GetDesktopFileContentsForCommand(
command_line,
"chrome-app-list",
GURL(),
base::ASCIIToUTF16("Chrome App Launcher"),
"chrome_app_list",
"Network;WebBrowser;",
false));
command_line, "chrome-app-list", GURL(),
base::ASCIIToUTF16("Chrome App Launcher"), "chrome_app_list",
"Network;WebBrowser;", "", false));
}
TEST(ShellIntegrationTest, GetDirectoryFileContents) {
......
......@@ -85,6 +85,7 @@ source_set("components") {
if (is_desktop_linux) {
# Desktop linux, doesn't count ChromeOS.
sources += [
"web_app_file_handler_registration_linux.cc",
"web_app_shortcut_linux.cc",
"web_app_shortcut_linux.h",
]
......
......@@ -11,43 +11,35 @@
namespace web_app {
FileHandlerManager::FileHandlerManager(Profile* profile)
: profile_(profile), registrar_observer_(this) {}
: profile_(profile), registrar_observer_(this), shortcut_observer_(this) {}
FileHandlerManager::~FileHandlerManager() = default;
void FileHandlerManager::SetSubsystems(AppRegistrar* registrar) {
void FileHandlerManager::SetSubsystems(AppRegistrar* registrar,
AppShortcutManager* shortcut_manager) {
registrar_ = registrar;
registrar_observer_.Add(registrar);
shortcut_manager_ = shortcut_manager;
}
void FileHandlerManager::OnWebAppInstalled(const AppId& installed_app_id) {
if (!base::FeatureList::IsEnabled(blink::features::kFileHandlingAPI) ||
!OsSupportsWebAppFileHandling())
return;
std::string app_name = registrar_->GetAppShortName(installed_app_id);
const std::vector<apps::FileHandlerInfo>* file_handlers =
GetFileHandlers(installed_app_id);
if (!file_handlers)
return;
std::set<std::string> file_extensions =
GetFileExtensionsFromFileHandlers(*file_handlers);
std::set<std::string> mime_types =
GetMimeTypesFromFileHandlers(*file_handlers);
RegisterFileHandlersForWebApp(installed_app_id, app_name, *profile_,
file_extensions, mime_types);
void FileHandlerManager::Start() {
DCHECK(registrar_);
DCHECK(shortcut_manager_);
registrar_observer_.Add(registrar_);
shortcut_observer_.Add(shortcut_manager_);
}
void FileHandlerManager::OnWebAppUninstalled(const AppId& installed_app_id) {
if (base::FeatureList::IsEnabled(blink::features::kFileHandlingAPI) &&
OsSupportsWebAppFileHandling()) {
UnregisterFileHandlersForWebApp(installed_app_id, *profile_);
UnregisterFileHandlersForWebApp(installed_app_id, profile_);
}
}
void FileHandlerManager::OnWebAppProfileWillBeDeleted(const AppId& app_id) {
if (base::FeatureList::IsEnabled(blink::features::kFileHandlingAPI) &&
OsSupportsWebAppFileHandling()) {
UnregisterFileHandlersForWebApp(app_id, *profile_);
UnregisterFileHandlersForWebApp(app_id, profile_);
}
}
......@@ -55,6 +47,29 @@ void FileHandlerManager::OnAppRegistrarDestroyed() {
registrar_observer_.RemoveAll();
}
void FileHandlerManager::OnShortcutsCreated(const AppId& installed_app_id) {
if (!base::FeatureList::IsEnabled(blink::features::kFileHandlingAPI) ||
!OsSupportsWebAppFileHandling()) {
return;
}
std::string app_name = registrar_->GetAppShortName(installed_app_id);
const std::vector<apps::FileHandlerInfo>* file_handlers =
GetFileHandlers(installed_app_id);
if (!file_handlers)
return;
std::set<std::string> file_extensions =
GetFileExtensionsFromFileHandlers(*file_handlers);
std::set<std::string> mime_types =
GetMimeTypesFromFileHandlers(*file_handlers);
RegisterFileHandlersForWebApp(installed_app_id, app_name, profile_,
file_extensions, mime_types);
}
void FileHandlerManager::OnShortcutManagerDestroyed() {
shortcut_observer_.RemoveAll();
}
std::set<std::string> GetFileExtensionsFromFileHandlers(
const std::vector<apps::FileHandlerInfo>& file_handlers) {
std::set<std::string> file_extensions;
......
......@@ -13,6 +13,8 @@
#include "base/scoped_observer.h"
#include "chrome/browser/web_applications/components/app_registrar.h"
#include "chrome/browser/web_applications/components/app_registrar_observer.h"
#include "chrome/browser/web_applications/components/app_shortcut_manager.h"
#include "chrome/browser/web_applications/components/app_shortcut_observer.h"
#include "chrome/browser/web_applications/components/web_app_helpers.h"
#include "components/services/app_service/public/cpp/file_handler_info.h"
......@@ -20,13 +22,16 @@ class Profile;
namespace web_app {
class FileHandlerManager : public AppRegistrarObserver {
class FileHandlerManager : public AppRegistrarObserver,
public AppShortcutObserver {
public:
explicit FileHandlerManager(Profile* profile);
~FileHandlerManager() override;
// |registrar| is used to observe OnWebAppInstalled/Uninstalled events.
void SetSubsystems(AppRegistrar* registrar);
void SetSubsystems(AppRegistrar* registrar,
AppShortcutManager* shortcut_manager);
void Start();
// Gets all file handlers for |app_id|. |nullptr| if the app has no file
// handlers.
......@@ -40,14 +45,19 @@ class FileHandlerManager : public AppRegistrarObserver {
private:
Profile* const profile_;
AppRegistrar* registrar_ = nullptr;
AppShortcutManager* shortcut_manager_ = nullptr;
// AppRegistrarObserver:
void OnWebAppInstalled(const AppId& app_id) override;
void OnWebAppUninstalled(const AppId& app_id) override;
void OnWebAppProfileWillBeDeleted(const AppId& app_id) override;
void OnAppRegistrarDestroyed() override;
// AppShortcutObserver:
void OnShortcutsCreated(const AppId& app_id) override;
void OnShortcutManagerDestroyed() override;
ScopedObserver<AppRegistrar, AppRegistrarObserver> registrar_observer_;
ScopedObserver<AppShortcutManager, AppShortcutObserver> shortcut_observer_;
DISALLOW_COPY_AND_ASSIGN(FileHandlerManager);
};
......
......@@ -9,22 +9,27 @@
namespace web_app {
#if !defined(OS_WIN)
// This block defines stub implementations of OS specific methods for
// FileHandling. Currently, Windows and Desktop Linux (but not Chrome OS) have
// their own implementations.
//
// Note: Because OS_LINUX includes OS_CHROMEOS be sure to use the stub on
// OS_CHROMEOS.
#if !defined(OS_WIN) && !(defined(OS_LINUX) && !defined(OS_CHROMEOS))
bool OsSupportsWebAppFileHandling() {
return false;
}
void RegisterFileHandlersForWebApp(const AppId& app_id,
const std::string& app_name,
const Profile& profile,
Profile* profile,
const std::set<std::string>& file_extensions,
const std::set<std::string>& mime_types) {
DCHECK(OsSupportsWebAppFileHandling());
// Stub function for OS's that don't support Web App file handling yet.
}
void UnregisterFileHandlersForWebApp(const AppId& app_id,
const Profile& profile) {
void UnregisterFileHandlersForWebApp(const AppId& app_id, Profile* profile) {
DCHECK(OsSupportsWebAppFileHandling());
// Stub function for OS's that don't support Web App file handling yet.
}
......
......@@ -22,14 +22,13 @@ bool OsSupportsWebAppFileHandling();
// This may also involve creating a shim app to launch Chrome from.
void RegisterFileHandlersForWebApp(const AppId& app_id,
const std::string& app_name,
const Profile& profile,
Profile* profile,
const std::set<std::string>& file_extensions,
const std::set<std::string>& mime_types);
// Undo the file extensions registration for the PWA with specified |app_id|.
// If a shim app was required, also removes the shim app.
void UnregisterFileHandlersForWebApp(const AppId& app_id,
const Profile& profile);
void UnregisterFileHandlersForWebApp(const AppId& app_id, Profile* profile);
} // namespace web_app
......
// Copyright 2019 The Chromium Authors. 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/web_applications/components/web_app_file_handler_registration.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "chrome/browser/web_applications/components/app_shortcut_manager.h"
#include "chrome/browser/web_applications/components/web_app_provider_base.h"
#include "chrome/browser/web_applications/components/web_app_shortcut.h"
namespace web_app {
namespace {
void OnShortcutInfoReceived(const std::set<std::string> mime_types,
std::unique_ptr<ShortcutInfo> info) {
for (const auto& mime_type : mime_types)
info->mime_types.push_back(mime_type);
base::FilePath shortcut_data_dir = internals::GetShortcutDataDir(*info);
ShortcutLocations locations;
locations.applications_menu_location = APP_MENU_LOCATION_SUBDIR_CHROMEAPPS;
internals::ScheduleCreatePlatformShortcuts(
std::move(shortcut_data_dir), locations,
ShortcutCreationReason::SHORTCUT_CREATION_BY_USER, std::move(info),
base::DoNothing());
}
} // namespace
bool OsSupportsWebAppFileHandling() {
return true;
}
void RegisterFileHandlersForWebApp(const AppId& app_id,
const std::string& app_name,
Profile* profile,
const std::set<std::string>& file_extensions,
const std::set<std::string>& mime_types) {
AppShortcutManager& shortcut_manager =
WebAppProviderBase::GetProviderBase(profile)->shortcut_manager();
shortcut_manager.GetShortcutInfoForApp(
app_id, base::BindOnce(OnShortcutInfoReceived, mime_types));
}
void UnregisterFileHandlersForWebApp(const AppId& app_id, Profile* profile) {
// TODO(harrisjay): Add support for unregistering file handlers.
}
} // namespace web_app
......@@ -14,14 +14,13 @@ bool OsSupportsWebAppFileHandling() {
void RegisterFileHandlersForWebApp(const AppId& app_id,
const std::string& app_name,
const Profile& profile,
Profile* profile,
const std::set<std::string>& file_extensions,
const std::set<std::string>& mime_types) {
// TODO(davidbienvenu): Setup shim app and windows registry for this |app_id|.
}
void UnregisterFileHandlersForWebApp(const AppId& app_id,
const Profile& profile) {
void UnregisterFileHandlersForWebApp(const AppId& app_id, Profile* profile) {
// TODO(davidbienvenu): Cleanup windows registry entries for this
// |app_id|.
}
......
......@@ -6,6 +6,8 @@
#define CHROME_BROWSER_WEB_APPLICATIONS_COMPONENTS_WEB_APP_SHORTCUT_H_
#include <memory>
#include <string>
#include <vector>
#include "base/callback_forward.h"
#include "base/files/file_path.h"
......@@ -38,6 +40,7 @@ struct ShortcutInfo {
base::FilePath profile_path;
std::string profile_name;
std::string version_for_display;
std::vector<std::string> mime_types;
private:
// Since gfx::ImageFamily |favicon| has a non-thread-safe reference count in
......
......@@ -270,7 +270,7 @@ bool CreateDesktopShortcut(
std::string contents = shell_integration_linux::GetDesktopFileContents(
chrome_exe_path, app_name, shortcut_info.url,
shortcut_info.extension_id, shortcut_info.title, icon_name,
shortcut_info.profile_path, "", false);
shortcut_info.profile_path, "", "", false);
success = CreateShortcutOnDesktop(shortcut_filename, contents);
}
......@@ -299,6 +299,7 @@ bool CreateDesktopShortcut(
std::string contents = shell_integration_linux::GetDesktopFileContents(
chrome_exe_path, app_name, shortcut_info.url, shortcut_info.extension_id,
shortcut_info.title, icon_name, shortcut_info.profile_path, "",
base::JoinString(shortcut_info.mime_types, ";"),
creation_locations.applications_menu_location ==
web_app::APP_MENU_LOCATION_HIDDEN);
success = CreateShortcutInApplicationsMenu(shortcut_filename, contents,
......
......@@ -224,7 +224,8 @@ void WebAppProvider::ConnectSubsystems() {
system_web_app_manager_->SetSubsystems(pending_app_manager_.get(),
registrar_.get(), ui_manager_.get());
web_app_policy_manager_->SetSubsystems(pending_app_manager_.get());
file_handler_manager_->SetSubsystems(registrar_.get());
file_handler_manager_->SetSubsystems(registrar_.get(),
shortcut_manager_.get());
shortcut_manager_->SetSubsystems(registrar_.get());
connected_ = true;
......@@ -246,6 +247,7 @@ void WebAppProvider::OnRegistryControllerReady() {
system_web_app_manager_->Start();
}
manifest_update_manager_->Start();
file_handler_manager_->Start();
on_registry_ready_.Signal();
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment