Commit af1ca923 authored by Jay Harris's avatar Jay Harris Committed by Commit Bot

WebApps: Enables/disables file handling depending on trial validity.

File handling is a slightly different to other features which go into
origin trial: The underlying OS holds state to let it identify handlers
for different file types.

This CL cleans up that state when an origin trial expires, so that apps
which don't have a valid trial, don't show up as file handlers in the
operating system.

On navigation to a page inside an installed app with file handlers,
we store the time that the trial expires. If the trial has
already expired, we unregister the file handlers. If the trial is valid,
but the app's file handlers are not registered, we register them.

On Chrome start up, we unregister file handlers for all apps for
which the origin trial has past it's expiry date.

This ensures that apps without a valid origin trial token will show up
in the open with menu at most one Chrome restart, or until the user
visits the site again, whichever happens first.

Bug: 1028448
Change-Id: I6e2d19d7462d1f94955f1ca51abffd2a4c53096d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1940001Reviewed-by: default avatarKinuko Yasuda <kinuko@chromium.org>
Reviewed-by: default avatarGiovanni Ortuño Urquidi <ortuno@chromium.org>
Commit-Queue: Jay Harris <harrisjay@chromium.org>
Cr-Commit-Position: refs/heads/master@{#739137}
parent 655b8048
...@@ -5,26 +5,68 @@ ...@@ -5,26 +5,68 @@
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/files/file_util.h" #include "base/files/file_util.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "base/test/bind_test_util.h"
#include "base/test/scoped_feature_list.h" #include "base/test/scoped_feature_list.h"
#include "base/threading/thread_restrictions.h" #include "base/threading/thread_restrictions.h"
#include "chrome/browser/apps/app_service/app_launch_params.h" #include "chrome/browser/apps/app_service/app_launch_params.h"
#include "chrome/browser/apps/launch_service/launch_service.h" #include "chrome/browser/apps/launch_service/launch_service.h"
#include "chrome/browser/ui/web_applications/web_app_controller_browsertest.h" #include "chrome/browser/ui/web_applications/web_app_controller_browsertest.h"
#include "chrome/browser/web_applications/components/file_handler_manager.h"
#include "chrome/browser/web_applications/components/web_app_prefs_utils.h"
#include "chrome/browser/web_applications/components/web_app_provider_base.h"
#include "chrome/common/web_application_info.h" #include "chrome/common/web_application_info.h"
#include "content/public/test/browser_test_utils.h" #include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_navigation_observer.h" #include "content/public/test/test_navigation_observer.h"
#include "mojo/public/cpp/bindings/associated_receiver.h"
#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
#include "third_party/blink/public/common/features.h" #include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/mojom/web_launch/file_handling_expiry.mojom-test-utils.h"
class WebAppFileHandlingBrowserTest // A fake file handling expiry service. This service allows us to mock having an
: public web_app::WebAppControllerBrowserTest { // origin trial which expires having a certain time, without needing to manage
// actual origin trial tokens.
class FakeFileHandlingExpiryService
: public blink::mojom::FileHandlingExpiryInterceptorForTesting {
public: public:
WebAppFileHandlingBrowserTest() { FakeFileHandlingExpiryService()
scoped_feature_list_.InitWithFeatures( : expiry_time_(base::Time::Now() + base::TimeDelta::FromDays(1)) {}
{blink::features::kNativeFileSystemAPI,
blink::features::kFileHandlingAPI}, blink::mojom::FileHandlingExpiry* GetForwardingInterface() override {
{}); NOTREACHED();
return nullptr;
}
void Bind(mojo::ScopedInterfaceEndpointHandle handle) {
receiver_.Bind(
mojo::PendingAssociatedReceiver<blink::mojom::FileHandlingExpiry>(
std::move(handle)));
}
void SetExpiryTime(base::Time expiry_time) { expiry_time_ = expiry_time; }
void RequestOriginTrialExpiryTime(
RequestOriginTrialExpiryTimeCallback callback) override {
std::move(callback).Run(expiry_time_);
}
private:
base::Time expiry_time_;
mojo::AssociatedReceiver<blink::mojom::FileHandlingExpiry> receiver_{this};
};
class WebAppFileHandlingTestBase : public web_app::WebAppControllerBrowserTest {
public:
web_app::WebAppProviderBase* provider() {
return web_app::WebAppProviderBase::GetProviderBase(profile());
} }
web_app::FileHandlerManager& file_handler_manager() {
return provider()->file_handler_manager();
}
web_app::AppRegistrar& registrar() { return provider()->registrar(); }
GURL GetSecureAppURL() { GURL GetSecureAppURL() {
return https_server()->GetURL("app.com", "/ssl/google.html"); return https_server()->GetURL("app.com", "/ssl/google.html");
} }
...@@ -37,20 +79,7 @@ class WebAppFileHandlingBrowserTest ...@@ -37,20 +79,7 @@ class WebAppFileHandlingBrowserTest
return https_server()->GetURL("app.com", "/ssl/page_with_refs.html"); return https_server()->GetURL("app.com", "/ssl/page_with_refs.html");
} }
base::FilePath NewTestFilePath(const base::FilePath::CharType* extension) { void InstallFileHandlingPWA() {
// CreateTemporaryFile blocks, temporarily allow blocking.
base::ScopedAllowBlockingForTesting allow_blocking;
// In order to test file handling, we need to be able to supply a file
// extension for the temp file.
base::FilePath test_file_path;
base::CreateTemporaryFile(&test_file_path);
base::FilePath new_file_path = test_file_path.AddExtension(extension);
EXPECT_TRUE(base::ReplaceFile(test_file_path, new_file_path, nullptr));
return new_file_path;
}
std::string InstallFileHandlingPWA() {
GURL url = GetSecureAppURL(); GURL url = GetSecureAppURL();
auto web_app_info = std::make_unique<WebApplicationInfo>(); auto web_app_info = std::make_unique<WebApplicationInfo>();
...@@ -72,10 +101,40 @@ class WebAppFileHandlingBrowserTest ...@@ -72,10 +101,40 @@ class WebAppFileHandlingBrowserTest
base::ASCIIToUTF16(".csv")); base::ASCIIToUTF16(".csv"));
web_app_info->file_handlers.push_back(std::move(entry2)); web_app_info->file_handlers.push_back(std::move(entry2));
return WebAppControllerBrowserTest::InstallWebApp(std::move(web_app_info)); app_id_ =
WebAppControllerBrowserTest::InstallWebApp(std::move(web_app_info));
}
protected:
const web_app::AppId& app_id() { return app_id_; }
private:
web_app::AppId app_id_;
};
class WebAppFileHandlingBrowserTest : public WebAppFileHandlingTestBase {
public:
WebAppFileHandlingBrowserTest() {
scoped_feature_list_.InitWithFeatures(
{blink::features::kNativeFileSystemAPI,
blink::features::kFileHandlingAPI},
{});
}
base::FilePath NewTestFilePath(const base::FilePath::CharType* extension) {
// CreateTemporaryFile blocks, temporarily allow blocking.
base::ScopedAllowBlockingForTesting allow_blocking;
// In order to test file handling, we need to be able to supply a file
// extension for the temp file.
base::FilePath test_file_path;
base::CreateTemporaryFile(&test_file_path);
base::FilePath new_file_path = test_file_path.AddExtension(extension);
EXPECT_TRUE(base::ReplaceFile(test_file_path, new_file_path, nullptr));
return new_file_path;
} }
content::WebContents* LaunchWithFiles( virtual content::WebContents* LaunchWithFiles(
const std::string& app_id, const std::string& app_id,
const GURL& expected_launch_url, const GURL& expected_launch_url,
const std::vector<base::FilePath>& files, const std::vector<base::FilePath>& files,
...@@ -111,9 +170,9 @@ IN_PROC_BROWSER_TEST_P(WebAppFileHandlingBrowserTest, ...@@ -111,9 +170,9 @@ IN_PROC_BROWSER_TEST_P(WebAppFileHandlingBrowserTest,
LaunchConsumerIsNotTriggeredWithNoFiles) { LaunchConsumerIsNotTriggeredWithNoFiles) {
ASSERT_TRUE(https_server()->Start()); ASSERT_TRUE(https_server()->Start());
const std::string app_id = InstallFileHandlingPWA(); InstallFileHandlingPWA();
content::WebContents* web_contents = content::WebContents* web_contents =
LaunchWithFiles(app_id, GetSecureAppURL(), {}); LaunchWithFiles(app_id(), GetSecureAppURL(), {});
EXPECT_EQ(false, content::EvalJs(web_contents, "!!window.launchParams")); EXPECT_EQ(false, content::EvalJs(web_contents, "!!window.launchParams"));
} }
...@@ -121,10 +180,10 @@ IN_PROC_BROWSER_TEST_P(WebAppFileHandlingBrowserTest, ...@@ -121,10 +180,10 @@ IN_PROC_BROWSER_TEST_P(WebAppFileHandlingBrowserTest,
PWAsCanReceiveFileLaunchParams) { PWAsCanReceiveFileLaunchParams) {
ASSERT_TRUE(https_server()->Start()); ASSERT_TRUE(https_server()->Start());
const std::string app_id = InstallFileHandlingPWA(); InstallFileHandlingPWA();
base::FilePath test_file_path = NewTestFilePath(FILE_PATH_LITERAL("txt")); base::FilePath test_file_path = NewTestFilePath(FILE_PATH_LITERAL("txt"));
content::WebContents* web_contents = content::WebContents* web_contents = LaunchWithFiles(
LaunchWithFiles(app_id, GetTextFileHandlerActionURL(), {test_file_path}); app_id(), GetTextFileHandlerActionURL(), {test_file_path});
EXPECT_EQ(1, EXPECT_EQ(1,
content::EvalJs(web_contents, "window.launchParams.files.length")); content::EvalJs(web_contents, "window.launchParams.files.length"));
...@@ -136,10 +195,10 @@ IN_PROC_BROWSER_TEST_P(WebAppFileHandlingBrowserTest, ...@@ -136,10 +195,10 @@ IN_PROC_BROWSER_TEST_P(WebAppFileHandlingBrowserTest,
PWAsCanReceiveFileLaunchParamsInTab) { PWAsCanReceiveFileLaunchParamsInTab) {
ASSERT_TRUE(https_server()->Start()); ASSERT_TRUE(https_server()->Start());
const std::string app_id = InstallFileHandlingPWA(); InstallFileHandlingPWA();
base::FilePath test_file_path = NewTestFilePath(FILE_PATH_LITERAL("txt")); base::FilePath test_file_path = NewTestFilePath(FILE_PATH_LITERAL("txt"));
content::WebContents* web_contents = content::WebContents* web_contents =
LaunchWithFiles(app_id, GetTextFileHandlerActionURL(), {test_file_path}, LaunchWithFiles(app_id(), GetTextFileHandlerActionURL(), {test_file_path},
apps::mojom::LaunchContainer::kLaunchContainerTab); apps::mojom::LaunchContainer::kLaunchContainerTab);
EXPECT_EQ(1, EXPECT_EQ(1,
...@@ -152,26 +211,152 @@ IN_PROC_BROWSER_TEST_P(WebAppFileHandlingBrowserTest, ...@@ -152,26 +211,152 @@ IN_PROC_BROWSER_TEST_P(WebAppFileHandlingBrowserTest,
PWAsDispatchOnCorrectFileHandlingURL) { PWAsDispatchOnCorrectFileHandlingURL) {
ASSERT_TRUE(https_server()->Start()); ASSERT_TRUE(https_server()->Start());
const std::string app_id = InstallFileHandlingPWA(); InstallFileHandlingPWA();
// Test that file handler dispatches correct URL based on file extension. // Test that file handler dispatches correct URL based on file extension.
LaunchWithFiles(app_id, GetSecureAppURL(), {}); LaunchWithFiles(app_id(), GetSecureAppURL(), {});
LaunchWithFiles(app_id, GetTextFileHandlerActionURL(), LaunchWithFiles(app_id(), GetTextFileHandlerActionURL(),
{NewTestFilePath(FILE_PATH_LITERAL("txt"))}); {NewTestFilePath(FILE_PATH_LITERAL("txt"))});
LaunchWithFiles(app_id, GetCSVFileHandlerActionURL(), LaunchWithFiles(app_id(), GetCSVFileHandlerActionURL(),
{NewTestFilePath(FILE_PATH_LITERAL("csv"))}); {NewTestFilePath(FILE_PATH_LITERAL("csv"))});
// Test as above in a tab. // Test as above in a tab.
LaunchWithFiles(app_id, GetSecureAppURL(), {}, LaunchWithFiles(app_id(), GetSecureAppURL(), {},
apps::mojom::LaunchContainer::kLaunchContainerTab); apps::mojom::LaunchContainer::kLaunchContainerTab);
LaunchWithFiles(app_id, GetTextFileHandlerActionURL(), LaunchWithFiles(app_id(), GetTextFileHandlerActionURL(),
{NewTestFilePath(FILE_PATH_LITERAL("txt"))}, {NewTestFilePath(FILE_PATH_LITERAL("txt"))},
apps::mojom::LaunchContainer::kLaunchContainerTab); apps::mojom::LaunchContainer::kLaunchContainerTab);
LaunchWithFiles(app_id, GetCSVFileHandlerActionURL(), LaunchWithFiles(app_id(), GetCSVFileHandlerActionURL(),
{NewTestFilePath(FILE_PATH_LITERAL("csv"))}, {NewTestFilePath(FILE_PATH_LITERAL("csv"))},
apps::mojom::LaunchContainer::kLaunchContainerTab); apps::mojom::LaunchContainer::kLaunchContainerTab);
} }
class WebAppFileHandlingOriginTrialBrowserTest
: public WebAppFileHandlingTestBase {
public:
WebAppFileHandlingOriginTrialBrowserTest() {
web_app::FileHandlerManager::DisableAutomaticFileHandlerCleanupForTesting();
}
content::WebContents* web_contents() {
return browser()->tab_strip_model()->GetActiveWebContents();
}
void SetUpOnMainThread() override {
WebAppFileHandlingTestBase::SetUpOnMainThread();
ASSERT_TRUE(https_server()->Start());
}
void SetUpInterceptorNavigateToAppAndMaybeWait() {
base::RunLoop loop;
file_handler_manager().SetOnFileHandlingExpiryUpdatedForTesting(
loop.QuitClosure());
web_contents()
->GetMainFrame()
->GetRemoteAssociatedInterfaces()
->OverrideBinderForTesting(
blink::mojom::FileHandlingExpiry::Name_,
base::BindRepeating(&FakeFileHandlingExpiryService::Bind,
base::Unretained(&file_handling_expiry_)));
NavigateInRenderer(web_contents(), GetSecureAppURL());
// The expiry time is only updated if the app is installed.
if (registrar().IsInstalled(app_id()))
loop.Run();
}
protected:
FakeFileHandlingExpiryService& file_handling_expiry() {
return file_handling_expiry_;
}
private:
FakeFileHandlingExpiryService file_handling_expiry_;
};
IN_PROC_BROWSER_TEST_P(WebAppFileHandlingOriginTrialBrowserTest,
FileHandlingIsNotAvailableUntilOriginTrialIsChecked) {
InstallFileHandlingPWA();
// We haven't navigated to the app, so we don't know if it's allowed to handle
// files.
EXPECT_FALSE(file_handler_manager().IsFileHandlingAPIAvailable(app_id()));
EXPECT_FALSE(file_handler_manager().AreFileHandlersEnabled(app_id()));
// Navigating to the app should update the origin trial expiry (and allow it
// to handle files).
SetUpInterceptorNavigateToAppAndMaybeWait();
EXPECT_TRUE(file_handler_manager().IsFileHandlingAPIAvailable(app_id()));
EXPECT_TRUE(file_handler_manager().AreFileHandlersEnabled(app_id()));
}
IN_PROC_BROWSER_TEST_P(WebAppFileHandlingOriginTrialBrowserTest,
FileHandlingOriginTrialIsCheckedAtInstallation) {
// Navigate to the app's launch url, so the origin trial token can be checked.
SetUpInterceptorNavigateToAppAndMaybeWait();
base::RunLoop loop;
file_handler_manager().SetOnFileHandlingExpiryUpdatedForTesting(
loop.QuitClosure());
InstallFileHandlingPWA();
loop.Run();
EXPECT_TRUE(file_handler_manager().IsFileHandlingAPIAvailable(app_id()));
EXPECT_TRUE(file_handler_manager().AreFileHandlersEnabled(app_id()));
}
IN_PROC_BROWSER_TEST_P(WebAppFileHandlingOriginTrialBrowserTest,
WhenOriginTrialHasExpiredFileHandlersAreNotAvailable) {
InstallFileHandlingPWA();
SetUpInterceptorNavigateToAppAndMaybeWait();
EXPECT_TRUE(file_handler_manager().AreFileHandlersEnabled(app_id()));
// Set the token's expiry to some time in the past.
file_handling_expiry().SetExpiryTime(base::Time());
// Refresh the page, to receive the updated expiry time.
SetUpInterceptorNavigateToAppAndMaybeWait();
EXPECT_FALSE(file_handler_manager().AreFileHandlersEnabled(app_id()));
EXPECT_FALSE(file_handler_manager().GetEnabledFileHandlers(app_id()));
}
// Tests that expired file handlers are cleaned up.
// Part 1: Install a file handling app and set it's expiry time to some time in
// the past.
IN_PROC_BROWSER_TEST_P(WebAppFileHandlingOriginTrialBrowserTest,
PRE_ExpiredTrialHandlersAreCleanedUpAtLaunch) {
InstallFileHandlingPWA();
SetUpInterceptorNavigateToAppAndMaybeWait();
EXPECT_TRUE(file_handler_manager().AreFileHandlersEnabled(app_id()));
// Update the expiry time to be in the past.
web_app::UpdateDoubleWebAppPref(profile()->GetPrefs(), app_id(),
web_app::kFileHandlingOriginTrialExpiryTime,
base::Time().ToDoubleT());
}
// Part 2: Test that expired file handlers for an app are cleaned up.
IN_PROC_BROWSER_TEST_P(WebAppFileHandlingOriginTrialBrowserTest,
ExpiredTrialHandlersAreCleanedUpAtLaunch) {
EXPECT_EQ(1, file_handler_manager().TriggerFileHandlerCleanupForTesting());
}
// Tests that non expired file handlers are not cleaned up.
// Part 1: Install an app with valid file handlers.
IN_PROC_BROWSER_TEST_P(WebAppFileHandlingOriginTrialBrowserTest,
PRE_ValidFileHandlerAreNotCleanedUpAtLaunch) {
InstallFileHandlingPWA();
SetUpInterceptorNavigateToAppAndMaybeWait();
EXPECT_TRUE(file_handler_manager().AreFileHandlersEnabled(app_id()));
}
// Part 2: Test that expired file handlers for an app are cleaned up.
IN_PROC_BROWSER_TEST_P(WebAppFileHandlingOriginTrialBrowserTest,
ValidFileHandlerAreNotCleanedUpAtLaunch) {
EXPECT_EQ(0, file_handler_manager().TriggerFileHandlerCleanupForTesting());
}
INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P(
All, All,
WebAppFileHandlingBrowserTest, WebAppFileHandlingBrowserTest,
...@@ -180,3 +365,12 @@ INSTANTIATE_TEST_SUITE_P( ...@@ -180,3 +365,12 @@ INSTANTIATE_TEST_SUITE_P(
web_app::ControllerType::kUnifiedControllerWithBookmarkApp, web_app::ControllerType::kUnifiedControllerWithBookmarkApp,
web_app::ControllerType::kUnifiedControllerWithWebApp), web_app::ControllerType::kUnifiedControllerWithWebApp),
web_app::ControllerTypeParamToString); web_app::ControllerTypeParamToString);
INSTANTIATE_TEST_SUITE_P(
All,
WebAppFileHandlingOriginTrialBrowserTest,
::testing::Values(
web_app::ControllerType::kHostedAppController,
web_app::ControllerType::kUnifiedControllerWithBookmarkApp,
web_app::ControllerType::kUnifiedControllerWithWebApp),
web_app::ControllerTypeParamToString);
...@@ -4,14 +4,25 @@ ...@@ -4,14 +4,25 @@
#include "chrome/browser/web_applications/components/file_handler_manager.h" #include "chrome/browser/web_applications/components/file_handler_manager.h"
#include "base/bind.h"
#include "base/feature_list.h" #include "base/feature_list.h"
#include "base/task/post_task.h"
#include "base/time/time.h"
#include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile.h"
#include "chrome/browser/web_applications/components/web_app_file_handler_registration.h" #include "chrome/browser/web_applications/components/web_app_file_handler_registration.h"
#include "chrome/browser/web_applications/components/web_app_prefs_utils.h" #include "chrome/browser/web_applications/components/web_app_prefs_utils.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_contents.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
#include "third_party/blink/public/common/features.h" #include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/mojom/web_launch/file_handling_expiry.mojom.h"
namespace web_app { namespace web_app {
bool FileHandlerManager::disable_automatic_file_handler_cleanup_for_testing_ =
false;
FileHandlerManager::FileHandlerManager(Profile* profile) FileHandlerManager::FileHandlerManager(Profile* profile)
: profile_(profile), registrar_observer_(this) {} : profile_(profile), registrar_observer_(this) {}
...@@ -25,12 +36,32 @@ void FileHandlerManager::Start() { ...@@ -25,12 +36,32 @@ void FileHandlerManager::Start() {
DCHECK(registrar_); DCHECK(registrar_);
registrar_observer_.Add(registrar_); registrar_observer_.Add(registrar_);
if (!FileHandlerManager::
disable_automatic_file_handler_cleanup_for_testing_) {
base::PostTask(
FROM_HERE,
{content::BrowserThread::UI, base::TaskPriority::BEST_EFFORT},
base::BindOnce(
base::IgnoreResult(&FileHandlerManager::CleanupAfterOriginTrials),
weak_ptr_factory_.GetWeakPtr()));
}
} }
void FileHandlerManager::DisableOsIntegrationForTesting() { void FileHandlerManager::DisableOsIntegrationForTesting() {
disable_os_integration_for_testing_ = true; disable_os_integration_for_testing_ = true;
} }
int FileHandlerManager::TriggerFileHandlerCleanupForTesting() {
return CleanupAfterOriginTrials();
}
void FileHandlerManager::SetOnFileHandlingExpiryUpdatedForTesting(
base::RepeatingCallback<void()> on_file_handling_expiry_updated) {
on_file_handling_expiry_updated_for_testing_ =
on_file_handling_expiry_updated;
}
void FileHandlerManager::EnableAndRegisterOsFileHandlers(const AppId& app_id) { void FileHandlerManager::EnableAndRegisterOsFileHandlers(const AppId& app_id) {
if (!IsFileHandlingAPIAvailable(app_id)) if (!IsFileHandlingAPIAvailable(app_id))
return; return;
...@@ -69,6 +100,20 @@ void FileHandlerManager::DisableAndUnregisterOsFileHandlers( ...@@ -69,6 +100,20 @@ void FileHandlerManager::DisableAndUnregisterOsFileHandlers(
UnregisterFileHandlersWithOs(app_id, profile()); UnregisterFileHandlersWithOs(app_id, profile());
} }
void FileHandlerManager::UpdateFileHandlingOriginTrialExpiry(
content::WebContents* web_contents,
const AppId& app_id) {
mojo::AssociatedRemote<blink::mojom::FileHandlingExpiry> expiry_service;
web_contents->GetMainFrame()->GetRemoteAssociatedInterfaces()->GetInterface(
&expiry_service);
DCHECK(expiry_service);
auto* raw = expiry_service.get();
raw->RequestOriginTrialExpiryTime(base::BindOnce(
&FileHandlerManager::OnOriginTrialExpiryTimeReceived,
weak_ptr_factory_.GetWeakPtr(), std::move(expiry_service), app_id));
}
const std::vector<apps::FileHandlerInfo>* const std::vector<apps::FileHandlerInfo>*
FileHandlerManager::GetEnabledFileHandlers(const AppId& app_id) { FileHandlerManager::GetEnabledFileHandlers(const AppId& app_id) {
if (AreFileHandlersEnabled(app_id) && IsFileHandlingAPIAvailable(app_id)) if (AreFileHandlersEnabled(app_id) && IsFileHandlingAPIAvailable(app_id))
...@@ -78,13 +123,62 @@ FileHandlerManager::GetEnabledFileHandlers(const AppId& app_id) { ...@@ -78,13 +123,62 @@ FileHandlerManager::GetEnabledFileHandlers(const AppId& app_id) {
} }
bool FileHandlerManager::IsFileHandlingAPIAvailable(const AppId& app_id) { bool FileHandlerManager::IsFileHandlingAPIAvailable(const AppId& app_id) {
return base::FeatureList::IsEnabled(blink::features::kFileHandlingAPI); return base::FeatureList::IsEnabled(blink::features::kFileHandlingAPI) ||
base::Time::FromDoubleT(GetDoubleWebAppPref(
profile()->GetPrefs(), app_id,
kFileHandlingOriginTrialExpiryTime)) >= base::Time::Now();
} }
bool FileHandlerManager::AreFileHandlersEnabled(const AppId& app_id) const { bool FileHandlerManager::AreFileHandlersEnabled(const AppId& app_id) const {
return GetBoolWebAppPref(profile()->GetPrefs(), app_id, kFileHandlersEnabled); return GetBoolWebAppPref(profile()->GetPrefs(), app_id, kFileHandlersEnabled);
} }
void FileHandlerManager::OnOriginTrialExpiryTimeReceived(
mojo::AssociatedRemote<blink::mojom::FileHandlingExpiry> /*interface*/,
const AppId& app_id,
base::Time expiry_time) {
web_app::UpdateDoubleWebAppPref(profile_->GetPrefs(), app_id,
kFileHandlingOriginTrialExpiryTime,
expiry_time.ToDoubleT());
// Only enable/disable file handlers if the state is changing, as
// enabling/disabling is a potentially expensive operation (it may involve
// creating an app shim, and will almost certainly involve IO).
const bool file_handlers_enabled = AreFileHandlersEnabled(app_id);
// If the trial is valid, ensure the file handlers are enabled.
// Otherwise disable them.
if (IsFileHandlingAPIAvailable(app_id)) {
if (!file_handlers_enabled)
EnableAndRegisterOsFileHandlers(app_id);
} else if (file_handlers_enabled) {
DisableAndUnregisterOsFileHandlers(app_id);
}
if (on_file_handling_expiry_updated_for_testing_)
on_file_handling_expiry_updated_for_testing_.Run();
}
void FileHandlerManager::DisableAutomaticFileHandlerCleanupForTesting() {
disable_automatic_file_handler_cleanup_for_testing_ = true;
}
int FileHandlerManager::CleanupAfterOriginTrials() {
int cleaned_up_count = 0;
for (const AppId& app_id : registrar_->GetAppIds()) {
if (!AreFileHandlersEnabled(app_id))
continue;
if (IsFileHandlingAPIAvailable(app_id))
continue;
// If the trial has expired, unregister handlers.
DisableAndUnregisterOsFileHandlers(app_id);
cleaned_up_count++;
}
return cleaned_up_count;
}
void FileHandlerManager::OnWebAppUninstalled(const AppId& app_id) { void FileHandlerManager::OnWebAppUninstalled(const AppId& app_id) {
DisableAndUnregisterOsFileHandlers(app_id); DisableAndUnregisterOsFileHandlers(app_id);
} }
......
...@@ -17,9 +17,15 @@ ...@@ -17,9 +17,15 @@
#include "chrome/browser/web_applications/components/app_shortcut_observer.h" #include "chrome/browser/web_applications/components/app_shortcut_observer.h"
#include "chrome/browser/web_applications/components/web_app_id.h" #include "chrome/browser/web_applications/components/web_app_id.h"
#include "components/services/app_service/public/cpp/file_handler_info.h" #include "components/services/app_service/public/cpp/file_handler_info.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "third_party/blink/public/mojom/web_launch/file_handling_expiry.mojom-forward.h"
class Profile; class Profile;
namespace content {
class WebContents;
}
namespace web_app { namespace web_app {
class FileHandlerManager : public AppRegistrarObserver { class FileHandlerManager : public AppRegistrarObserver {
...@@ -37,6 +43,20 @@ class FileHandlerManager : public AppRegistrarObserver { ...@@ -37,6 +43,20 @@ class FileHandlerManager : public AppRegistrarObserver {
// systems. // systems.
void DisableOsIntegrationForTesting(); void DisableOsIntegrationForTesting();
// This is needed because cleanup can run before the tests have finished
// setting up.
static void DisableAutomaticFileHandlerCleanupForTesting();
// Manually trigger file handler cleanup for tests. Returns the number of file
// handlers which were cleaned up. After the origin trial for file handling is
// completed this can be removed.
int TriggerFileHandlerCleanupForTesting();
// Set a callback which is fired when the file handling expiry time is
// updated.
void SetOnFileHandlingExpiryUpdatedForTesting(
base::RepeatingCallback<void()> on_file_handling_expiry_updated);
// Returns |app_id|'s URL registered to handle |launch_files|'s extensions, or // Returns |app_id|'s URL registered to handle |launch_files|'s extensions, or
// nullopt otherwise. // nullopt otherwise.
const base::Optional<GURL> GetMatchingFileHandlerURL( const base::Optional<GURL> GetMatchingFileHandlerURL(
...@@ -53,6 +73,11 @@ class FileHandlerManager : public AppRegistrarObserver { ...@@ -53,6 +73,11 @@ class FileHandlerManager : public AppRegistrarObserver {
// separately but they are still enabled and disabled here. // separately but they are still enabled and disabled here.
void DisableAndUnregisterOsFileHandlers(const AppId& app_id); void DisableAndUnregisterOsFileHandlers(const AppId& app_id);
// Updates the file handling origin trial expiry timer based on a currently
// open instance of the site.
void UpdateFileHandlingOriginTrialExpiry(content::WebContents* web_contents,
const AppId& app_id);
// Gets all enabled file handlers for |app_id|. |nullptr| if the app has no // Gets all enabled file handlers for |app_id|. |nullptr| if the app has no
// enabled file handlers. Note: The lifetime of the file handlers are tied to // enabled file handlers. Note: The lifetime of the file handlers are tied to
// the app they belong to. // the app they belong to.
...@@ -60,18 +85,17 @@ class FileHandlerManager : public AppRegistrarObserver { ...@@ -60,18 +85,17 @@ class FileHandlerManager : public AppRegistrarObserver {
const AppId& app_id); const AppId& app_id);
// Determines whether file handling is allowed for |app_id|. This is true if // Determines whether file handling is allowed for |app_id|. This is true if
// the FileHandlingAPI flag is enabled. // the app has a valid origin trial token for the file handling API or if the
// TODO(crbug.com/1028448): Also return true if there is a valid file handling // FileHandlingAPI flag is enabled.
// origin trial token for |app_id|.
bool IsFileHandlingAPIAvailable(const AppId& app_id); bool IsFileHandlingAPIAvailable(const AppId& app_id);
// Indicates whether file handlers have been registered for an app.
bool AreFileHandlersEnabled(const AppId& app_id) const;
protected: protected:
Profile* profile() const { return profile_; } Profile* profile() const { return profile_; }
AppRegistrar* registrar() { return registrar_; } AppRegistrar* registrar() { return registrar_; }
// Indicates whether file handlers have been registered for an app.
bool AreFileHandlersEnabled(const AppId& app_id) const;
// Gets all file handlers for |app_id|. |nullptr| if the app has no file // Gets all file handlers for |app_id|. |nullptr| if the app has no file
// handlers. // handlers.
// Note: The lifetime of the file handlers are tied to the app they belong to. // Note: The lifetime of the file handlers are tied to the app they belong to.
...@@ -79,11 +103,23 @@ class FileHandlerManager : public AppRegistrarObserver { ...@@ -79,11 +103,23 @@ class FileHandlerManager : public AppRegistrarObserver {
const AppId& app_id) = 0; const AppId& app_id) = 0;
private: private:
static bool disable_automatic_file_handler_cleanup_for_testing_;
bool disable_os_integration_for_testing_ = false; bool disable_os_integration_for_testing_ = false;
base::RepeatingCallback<void()> on_file_handling_expiry_updated_for_testing_;
Profile* const profile_; Profile* const profile_;
AppRegistrar* registrar_ = nullptr; AppRegistrar* registrar_ = nullptr;
void OnOriginTrialExpiryTimeReceived(
mojo::AssociatedRemote<blink::mojom::FileHandlingExpiry> /*interface*/,
const AppId& app_id,
base::Time expiry_time);
// Removes file handlers whose origin trials have expired (assuming
// kFileHandlingAPI isn't enabled). Returns the number of apps that had file
// handlers unregistered, for use in tests.
int CleanupAfterOriginTrials();
// AppRegistrarObserver: // AppRegistrarObserver:
void OnWebAppUninstalled(const AppId& app_id) override; void OnWebAppUninstalled(const AppId& app_id) override;
void OnWebAppProfileWillBeDeleted(const AppId& app_id) override; void OnWebAppProfileWillBeDeleted(const AppId& app_id) override;
...@@ -91,6 +127,8 @@ class FileHandlerManager : public AppRegistrarObserver { ...@@ -91,6 +127,8 @@ class FileHandlerManager : public AppRegistrarObserver {
ScopedObserver<AppRegistrar, AppRegistrarObserver> registrar_observer_; ScopedObserver<AppRegistrar, AppRegistrarObserver> registrar_observer_;
base::WeakPtrFactory<FileHandlerManager> weak_ptr_factory_{this};
DISALLOW_COPY_AND_ASSIGN(FileHandlerManager); DISALLOW_COPY_AND_ASSIGN(FileHandlerManager);
}; };
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#include "base/unguessable_token.h" #include "base/unguessable_token.h"
#include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile.h"
#include "chrome/browser/web_applications/components/file_handler_manager.h"
#include "chrome/browser/web_applications/components/policy/web_app_policy_manager.h" #include "chrome/browser/web_applications/components/policy/web_app_policy_manager.h"
#include "chrome/browser/web_applications/components/web_app_audio_focus_id_map.h" #include "chrome/browser/web_applications/components/web_app_audio_focus_id_map.h"
#include "chrome/browser/web_applications/components/web_app_provider_base.h" #include "chrome/browser/web_applications/components/web_app_provider_base.h"
...@@ -72,6 +73,8 @@ void WebAppTabHelper::DidFinishNavigation( ...@@ -72,6 +73,8 @@ void WebAppTabHelper::DidFinishNavigation(
if (!navigation_handle->IsInMainFrame() || !navigation_handle->HasCommitted()) if (!navigation_handle->IsInMainFrame() || !navigation_handle->HasCommitted())
return; return;
is_error_page_ = navigation_handle->IsErrorPage();
const GURL& url = navigation_handle->GetURL(); const GURL& url = navigation_handle->GetURL();
const AppId app_id = FindAppIdWithUrlInScope(url); const AppId app_id = FindAppIdWithUrlInScope(url);
SetAppId(app_id); SetAppId(app_id);
...@@ -81,6 +84,25 @@ void WebAppTabHelper::DidFinishNavigation( ...@@ -81,6 +84,25 @@ void WebAppTabHelper::DidFinishNavigation(
ReinstallPlaceholderAppIfNecessary(navigation_handle->GetURL()); ReinstallPlaceholderAppIfNecessary(navigation_handle->GetURL());
} }
void WebAppTabHelper::DOMContentLoaded(
content::RenderFrameHost* render_frame_host) {
if (render_frame_host != web_contents()->GetMainFrame())
return;
// Don't try and update the expiry time if this is an error page.
if (is_error_page_)
return;
// Don't try and manage file handlers unless this page is for an installed
// app.
if (app_id_.empty())
return;
// Update when the file handling origin trial expires for this app.
provider_->file_handler_manager().UpdateFileHandlingOriginTrialExpiry(
web_contents(), app_id_);
}
void WebAppTabHelper::DidCloneToNewWebContents( void WebAppTabHelper::DidCloneToNewWebContents(
content::WebContents* old_web_contents, content::WebContents* old_web_contents,
content::WebContents* new_web_contents) { content::WebContents* new_web_contents) {
...@@ -100,8 +122,15 @@ bool WebAppTabHelper::IsInAppWindow() const { ...@@ -100,8 +122,15 @@ bool WebAppTabHelper::IsInAppWindow() const {
void WebAppTabHelper::OnWebAppInstalled(const AppId& installed_app_id) { void WebAppTabHelper::OnWebAppInstalled(const AppId& installed_app_id) {
// Check if current web_contents url is in scope for the newly installed app. // Check if current web_contents url is in scope for the newly installed app.
AppId app_id = FindAppIdWithUrlInScope(web_contents()->GetURL()); AppId app_id = FindAppIdWithUrlInScope(web_contents()->GetURL());
if (app_id == installed_app_id) if (app_id != installed_app_id)
SetAppId(app_id); return;
SetAppId(app_id);
// When the app is installed record when its File Handling origin trial
// expires, so it can be removed.
provider_->file_handler_manager().UpdateFileHandlingOriginTrialExpiry(
web_contents(), installed_app_id);
} }
void WebAppTabHelper::OnWebAppUninstalled(const AppId& uninstalled_app_id) { void WebAppTabHelper::OnWebAppUninstalled(const AppId& uninstalled_app_id) {
......
...@@ -43,6 +43,7 @@ class WebAppTabHelper : public WebAppTabHelperBase, ...@@ -43,6 +43,7 @@ class WebAppTabHelper : public WebAppTabHelperBase,
// content::WebContentsObserver: // content::WebContentsObserver:
void DidFinishNavigation( void DidFinishNavigation(
content::NavigationHandle* navigation_handle) override; content::NavigationHandle* navigation_handle) override;
void DOMContentLoaded(content::RenderFrameHost* render_frame_host) override;
void DidCloneToNewWebContents( void DidCloneToNewWebContents(
content::WebContents* old_web_contents, content::WebContents* old_web_contents,
content::WebContents* new_web_contents) override; content::WebContents* new_web_contents) override;
...@@ -76,6 +77,11 @@ class WebAppTabHelper : public WebAppTabHelperBase, ...@@ -76,6 +77,11 @@ class WebAppTabHelper : public WebAppTabHelperBase,
// WebApp associated with this tab. Empty string if no app associated. // WebApp associated with this tab. Empty string if no app associated.
AppId app_id_; AppId app_id_;
// Indicates if the current page is an error page (e.g. the page failed to
// load). We store this because it isn't accessible off a |WebContents| or a
// |RenderFrameHost|.
bool is_error_page_ = false;
// The audio focus group id is used to group media sessions together for apps. // The audio focus group id is used to group media sessions together for apps.
// We store the applied group id locally on the helper for testing. // We store the applied group id locally on the helper for testing.
base::UnguessableToken audio_focus_group_id_ = base::UnguessableToken::Null(); base::UnguessableToken audio_focus_group_id_ = base::UnguessableToken::Null();
......
...@@ -145,6 +145,7 @@ mojom("mojom_platform") { ...@@ -145,6 +145,7 @@ mojom("mojom_platform") {
"user_agent/user_agent_metadata.mojom", "user_agent/user_agent_metadata.mojom",
"v8_cache_options.mojom", "v8_cache_options.mojom",
"wake_lock/wake_lock.mojom", "wake_lock/wake_lock.mojom",
"web_launch/file_handling_expiry.mojom",
"web_launch/web_launch.mojom", "web_launch/web_launch.mojom",
"webaudio/audio_context_manager.mojom", "webaudio/audio_context_manager.mojom",
"webdatabase/web_database.mojom", "webdatabase/web_database.mojom",
......
// Copyright 2020 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.
module blink.mojom;
import "mojo/public/mojom/base/time.mojom";
// Interface for finding when the file handling origin trial for a WebApp will
// expire. This information is needed because file handlers are registered
// outside Chromium, in the underlying operating system, so we need to know
// when the trial expires so that they can be cleaned up.
//
// This service is hosted in the renderer. The browser process is responsible
// for acquiring this interface and calling it.
interface FileHandlingExpiry {
// Gets the time that the FileHandlingAPI origin trial will expire. If there
// is a valid token, this will return the time the token expires. Otherwise
// it will return base::Time().
RequestOriginTrialExpiryTime() => (mojo_base.mojom.Time expiry_time);
};
\ No newline at end of file
...@@ -8,6 +8,8 @@ blink_modules_sources("launch") { ...@@ -8,6 +8,8 @@ blink_modules_sources("launch") {
sources = [ sources = [
"dom_window_launch_queue.cc", "dom_window_launch_queue.cc",
"dom_window_launch_queue.h", "dom_window_launch_queue.h",
"file_handling_expiry_impl.cc",
"file_handling_expiry_impl.h",
"launch_params.cc", "launch_params.cc",
"launch_params.h", "launch_params.h",
"launch_queue.cc", "launch_queue.cc",
......
// 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 "third_party/blink/renderer/modules/launch/file_handling_expiry_impl.h"
#include <memory>
#include "mojo/public/cpp/bindings/self_owned_associated_receiver.h"
#include "third_party/blink/public/mojom/native_file_system/native_file_system_directory_handle.mojom-blink.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/origin_trials/origin_trial_context.h"
#include "third_party/blink/renderer/core/script/script.h"
#include "third_party/blink/renderer/modules/launch/dom_window_launch_queue.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/heap/heap_allocator.h"
#include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h"
namespace blink {
void FileHandlingExpiryImpl::Create(
LocalFrame* frame,
mojo::PendingAssociatedReceiver<mojom::blink::FileHandlingExpiry>
receiver) {
DCHECK(frame);
mojo::MakeSelfOwnedAssociatedReceiver(
std::make_unique<FileHandlingExpiryImpl>(*frame->DomWindow()),
std::move(receiver));
}
FileHandlingExpiryImpl::FileHandlingExpiryImpl(LocalDOMWindow& window)
: window_(window) {}
FileHandlingExpiryImpl::~FileHandlingExpiryImpl() = default;
void FileHandlingExpiryImpl::RequestOriginTrialExpiryTime(
RequestOriginTrialExpiryTimeCallback callback) {
if (!window_)
return;
auto* origin_trials = window_->GetExecutionContext()->GetOriginTrialContext();
base::Time expiry_time =
origin_trials->GetFeatureExpiry(OriginTrialFeature::kFileHandling);
std::move(callback).Run(expiry_time);
}
} // namespace blink
// 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.
#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_LAUNCH_FILE_HANDLING_EXPIRY_IMPL_H_
#define THIRD_PARTY_BLINK_RENDERER_MODULES_LAUNCH_FILE_HANDLING_EXPIRY_IMPL_H_
#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
#include "third_party/blink/public/mojom/web_launch/file_handling_expiry.mojom-blink.h"
#include "third_party/blink/renderer/modules/modules_export.h"
#include "third_party/blink/renderer/platform/heap/persistent.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"
namespace blink {
class LocalFrame;
class LocalDOMWindow;
// Implementation of FileHandlingExpiry service, to allow the browser to query
// the expiry time of the file handling origin trial for a Document.
class MODULES_EXPORT FileHandlingExpiryImpl final
: public mojom::blink::FileHandlingExpiry {
public:
static void Create(
LocalFrame* frame,
mojo::PendingAssociatedReceiver<mojom::blink::FileHandlingExpiry>);
explicit FileHandlingExpiryImpl(LocalDOMWindow& frame);
~FileHandlingExpiryImpl() override;
// blink::mojom::FileHandlingExpiry:
void RequestOriginTrialExpiryTime(
RequestOriginTrialExpiryTimeCallback callback) override;
private:
WeakPersistent<LocalDOMWindow> window_;
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_MODULES_LAUNCH_FILE_HANDLING_EXPIRY_IMPL_H_
...@@ -59,6 +59,7 @@ ...@@ -59,6 +59,7 @@
#include "third_party/blink/renderer/modules/indexeddb/inspector_indexed_db_agent.h" #include "third_party/blink/renderer/modules/indexeddb/inspector_indexed_db_agent.h"
#include "third_party/blink/renderer/modules/installation/installation_service_impl.h" #include "third_party/blink/renderer/modules/installation/installation_service_impl.h"
#include "third_party/blink/renderer/modules/installedapp/installed_app_controller.h" #include "third_party/blink/renderer/modules/installedapp/installed_app_controller.h"
#include "third_party/blink/renderer/modules/launch/file_handling_expiry_impl.h"
#include "third_party/blink/renderer/modules/launch/web_launch_service_impl.h" #include "third_party/blink/renderer/modules/launch/web_launch_service_impl.h"
#include "third_party/blink/renderer/modules/manifest/manifest_manager.h" #include "third_party/blink/renderer/modules/manifest/manifest_manager.h"
#include "third_party/blink/renderer/modules/media_controls/media_controls_impl.h" #include "third_party/blink/renderer/modules/media_controls/media_controls_impl.h"
...@@ -166,6 +167,9 @@ void ModulesInitializer::InitLocalFrame(LocalFrame& frame) const { ...@@ -166,6 +167,9 @@ void ModulesInitializer::InitLocalFrame(LocalFrame& frame) const {
frame.GetInterfaceRegistry()->AddAssociatedInterface(WTF::BindRepeating( frame.GetInterfaceRegistry()->AddAssociatedInterface(WTF::BindRepeating(
&WebLaunchServiceImpl::Create, WrapWeakPersistent(&frame))); &WebLaunchServiceImpl::Create, WrapWeakPersistent(&frame)));
} }
frame.GetInterfaceRegistry()->AddAssociatedInterface(WTF::BindRepeating(
&FileHandlingExpiryImpl::Create, WrapWeakPersistent(&frame)));
frame.GetInterfaceRegistry()->AddInterface(WTF::BindRepeating( frame.GetInterfaceRegistry()->AddInterface(WTF::BindRepeating(
&InstallationServiceImpl::Create, WrapWeakPersistent(&frame))); &InstallationServiceImpl::Create, WrapWeakPersistent(&frame)));
// TODO(dominickn): This interface should be document-scoped rather than // TODO(dominickn): This interface should be document-scoped rather than
......
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