blob: 73ebad8250248c02a4a9fa8a1bc22f00ed8621a1 [file] [log] [blame]
// 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 "base/bind.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/strings/strcat.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/threading/thread_restrictions.h"
#include "base/values.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/apps/app_service/app_launch_params.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/apps/app_service/browser_app_launcher.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/web_applications/file_handling_permission_request_dialog_test_api.h"
#include "chrome/browser/ui/web_applications/web_app_controller_browsertest.h"
#include "chrome/browser/ui/webui/settings/site_settings_helper.h"
#include "chrome/browser/web_applications/components/file_handler_manager.h"
#include "chrome/browser/web_applications/components/os_integration_manager.h"
#include "chrome/browser/web_applications/components/web_app_constants.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/browser/web_applications/components/web_app_utils.h"
#include "chrome/browser/web_applications/components/web_application_info.h"
#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
#include "chrome/browser/web_applications/web_app.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/embedder_support/switches.h"
#include "components/permissions/test/permission_request_observer.h"
#include "components/policy/core/browser/browser_policy_connector.h"
#include "components/policy/core/common/mock_configuration_policy_provider.h"
#include "components/policy/core/common/policy_map.h"
#include "components/policy/policy_constants.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/url_loader_interceptor.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/mojom/web_launch/file_handling_expiry.mojom-test-utils.h"
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "chrome/browser/ash/file_manager/file_manager_test_util.h"
#endif
namespace web_app {
// A fake file handling expiry service. This service allows us to mock having an
// origin trial which expires having a certain time, without needing to manage
// actual origin trial tokens.
class FakeFileHandlingExpiryService
: public blink::mojom::FileHandlingExpiryInterceptorForTesting {
public:
FakeFileHandlingExpiryService()
: expiry_time_(base::Time::Now() + base::TimeDelta::FromDays(1)) {}
blink::mojom::FileHandlingExpiry* GetForwardingInterface() override {
NOTREACHED();
return nullptr;
}
void Bind(mojo::ScopedInterfaceEndpointHandle handle) {
receiver_.reset();
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 {
if (before_reply_callback_) {
std::move(before_reply_callback_).Run();
}
std::move(callback).Run(expiry_time_);
}
// Set a callback to be called before FileHandlingExpiry interface replies
// the expiry time. Useful for testing inflight IPC.
void SetBeforeReplyCallback(base::RepeatingClosure before_reply_callback) {
before_reply_callback_ = before_reply_callback;
}
private:
base::Time expiry_time_;
RequestOriginTrialExpiryTimeCallback callback_;
base::RepeatingClosure before_reply_callback_;
mojo::AssociatedReceiver<blink::mojom::FileHandlingExpiry> receiver_{this};
};
class WebAppFileHandlingTestBase : public WebAppControllerBrowserTest {
public:
WebAppProviderBase* provider() {
return WebAppProviderBase::GetProviderBase(profile());
}
FileHandlerManager& file_handler_manager() {
return provider()
->os_integration_manager()
.file_handler_manager_for_testing();
}
AppRegistrar& registrar() { return provider()->registrar(); }
GURL GetSecureAppURL() {
return https_server()->GetURL("app.com", "/ssl/google.html");
}
GURL GetTextFileHandlerActionURL() {
return https_server()->GetURL("app.com", "/ssl/blank_page.html");
}
GURL GetCSVFileHandlerActionURL() {
return https_server()->GetURL("app.com", "/ssl/page_with_refs.html");
}
GURL GetHTMLFileHandlerActionURL() {
return https_server()->GetURL("app.com", "/ssl/page_with_frame.html");
}
void InstallFileHandlingPWA() {
GURL url = GetSecureAppURL();
auto web_app_info = std::make_unique<WebApplicationInfo>();
web_app_info->start_url = url;
web_app_info->scope = url.GetWithoutFilename();
web_app_info->title = u"A Hosted App";
// Basic plain text format.
blink::Manifest::FileHandler entry1;
entry1.action = GetTextFileHandlerActionURL();
entry1.name = u"text";
entry1.accept[u"text/*"].push_back(u".txt");
web_app_info->file_handlers.push_back(std::move(entry1));
// A format that the browser is also a handler for, to confirm that the
// browser doesn't override PWAs using File Handling for types that the
// browser also handles.
blink::Manifest::FileHandler entry2;
entry2.action = GetHTMLFileHandlerActionURL();
entry2.name = u"html";
entry2.accept[u"text/html"].push_back(u".html");
web_app_info->file_handlers.push_back(std::move(entry2));
// application/* format.
blink::Manifest::FileHandler entry3;
entry3.action = GetCSVFileHandlerActionURL();
entry3.name = u"csv";
entry3.accept[u"application/csv"].push_back(u".csv");
web_app_info->file_handlers.push_back(std::move(entry3));
app_id_ =
WebAppControllerBrowserTest::InstallWebApp(std::move(web_app_info));
}
void InstallAnotherFileHandlingPwa(const GURL& start_url) {
auto web_app_info = std::make_unique<WebApplicationInfo>();
web_app_info->start_url = start_url;
web_app_info->scope = start_url.GetWithoutFilename();
web_app_info->title = u"A second app";
// This one handles jpegs.
blink::Manifest::FileHandler entry1;
entry1.action = GetTextFileHandlerActionURL();
entry1.name = u"jpeg";
entry1.accept[u"image/jpeg"].push_back(u".jpeg");
web_app_info->file_handlers.push_back(std::move(entry1));
WebAppControllerBrowserTest::InstallWebApp(std::move(web_app_info));
}
protected:
ContentSetting GetFileHandlingPermission(const GURL& url) {
auto* map =
HostContentSettingsMapFactory::GetForProfile(browser()->profile());
return map->GetContentSetting(url, url, ContentSettingsType::FILE_HANDLING);
}
void SetFileHandlingPermission(ContentSetting setting) {
auto* map =
HostContentSettingsMapFactory::GetForProfile(browser()->profile());
map->SetDefaultContentSetting(ContentSettingsType::FILE_HANDLING, setting);
}
const AppId& app_id() { return app_id_; }
private:
AppId app_id_;
};
namespace {
base::FilePath NewTestFilePath(const base::StringPiece 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.AddExtensionASCII(extension);
EXPECT_TRUE(base::ReplaceFile(test_file_path, new_file_path, nullptr));
return new_file_path;
}
// Launches the |app_id| web app with |files| handles, awaits for
// |expected_launch_url| to load and stashes any launch params on
// "window.launchParams" for further inspection.
content::WebContents* LaunchApplication(
Profile* profile,
const std::string& app_id,
const GURL& expected_launch_url,
const apps::mojom::LaunchContainer launch_container =
apps::mojom::LaunchContainer::kLaunchContainerWindow,
const apps::mojom::AppLaunchSource launch_source =
apps::mojom::AppLaunchSource::kSourceTest,
const std::vector<base::FilePath>& files = std::vector<base::FilePath>()) {
apps::AppLaunchParams params(app_id, launch_container,
WindowOpenDisposition::NEW_WINDOW,
launch_source);
if (files.size())
params.launch_files = files;
content::TestNavigationObserver navigation_observer(expected_launch_url);
navigation_observer.StartWatchingNewWebContents();
content::WebContents* web_contents =
apps::AppServiceProxyFactory::GetForProfile(profile)
->BrowserAppLauncher()
->LaunchAppWithParams(std::move(params));
navigation_observer.Wait();
// Attach the launchParams to the window so we can inspect them easily.
auto result = content::EvalJs(web_contents,
"launchQueue.setConsumer(launchParams => {"
" window.launchParams = launchParams;"
"});");
return web_contents;
}
} // namespace
class WebAppFileHandlingBrowserTest : public WebAppFileHandlingTestBase {
public:
WebAppFileHandlingBrowserTest() {
scoped_feature_list_.InitWithFeatures({blink::features::kFileHandlingAPI},
{});
}
void LaunchWithFiles(
const std::string& app_id,
const GURL& expected_launch_url,
const std::vector<base::FilePath>& files,
const apps::mojom::LaunchContainer launch_container =
apps::mojom::LaunchContainer::kLaunchContainerWindow) {
web_contents_ = LaunchApplication(
profile(), app_id, expected_launch_url, launch_container,
apps::mojom::AppLaunchSource::kSourceFileHandler, files);
}
void VerifyPwaDidReceiveFileLaunchParams(
bool expect_got_launch_params,
const base::FilePath& expected_file_path = {}) {
bool got_launch_params =
content::EvalJs(web_contents_, "!!window.launchParams").ExtractBool();
ASSERT_EQ(expect_got_launch_params, got_launch_params);
if (got_launch_params) {
EXPECT_EQ(1, content::EvalJs(web_contents_,
"window.launchParams.files.length"));
EXPECT_EQ(
expected_file_path.BaseName().AsUTF8Unsafe(),
content::EvalJs(web_contents_, "window.launchParams.files[0].name"));
}
}
protected:
base::test::ScopedFeatureList scoped_feature_list_;
content::WebContents* web_contents_ = nullptr;
};
IN_PROC_BROWSER_TEST_F(WebAppFileHandlingBrowserTest,
LaunchConsumerIsNotTriggeredWithNoFiles) {
InstallFileHandlingPWA();
SetFileHandlingPermission(CONTENT_SETTING_ALLOW);
LaunchWithFiles(app_id(), GetSecureAppURL(), {});
VerifyPwaDidReceiveFileLaunchParams(false);
}
IN_PROC_BROWSER_TEST_F(WebAppFileHandlingBrowserTest,
PWAsCanReceiveFileLaunchParams) {
InstallFileHandlingPWA();
SetFileHandlingPermission(CONTENT_SETTING_ALLOW);
base::FilePath test_file_path = NewTestFilePath("txt");
LaunchWithFiles(app_id(), GetTextFileHandlerActionURL(), {test_file_path});
VerifyPwaDidReceiveFileLaunchParams(true, test_file_path);
}
IN_PROC_BROWSER_TEST_F(WebAppFileHandlingBrowserTest,
LaunchConsumerIsNotTriggeredWithPermissionDenied) {
InstallFileHandlingPWA();
SetFileHandlingPermission(CONTENT_SETTING_BLOCK);
base::FilePath test_file_path = NewTestFilePath("txt");
LaunchWithFiles(app_id(), GetTextFileHandlerActionURL(), {test_file_path});
VerifyPwaDidReceiveFileLaunchParams(false);
}
IN_PROC_BROWSER_TEST_F(WebAppFileHandlingBrowserTest,
PWAsCanReceiveFileLaunchParamsInTab) {
InstallFileHandlingPWA();
SetFileHandlingPermission(CONTENT_SETTING_ALLOW);
base::FilePath test_file_path = NewTestFilePath("txt");
LaunchWithFiles(app_id(), GetTextFileHandlerActionURL(), {test_file_path},
apps::mojom::LaunchContainer::kLaunchContainerTab);
VerifyPwaDidReceiveFileLaunchParams(true, test_file_path);
}
IN_PROC_BROWSER_TEST_F(WebAppFileHandlingBrowserTest,
PWAsDispatchOnCorrectFileHandlingURL) {
InstallFileHandlingPWA();
SetFileHandlingPermission(CONTENT_SETTING_ALLOW);
// Test that file handler dispatches correct URL based on file extension.
LaunchWithFiles(app_id(), GetSecureAppURL(), {});
LaunchWithFiles(app_id(), GetTextFileHandlerActionURL(),
{NewTestFilePath("txt")});
LaunchWithFiles(app_id(), GetHTMLFileHandlerActionURL(),
{NewTestFilePath("html")});
LaunchWithFiles(app_id(), GetCSVFileHandlerActionURL(),
{NewTestFilePath("csv")});
// Test as above in a tab.
LaunchWithFiles(app_id(), GetSecureAppURL(), {},
apps::mojom::LaunchContainer::kLaunchContainerTab);
LaunchWithFiles(app_id(), GetTextFileHandlerActionURL(),
{NewTestFilePath("txt")},
apps::mojom::LaunchContainer::kLaunchContainerTab);
LaunchWithFiles(app_id(), GetHTMLFileHandlerActionURL(),
{NewTestFilePath("html")},
apps::mojom::LaunchContainer::kLaunchContainerTab);
LaunchWithFiles(app_id(), GetCSVFileHandlerActionURL(),
{NewTestFilePath("csv")},
apps::mojom::LaunchContainer::kLaunchContainerTab);
}
// Regression test for crbug.com/1205528
IN_PROC_BROWSER_TEST_F(WebAppFileHandlingBrowserTest,
LaunchParamsEmptyIfFileUnhandled) {
InstallFileHandlingPWA();
SetFileHandlingPermission(CONTENT_SETTING_ALLOW);
// Test that file handler dispatches to the normal start URL when the file
// path is not a handled file type, and `launchParams` remains undefined.
LaunchWithFiles(app_id(), GetSecureAppURL(), {NewTestFilePath("png")});
VerifyPwaDidReceiveFileLaunchParams(false);
}
// Regression test for crbug.com/1126091
IN_PROC_BROWSER_TEST_F(WebAppFileHandlingBrowserTest,
LaunchQueueSetOnRedirect) {
SetFileHandlingPermission(CONTENT_SETTING_ALLOW);
// Install an app where the file handling action page redirects.
auto web_app_info = std::make_unique<WebApplicationInfo>();
web_app_info->start_url =
https_server()->GetURL("app.com", "/web_app_file_handling/index.html");
web_app_info->scope = web_app_info->start_url.GetWithoutFilename();
web_app_info->title = u"An app that redirects";
blink::Manifest::FileHandler entry;
entry.action = https_server()->GetURL(
"app.com", "/web_app_file_handling/handle_files_with_redirect.html");
entry.name = u"text";
entry.accept[u"text/*"].push_back(u".txt");
web_app_info->file_handlers.push_back(std::move(entry));
AppId app_id =
WebAppControllerBrowserTest::InstallWebApp(std::move(web_app_info));
// The redirect points to handle_files.html, so wait till that navigation is
// finished.
base::FilePath file = NewTestFilePath("txt");
LaunchWithFiles(app_id,
https_server()->GetURL(
"app.com", "/web_app_file_handling/handle_files.html"),
{file});
// The redirected-to page should get the launch queue.
VerifyPwaDidReceiveFileLaunchParams(true, file);
}
IN_PROC_BROWSER_TEST_F(WebAppFileHandlingBrowserTest,
LaunchQueueNotSetOnCrossOriginRedirect) {
SetFileHandlingPermission(CONTENT_SETTING_ALLOW);
// Install an app where the file handling action page redirects to a page on a
// different origin.
auto web_app_info = std::make_unique<WebApplicationInfo>();
web_app_info->start_url =
https_server()->GetURL("app.com", "/web_app_file_handling/index.html");
web_app_info->scope = web_app_info->start_url.GetWithoutFilename();
web_app_info->title = u"An app that redirects to a different origin";
blink::Manifest::FileHandler entry;
entry.action = https_server()->GetURL(
"app.com",
"/web_app_file_handling/handle_files_with_redirect_to_other_origin.html");
entry.name = u"text";
entry.accept[u"text/*"].push_back(u".txt");
web_app_info->file_handlers.push_back(std::move(entry));
AppId app_id =
WebAppControllerBrowserTest::InstallWebApp(std::move(web_app_info));
// The redirect points to handle_files.html with a different origin, so wait
// till that navigation is finished.
base::FilePath file = NewTestFilePath("txt");
LaunchWithFiles(
app_id,
https_server()->GetURL("example.com",
"/web_app_file_handling/handle_files.html"),
{file});
// The redirected-to page should NOT get the launch queue.
VerifyPwaDidReceiveFileLaunchParams(false);
}
// Disabled due to flakiness on Linux bots. http://crbug.com/1207370
#if defined(OS_LINUX)
#define MAYBE_UnlimitedFileHandlersForChrome \
DISABLED_UnlimitedFileHandlersForChrome
#else
#define MAYBE_UnlimitedFileHandlersForChrome UnlimitedFileHandlersForChrome
#endif
IN_PROC_BROWSER_TEST_F(WebAppFileHandlingBrowserTest,
MAYBE_UnlimitedFileHandlersForChrome) {
// We install more than |kMaxFileHandlers| file handlers.
const unsigned kNumHandlers = 2 * kMaxFileHandlers + 1;
auto action_url = [](unsigned index) {
return GURL(base::StringPrintf("chrome://interstitials/#a%u", index));
};
auto mime_type = [](unsigned index) {
return base::StringPrintf("application/x-%u", index);
};
auto extension = [](unsigned index) {
return base::StringPrintf(".e%u", index);
};
AppId app_id;
{
auto web_app_info = std::make_unique<WebApplicationInfo>();
web_app_info->start_url = GURL("chrome://interstitials/");
web_app_info->scope = web_app_info->start_url;
web_app_info->title = u"Many File Handlers";
for (unsigned i = 0; i < kNumHandlers; ++i) {
const std::u16string name =
base::UTF8ToUTF16(base::StringPrintf("n%u", i));
std::map<std::u16string, std::vector<std::u16string>> accept;
accept[base::UTF8ToUTF16(mime_type(i))] = {
base::UTF8ToUTF16(extension(i))};
web_app_info->file_handlers.push_back(
{action_url(i), name, std::vector<blink::Manifest::ImageResource>(),
std::move(accept)});
}
app_id =
WebAppControllerBrowserTest::InstallWebApp(std::move(web_app_info));
}
EXPECT_EQ(registrar()
.AsWebAppRegistrar()
->GetAppById(app_id)
->file_handlers()
.size(),
kNumHandlers);
SetFileHandlingPermission(CONTENT_SETTING_ALLOW);
// Test that file handler dispatches correct URL based on file extension.
for (unsigned i = 0; i < kNumHandlers; ++i) {
LaunchWithFiles(app_id, action_url(i), {NewTestFilePath(extension(i))});
}
}
// Tests that when two apps are installed and share an origin (but not scope),
// `GetFileHandlersForAllWebAppsWithOrigin` will report all the file handlers
// across both apps.
IN_PROC_BROWSER_TEST_F(WebAppFileHandlingBrowserTest,
FileHandlerAggregationForUi) {
InstallFileHandlingPWA();
EXPECT_EQ(3U,
GetFileHandlersForAllWebAppsWithOrigin(profile(), GetSecureAppURL())
.size());
GURL second_app_url = https_server()->GetURL("app.com", "/pwa/app2.html");
InstallAnotherFileHandlingPwa(second_app_url);
EXPECT_EQ(2U, registrar().GetAppIds().size());
EXPECT_EQ(4U,
GetFileHandlersForAllWebAppsWithOrigin(profile(), GetSecureAppURL())
.size());
EXPECT_EQ(
4U,
GetFileHandlersForAllWebAppsWithOrigin(profile(), second_app_url).size());
std::u16string display_string_app1 =
GetFileTypeAssociationsHandledByWebAppsForDisplay(profile(),
GetSecureAppURL());
std::u16string display_string_app2 =
GetFileTypeAssociationsHandledByWebAppsForDisplay(profile(),
second_app_url);
EXPECT_EQ(display_string_app1, display_string_app2);
#if defined(OS_LINUX)
const std::u16string kHtmlDisplayString = u"text/html";
const std::u16string kJpegDisplayString = u"image/jpeg";
#else
const std::u16string kHtmlDisplayString = u"HTML";
const std::u16string kJpegDisplayString = u"JPEG";
#endif
EXPECT_NE(std::u16string::npos, display_string_app1.find(kHtmlDisplayString));
EXPECT_NE(std::u16string::npos, display_string_app1.find(kJpegDisplayString));
}
IN_PROC_BROWSER_TEST_F(WebAppFileHandlingBrowserTest,
SometimesResetPermission) {
// Install the first app and simulate the user granting it the file handling
// permission.
InstallFileHandlingPWA();
auto* map = HostContentSettingsMapFactory::GetForProfile(profile());
const GURL origin = GetSecureAppURL().GetOrigin();
EXPECT_EQ(CONTENT_SETTING_ASK,
map->GetContentSetting(origin, origin,
ContentSettingsType::FILE_HANDLING));
map->SetContentSettingDefaultScope(origin, origin,
ContentSettingsType::FILE_HANDLING,
CONTENT_SETTING_ALLOW);
EXPECT_EQ(CONTENT_SETTING_ALLOW,
map->GetContentSetting(origin, origin,
ContentSettingsType::FILE_HANDLING));
// Install a second app, which is on the same origin and asks to handle more
// file types. The permission should have been set back to ASK.
GURL second_app_url = https_server()->GetURL("app.com", "/pwa/app2.html");
InstallAnotherFileHandlingPwa(second_app_url);
EXPECT_EQ(CONTENT_SETTING_ASK,
map->GetContentSetting(origin, origin,
ContentSettingsType::FILE_HANDLING));
// Set to ALLOW again.
map->SetContentSettingDefaultScope(origin, origin,
ContentSettingsType::FILE_HANDLING,
CONTENT_SETTING_ALLOW);
// Install a third app, which is on a different origin; this should have no
// effect on the permission.
GURL third_app_url = https_server()->GetURL("otherapp.com", "/pwa/app2.html");
InstallAnotherFileHandlingPwa(third_app_url);
EXPECT_EQ(CONTENT_SETTING_ALLOW,
map->GetContentSetting(origin, origin,
ContentSettingsType::FILE_HANDLING));
GURL third_app_origin = third_app_url.GetOrigin();
EXPECT_EQ(CONTENT_SETTING_ASK,
map->GetContentSetting(third_app_origin, third_app_origin,
ContentSettingsType::FILE_HANDLING));
// Install a fourth app, which is on the same origin but asks for a subset of
// the file types of the first two. This should have no effect on the
// permission.
GURL fourth_app_url = https_server()->GetURL("app.com", "/pwa2/app2.html");
InstallAnotherFileHandlingPwa(fourth_app_url);
EXPECT_EQ(CONTENT_SETTING_ALLOW,
map->GetContentSetting(origin, origin,
ContentSettingsType::FILE_HANDLING));
}
class WebAppFileHandlingPermissionDialogTest
: public WebAppFileHandlingBrowserTest {
public:
WebAppFileHandlingPermissionDialogTest() {
scoped_feature_list_.InitWithFeatures(
{features::kFileHandlingPermissionUiV2}, {});
}
void SetUpOnMainThread() override {
WebAppFileHandlingBrowserTest::SetUpOnMainThread();
InstallFileHandlingPWA();
SetFileHandlingPermission(CONTENT_SETTING_ASK);
EXPECT_FALSE(FileHandlingPermissionRequestDialogTestApi::IsShowing());
test_file_path_ = NewTestFilePath("txt");
LaunchWithFiles(app_id(), GetTextFileHandlerActionURL(), {test_file_path_});
// The permission request is dequeued asynchronously. It may or may not be
// showing by now.
if (!FileHandlingPermissionRequestDialogTestApi::IsShowing())
permissions::PermissionRequestObserver(web_contents_).Wait();
// A dialog is showing now.
ASSERT_TRUE(FileHandlingPermissionRequestDialogTestApi::IsShowing());
// The launch consumer isn't triggered while the dialog is showing.
VerifyPwaDidReceiveFileLaunchParams(false);
}
protected:
base::test::ScopedFeatureList scoped_feature_list_;
base::FilePath test_file_path_;
};
IN_PROC_BROWSER_TEST_F(WebAppFileHandlingPermissionDialogTest, AllowAlways) {
FileHandlingPermissionRequestDialogTestApi::Resolve(/*checked=*/true,
/*accept=*/true);
VerifyPwaDidReceiveFileLaunchParams(true, test_file_path_);
EXPECT_EQ(CONTENT_SETTING_ALLOW,
GetFileHandlingPermission(GetSecureAppURL()));
}
IN_PROC_BROWSER_TEST_F(WebAppFileHandlingPermissionDialogTest, AllowOnce) {
FileHandlingPermissionRequestDialogTestApi::Resolve(/*checked=*/false,
/*accept=*/true);
VerifyPwaDidReceiveFileLaunchParams(true, test_file_path_);
EXPECT_EQ(CONTENT_SETTING_ASK, GetFileHandlingPermission(GetSecureAppURL()));
}
IN_PROC_BROWSER_TEST_F(WebAppFileHandlingPermissionDialogTest, BlockAlways) {
FileHandlingPermissionRequestDialogTestApi::Resolve(/*checked=*/true,
/*accept=*/false);
VerifyPwaDidReceiveFileLaunchParams(false);
EXPECT_EQ(CONTENT_SETTING_BLOCK,
GetFileHandlingPermission(GetSecureAppURL()));
}
IN_PROC_BROWSER_TEST_F(WebAppFileHandlingPermissionDialogTest, BlockOnce) {
FileHandlingPermissionRequestDialogTestApi::Resolve(/*checked=*/false,
/*accept=*/false);
VerifyPwaDidReceiveFileLaunchParams(false);
EXPECT_EQ(CONTENT_SETTING_ASK, GetFileHandlingPermission(GetSecureAppURL()));
}
class WebAppFileHandlingOriginTrialBrowserTest
: public WebAppFileHandlingTestBase {
public:
WebAppFileHandlingOriginTrialBrowserTest() {
FileHandlerManager::DisableAutomaticFileHandlerCleanupForTesting();
}
content::WebContents* web_contents() {
return browser()->tab_strip_model()->GetActiveWebContents();
}
void SetUpOnMainThread() override {
WebAppFileHandlingTestBase::SetUpOnMainThread();
}
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_F(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_F(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_F(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_F(WebAppFileHandlingOriginTrialBrowserTest,
PRE_ExpiredTrialHandlersAreCleanedUpAtLaunch) {
InstallFileHandlingPWA();
SetUpInterceptorNavigateToAppAndMaybeWait();
EXPECT_TRUE(file_handler_manager().AreFileHandlersEnabled(app_id()));
// Update the expiry time to be in the past.
UpdateDoubleWebAppPref(profile()->GetPrefs(), app_id(),
kFileHandlingOriginTrialExpiryTime,
base::Time().ToDoubleT());
}
// Part 2: Test that expired file handlers for an app are cleaned up.
IN_PROC_BROWSER_TEST_F(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_F(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_F(WebAppFileHandlingOriginTrialBrowserTest,
ValidFileHandlerAreNotCleanedUpAtLaunch) {
EXPECT_EQ(0, file_handler_manager().TriggerFileHandlerCleanupForTesting());
}
IN_PROC_BROWSER_TEST_F(WebAppFileHandlingOriginTrialBrowserTest,
DisableForceEnabledFileHandlingOriginTrial) {
InstallFileHandlingPWA();
SetUpInterceptorNavigateToAppAndMaybeWait();
ASSERT_TRUE(file_handler_manager().AreFileHandlersEnabled(app_id()));
ASSERT_TRUE(file_handler_manager().GetEnabledFileHandlers(app_id()));
// Calling this on non-force-enabled origin trial should have no effect.
file_handler_manager().DisableForceEnabledFileHandlingOriginTrial(app_id());
EXPECT_TRUE(file_handler_manager().AreFileHandlersEnabled(app_id()));
EXPECT_TRUE(file_handler_manager().GetEnabledFileHandlers(app_id()));
// Force enables file handling.
file_handler_manager().ForceEnableFileHandlingOriginTrial(app_id());
// Calling this on force enabled origin trial should remove file handlers.
file_handler_manager().DisableForceEnabledFileHandlingOriginTrial(app_id());
EXPECT_FALSE(file_handler_manager().AreFileHandlersEnabled(app_id()));
EXPECT_EQ(nullptr, file_handler_manager().GetEnabledFileHandlers(app_id()));
}
IN_PROC_BROWSER_TEST_F(WebAppFileHandlingOriginTrialBrowserTest,
ForceEnabledFileHandling_IgnoreExpiryTimeUpdate) {
InstallFileHandlingPWA();
SetUpInterceptorNavigateToAppAndMaybeWait();
EXPECT_FALSE(file_handler_manager().IsFileHandlingForceEnabled(app_id()));
// Force enables file handling.
file_handler_manager().ForceEnableFileHandlingOriginTrial(app_id());
EXPECT_TRUE(file_handler_manager().IsFileHandlingForceEnabled(app_id()));
// Update origin trial expiry time from the App's WebContents.
base::RunLoop loop;
file_handler_manager().SetOnFileHandlingExpiryUpdatedForTesting(
loop.QuitClosure());
file_handling_expiry().SetExpiryTime(base::Time());
file_handler_manager().MaybeUpdateFileHandlingOriginTrialExpiry(
web_contents(), app_id());
loop.Run();
// Force enabled file handling should not be updated by the expiry time in
// App's WebContents (i.e. origin trial token expiry).
EXPECT_TRUE(file_handler_manager().IsFileHandlingForceEnabled(app_id()));
EXPECT_TRUE(file_handler_manager().IsFileHandlingAPIAvailable(app_id()));
EXPECT_TRUE(file_handler_manager().GetEnabledFileHandlers(app_id()));
}
IN_PROC_BROWSER_TEST_F(WebAppFileHandlingOriginTrialBrowserTest,
ForceEnabledFileHandling_IgnoreExpiryTimeInflightIPC) {
InstallFileHandlingPWA();
SetUpInterceptorNavigateToAppAndMaybeWait();
EXPECT_FALSE(file_handler_manager().IsFileHandlingForceEnabled(app_id()));
// Request to update origin trial expiry time from the App's WebContents, and
// force enables file handling origin trial before the expiry time reply is
// received.
base::RunLoop loop;
file_handler_manager().SetOnFileHandlingExpiryUpdatedForTesting(
loop.QuitClosure());
file_handling_expiry().SetExpiryTime(base::Time());
file_handling_expiry().SetBeforeReplyCallback(
base::BindLambdaForTesting([&]() {
EXPECT_FALSE(
file_handler_manager().IsFileHandlingForceEnabled(app_id()));
file_handler_manager().ForceEnableFileHandlingOriginTrial(app_id());
}));
EXPECT_FALSE(file_handler_manager().IsFileHandlingForceEnabled(app_id()));
file_handler_manager().MaybeUpdateFileHandlingOriginTrialExpiry(
web_contents(), app_id());
loop.Run();
// Force enabled file handling should not be updated by the inflight expiry
// time IPC.
EXPECT_TRUE(file_handler_manager().IsFileHandlingForceEnabled(app_id()));
EXPECT_TRUE(file_handler_manager().IsFileHandlingAPIAvailable(app_id()));
EXPECT_TRUE(file_handler_manager().GetEnabledFileHandlers(app_id()));
}
namespace {
constexpr char kBaseDataDir[] = "chrome/test/data/web_app_file_handling";
// This is the public key of tools/origin_trials/eftest.key, used to validate
// origin trial tokens generated by tools/origin_trials/generate_token.py.
constexpr char kOriginTrialPublicKeyForTesting[] =
"dRCs+TocuKkocNKa0AtZ4awrt9XKH2SQCI6o4FY6BNA=";
} // namespace
class WebAppFileHandlingOriginTrialTest : public WebAppControllerBrowserTest {
public:
void SetUpCommandLine(base::CommandLine* command_line) override {
WebAppControllerBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitchASCII(embedder_support::kOriginTrialPublicKey,
kOriginTrialPublicKeyForTesting);
}
void TearDownOnMainThread() override { interceptor_.reset(); }
protected:
void GrantFileHandlingPermission() {
auto* map =
HostContentSettingsMapFactory::GetForProfile(browser()->profile());
map->SetDefaultContentSetting(ContentSettingsType::FILE_HANDLING,
CONTENT_SETTING_ALLOW);
}
AppId InstallFileHandlingWebApp(GURL* start_url_out = nullptr) {
std::string origin = "https://file-handling-pwa";
// We need to use URLLoaderInterceptor (rather than a EmbeddedTestServer),
// because origin trial token is associated with a fixed origin, whereas
// EmbeddedTestServer serves content on a random port.
interceptor_ =
content::URLLoaderInterceptor::ServeFilesFromDirectoryAtOrigin(
kBaseDataDir, GURL(origin));
GURL start_url = GURL(origin + "/index.html");
auto web_app_info = std::make_unique<WebApplicationInfo>();
web_app_info->start_url = start_url;
web_app_info->scope = start_url.GetWithoutFilename();
web_app_info->title = u"A Web App";
blink::Manifest::FileHandler entry1;
entry1.action = start_url;
entry1.name = u"text";
entry1.accept[u"text/*"].push_back(u".txt");
web_app_info->file_handlers.push_back(std::move(entry1));
AppId app_id =
WebAppControllerBrowserTest::InstallWebApp(std::move(web_app_info));
// Here we need first launch the App, so it can update the origin trial
// expiry time in prefs. This is needed because the above InstallWebApp
// invocation bypassed the normal Web App install pipeline.
content::WebContents* web_content =
LaunchApplication(profile(), app_id, start_url);
web_content->Close();
if (start_url_out)
*start_url_out = start_url;
return app_id;
}
private:
std::unique_ptr<content::URLLoaderInterceptor> interceptor_;
};
IN_PROC_BROWSER_TEST_F(WebAppFileHandlingOriginTrialTest,
LaunchParamsArePassedCorrectly) {
GURL start_url;
const AppId app_id = InstallFileHandlingWebApp(&start_url);
GrantFileHandlingPermission();
base::FilePath test_file_path = NewTestFilePath("txt");
content::WebContents* web_content = LaunchApplication(
profile(), app_id, start_url,
apps::mojom::LaunchContainer::kLaunchContainerWindow,
apps::mojom::AppLaunchSource::kSourceFileHandler, {test_file_path});
EXPECT_EQ(1,
content::EvalJs(web_content, "window.launchParams.files.length"));
EXPECT_EQ(test_file_path.BaseName().AsUTF8Unsafe(),
content::EvalJs(web_content, "window.launchParams.files[0].name"));
}
class WebAppFileHandlingPolicyBrowserTest
: public WebAppFileHandlingBrowserTest {
public:
// Set the file handling policy to BLOCK the app between the PRE test and the
// actual test.
void SetUpInProcessBrowserTestFixture() override {
if (GetTestPreCount() == 0) {
SetFileHandlingBlockPolicy();
}
}
private:
void SetFileHandlingBlockPolicy() {
ON_CALL(provider_, IsInitializationComplete(testing::_))
.WillByDefault(testing::Return(true));
ON_CALL(provider_, IsFirstPolicyLoadComplete(testing::_))
.WillByDefault(testing::Return(true));
policy::BrowserPolicyConnector::SetPolicyProviderForTesting(&provider_);
policy::PolicyMap values;
base::Value list(base::Value::Type::LIST);
list.Append(base::Value("https://app.com"));
policy::PolicyMap::Entry entry_list(
policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_MACHINE,
policy::POLICY_SOURCE_CLOUD, std::move(list), nullptr);
values.Set(policy::key::kFileHandlingBlockedForUrls, std::move(entry_list));
provider_.UpdateChromePolicy(values);
}
testing::NiceMock<policy::MockConfigurationPolicyProvider> provider_;
};
IN_PROC_BROWSER_TEST_F(WebAppFileHandlingPolicyBrowserTest,
PRE_PolicySettingsBlockedUrl) {
InstallFileHandlingPWA();
EXPECT_EQ(registrar().GetAppIds().size(), 1u);
EXPECT_FALSE(registrar()
.AsWebAppRegistrar()
->GetAppById(app_id())
->file_handler_permission_blocked());
}
// Test that the app's `file_handler_permission_blocked` state should be updated
// on WebAppProvider system setup based on current permission settings.
IN_PROC_BROWSER_TEST_F(WebAppFileHandlingPolicyBrowserTest,
PolicySettingsBlockedUrl) {
auto* provider = WebAppProvider::Get(profile());
DCHECK(provider);
test::WaitUntilReady(provider);
std::vector<AppId> app_ids = registrar().GetAppIds();
EXPECT_EQ(app_ids.size(), 1u);
EXPECT_TRUE(registrar()
.AsWebAppRegistrar()
->GetAppById(app_ids[0])
->file_handler_permission_blocked());
}
IN_PROC_BROWSER_TEST_F(WebAppFileHandlingBrowserTest,
SettingsCategoryVisibility) {
// The file handling permission is visible in a general context.
const std::vector<ContentSettingsType>& all_categories =
site_settings::GetVisiblePermissionCategories();
EXPECT_FALSE(std::find(all_categories.begin(), all_categories.end(),
ContentSettingsType::FILE_HANDLING) ==
all_categories.end());
// The file handling permission is not visible in the context of an origin
// that doesn't correspond to a PWA.
std::vector<ContentSettingsType> categories_for_arbitrary_website =
site_settings::GetVisiblePermissionCategoriesForOrigin(
profile(), GURL("https://example.com"));
EXPECT_TRUE(std::find(categories_for_arbitrary_website.begin(),
categories_for_arbitrary_website.end(),
ContentSettingsType::FILE_HANDLING) ==
categories_for_arbitrary_website.end());
// The file handling permission *is* visible for a PWA origin.
InstallFileHandlingPWA();
std::vector<ContentSettingsType> categories_for_pwa =
site_settings::GetVisiblePermissionCategoriesForOrigin(
profile(), GetSecureAppURL().GetOrigin());
EXPECT_FALSE(std::find(categories_for_pwa.begin(), categories_for_pwa.end(),
ContentSettingsType::FILE_HANDLING) ==
categories_for_pwa.end());
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
// End-to-end test to ensure the file handler is registered on ChromeOS when the
// extension system is initialized. Gives more coverage than the unit tests for
// web_file_tasks.cc.
IN_PROC_BROWSER_TEST_F(WebAppFileHandlingOriginTrialTest,
IsFileHandlerOnChromeOS) {
const AppId app_id = InstallFileHandlingWebApp();
GrantFileHandlingPermission();
base::FilePath test_file_path = NewTestFilePath("txt");
std::vector<file_manager::file_tasks::FullTaskDescriptor> tasks =
file_manager::test::GetTasksForFile(profile(), test_file_path);
// Note that there are normally multiple tasks due to default-installed
// handlers (e.g. add to zip file). But those handlers are not installed by
// default in browser tests.
EXPECT_EQ(1u, tasks.size());
EXPECT_EQ(tasks[0].task_descriptor().app_id, app_id);
}
// Ensures correct behavior for files on "special volumes", such as file systems
// provided by extensions. These do not have local files (i.e. backed by
// inodes).
IN_PROC_BROWSER_TEST_F(WebAppFileHandlingOriginTrialTest,
NotHandlerForNonNativeFiles) {
const AppId app_id = InstallFileHandlingWebApp();
GrantFileHandlingPermission();
base::WeakPtr<file_manager::Volume> fsp_volume =
file_manager::test::InstallFileSystemProviderChromeApp(profile());
// File in chrome/test/data/extensions/api_test/file_browser/image_provider/.
base::FilePath test_file_path =
fsp_volume->mount_path().AppendASCII("readonly.txt");
std::vector<file_manager::file_tasks::FullTaskDescriptor> tasks =
file_manager::test::GetTasksForFile(profile(), test_file_path);
// Current expectation is for the task not to be found while the native
// filesystem API is still being built up. See https://crbug.com/1079065.
// When the "special file" check in file_manager::file_tasks::FindWebTasks()
// is removed, this test should work the same as IsFileHandlerOnChromeOS.
EXPECT_EQ(0u, tasks.size());
}
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
} // namespace web_app